Перейти к содержанию
    

Движок для исполнения пользовательского скрипта.

9 часов назад, sigmaN сказал:

В общем, над безопасностью нужно хорошо подумать )

Либо использовать запуск в вирт. адресном пространстве (если МК с MMU) или делать свою сборку ГЦЦ с анализом адресов при компиляции и шифровании байткода, тогда никто не влезет :wink3:

Сам решал подобную задачу несколько лет назад, решил использовать интрепретатор, за основу взял проект, подобный с4, вышеуказанный, оставил один тип данных int32, добавил многопоточность, обработчик ошибок, парсер дефайнов и несколько функций. Получилось хорошее быстродействие, простота написания скрипта, хорошая функциональность (можно писать полноценные программы), вообщем ТСу рекомендую такой подход.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 5/15/2019 at 4:45 PM, jenya7 said:

В продолжение темы с уважаемым xvr.

 

Мне нужно исполнять скрипт.

Пользователь передает его в микроконтролер, скрипт сохраняется (в FLASH) и исполняется. в основном это проверка входных условий (переменных) и если условия выполнились - изменение выходных условий (переменных).

Самоя сложное выражение (пока) это к примеру такое

Выражений может быть до .... ну не знаю пока 40.

в IF все AND - поэтому если хоть одно условие не выполниться THEN не исполняется.

 

  Reveal hidden contents


char *ToLowerCase(char *str)
{
  int i;
  for(i = 0; i < strlen(str); i++)
  {
      if(str[i]>='A' && str[i]<='Z')
       str[i]=str[i]+32;
  }
  //*str++ = '\0';
  return str;
}

uint32_t IsAlphaNumeric(char c)
{
    return ( ( c > 64 && c < 91 ) || (c > 96 && c < 123) || (c > 47 && c < 58) );
}

uint32_t IsLetter(char c)
{
    return (( c > 64 && c < 91 ) || (c > 96 && c < 123));
}

uint32_t IsDigit(char c)
{
    return (c > 47 && c < 58);
}

uint32_t IsNum(char c)
{
    return ((c > 47 && c < 58) || c == '.' /*|| c == '-'*/);
}

uint32_t IsCompare(char c)
{
   return ((c == '>') || (c == '<') || (c == '=') || (c == '!'));
}

uint32_t IsMath(char c)
{
   return ((c == '+') || (c == '-') || (c == '*') || (c == '/') || (c == '='));
}

uint32_t IsBitLogic(char c)
{
   return ((c == '|') || (c == '&'));
}

uint32_t IsGroup (char c)
{
   return ((c == '(') || (c == ')'));
}

uint32_t IsEnd(char c)
{
    return (c == ';');
}

uint32_t IsEndOfLine(char c)
{
   return ((c == '\n') || (c == '\r'));
}

uint32_t IsLogic(char *str)
{  
    char *l_str = ToLower(str); 
    return ( (strcmp(l_str,"or")==0) || (strcmp(l_str,"and")==0) || (strcmp(l_str,"||")==0) || (strcmp(l_str,"&&")==0));
}

uint32_t IsIn(char *str)
{  
    char *l_str = ToLower(str); 
    return ( (strcmp(l_str,"in")==0));
}

uint32_t IsOut(char *str)
{  
    char *l_str = ToLower(str); 
    return ( (strcmp(l_str,"out")==0));
}

uint32_t IsIf(char *str)
{  
    char *l_str = ToLower(str); 
    return ( (strcmp(l_str,"if")==0));
}

uint32_t IsThen(char *str)
{  
    char *l_str = ToLower(str); 
    return ( (strcmp(l_str,"then")==0));
}

uint32_t LEXER_GetToken(char *expr, TOKEN *token, uint32_t *exp_idx)
{
    //uint32_t idx = 0;
    uint32_t i  = 0;
    uint32_t slen = strlen(expr);
    uint32_t space = 0;
    
    //end of expression
    if(expr[*exp_idx]=='\0')
    {
        (*exp_idx)++;
	    return LEXER_OK;
    }

    //skip spaces
    while(expr[*exp_idx] == ' ')
    {
	//token->type = Space;
        space = 1;
        (*exp_idx)++;
	if (*exp_idx > slen)
	    return LEXER_FAIL;
    }

    //get compare
    if (IsCompare(expr[*exp_idx]))
    {
        i = 0;
        while (IsCompare(expr[*exp_idx]))
        {
            token->name[i++] = expr[(*exp_idx)++];
            if ( (i > 2) || (*exp_idx > slen))
	        return LEXER_FAIL;
        }
        token->name[i] = '\0';
        token->type = Compare;
        
        return LEXER_OK;
    }

    //get a variable - start with a letter
    if (IsLetter(expr[*exp_idx]))
    {
	i = 0;
	while (IsLetter(expr[*exp_idx]) || IsDigit(expr[*exp_idx]))
	{
	    token->name[i++] = expr[(*exp_idx)++];
	    if ( (i > TOKEN_MAX_SIZE) || (*exp_idx > slen))
		return LEXER_FAIL;
	}
	token->name[i] = '\0';
        
        if (IsIn(token->name))
            token->type = In;
        else if (IsOut(token->name))
            token->type = Out;
        else if (IsLogic(token->name))
            token->type = Logic;
        else if (IsIf(token->name))
            token->type = If;   
        else if (IsThen(token->name))
            token->type = Then;   
        else     
	    token->type = Alphanumeric;
        
        return LEXER_OK;
    }

    //get a number
    if (IsNum(expr[*exp_idx]))
    {
        i = 0;
	while (IsDigit(expr[*exp_idx]))
	{
	    token->name[i++] = expr[(*exp_idx)++];
	    if ( (i > TOKEN_MAX_SIZE) || (*exp_idx > slen) )
	        return LEXER_FAIL;
	}
	token->name[i] = '\0';
	token->type = Number;
        return LEXER_OK;
    }
        
    //get an arithmetic
    if (IsMath(expr[*exp_idx]))
    {
        // '-' may be operand or negative number
        if (expr[*exp_idx] == '-')
        {  
            i = 0;
            if (space == 1) 
            {
                space = 0;
                token->name[i++] = expr[(*exp_idx)++];
                if (IsDigit(expr[*exp_idx]))
                {
                   while (IsDigit(expr[*exp_idx]))
                   {
                       token->name[i++] = expr[(*exp_idx)++];
                       if ( (i > TOKEN_MAX_SIZE) || (*exp_idx > slen) )
	                   return LEXER_FAIL;
                   }
                   token->name[i] = '\0';
	           token->type = Number;
                   return LEXER_OK;
                }
                else
                {
                    while (IsMath(expr[*exp_idx]))
                    {
                        token->name[i++] = expr[(*exp_idx)++];
                        if ( (i > 2) || (*exp_idx > slen) )
                        return LEXER_FAIL;
                    }
                    token->name[i] = '\0';
                    token->type = Math;
                    return LEXER_OK;
                }
            }
            else
            {
                while (IsMath(expr[*exp_idx]))
                {
                    token->name[i++] = expr[(*exp_idx)++];
                    if ( (i > 2) || (*exp_idx > slen) )
                        return LEXER_FAIL;
                }
                token->name[i] = '\0';
                token->type = Math;
                return LEXER_OK;
            }
        }
        else
        {
            i = 0;  
            while (IsMath(expr[*exp_idx]))
            {
                token->name[i++] = expr[(*exp_idx)++];
                if ( (i > 2) || (*exp_idx > slen) )
                    return LEXER_FAIL;
            }
            token->name[i] = '\0';
            token->type = Math;
        }
        return LEXER_OK;
    }
    
    //get logic
    if (IsBitLogic(expr[*exp_idx]))
    {
        i = 0; 
        while (IsBitLogic(expr[*exp_idx]))
        {
            token->name[i++] = expr[(*exp_idx)++];
            if ( (i > 2) || (*exp_idx > slen) )
		return LEXER_FAIL;
        }
        token->name[i] = '\0';
        
        if (i==1)
           token->type = Bitlogic;
        else if (i==2)
	   token->type = Logic;
        
        return LEXER_OK;
    }
        
    if (IsGroup(expr[*exp_idx]))
    {
        i = 0; 
        token->name[i++] = expr[(*exp_idx)++];
        token->name[i] = '\0';
	token->type = Group;
        return LEXER_OK;
    }
    
    if (IsEnd(expr[*exp_idx]))
    {
        i = 0; 
        token->name[i++] = expr[(*exp_idx)++];
        token->name[i] = '\0';
        token->type = End;
        return LEXER_OK;
    }
        
    return LEXER_OK;
}

 

 

 

Самое простое - сделать интерпретатор виртуальной Форт-машины, FVM. Полной (по Тьюрингу) машине надо всего пару десятков команд (токенов), что реализуется в несколько сотен строк текста на С. Как вариант можно попробовать использовать такую тулзу для автоматической генерации интерпретатора и кросс-компилятора для него, однако написать интерпретатор самому "с нуля" может оказаться быстрее и проще.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 5/25/2019 at 6:37 PM, jenya7 said:

доинсталировал бизоновский пакет, прописал путь, все равно не видит бизон.

Что то не так прописали. Откройте шелл (командную строку - cmd.exe) и наберите там bison - должен написать что он bison, версию и пр. Если не пишет - либо нет bison'а, либо нет путей

On 5/25/2019 at 6:37 PM, jenya7 said:

я другого не понимаю - главное это интерпретер, он делает всю работу. какая разница каким способом распарсить и передать ему опкоды?

Никакой разницы. Можете хоть руками в хексе писать, или сделать свой собственный компилятор (если с bison'ом справится не можете).

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, xvr said:

Что то не так прописали. Откройте шелл (командную строку - cmd.exe) и наберите там bison - должен написать что он bison, версию и пр. Если не пишет - либо нет bison'а, либо нет путей

Никакой разницы. Можете хоть руками в хексе писать, или сделать свой собственный компилятор (если с bison'ом справится не можете).

 

cmd.exe bison -V возвращает
 

Quote

bison (GNU Bison) 3.0.4
Written by Robert Corbett and Richard Stallman.

Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 5/25/2019 at 8:23 PM, sigmaN said:

Раз уж тут никого не смущает бизон и запуск интерпретации на стороне ПК, то может быть просто воспользоваться LLVM? ))))

 

11 hours ago, mantech said:

Либо использовать запуск в вирт. адресном пространстве (если МК с MMU) или делать свою сборку ГЦЦ с анализом адресов при компиляции и шифровании байткода, тогда никто не влезет

42 minutes ago, =AK= said:

Самое простое - сделать интерпретатор виртуальной Форт-машины, FVM.

Господа, ТС не может собрать готовый тривиальный проект VS на С++ (размером в 500 строк), а вы ему тут LLVM, FORTH, GCC и пр :crazy:

 

PS. FORTH не надо - это не гуманно по отношению к пользователям :biggrin:

1 minute ago, jenya7 said:

cmd.exe bison -V возвращает

Значит должно работать - VS перезапускали (после прописывания путей)?

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Прикладываю собранный компилятор скрипта (32х битный код, статическая сборка - должно запустится где угодно)

 

 

script.zip

 

PS. Дайте файл var_defs.inc с реальным списком переменных - соберу с ним

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, xvr said:

Прикладываю собранный компилятор скрипта (32х битный код, статическая сборка - должно запустится где угодно)

 

 

script.zip

 

PS. Дайте файл var_defs.inc с реальным списком переменных - соберу с ним

 

с переменными не совсем понятно. у меня массив переменных. скажем 8 моторов - отсюда восемь переменных содержащих позицию с энкодера POS0-POS7. как парсер поймет к какой переменной обратиться?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

13 minutes ago, jenya7 said:

с переменными не совсем понятно. у меня массив переменных. скажем 8 моторов - отсюда восемь переменных содержащих позицию с энкодера POS0-POS7. как парсер поймет к какой переменной обратиться?

В файле var_defs.inc должны быть такие строчки:

VAR(POS0)
VAR(POS1)
VAR(POS2)
VAR(POS3)
VAR(POS4)
VAR(POS5)
VAR(POS6)
VAR(POS7)

Интерпретатор ожидает, что в МК есть 8 переменных (POS0, POS1, POS2, ... POS7), все типа int32_t (или аналогичного). И что они будут видны из кода самого интерпретатора.

Если у вас нечто большее, то адаптируйте код в строках 58-60 в файле interpreter.c

Адреса переменных связываются с индексами переменных посредством массива vars в строках 6-10

 

Можно в макрос VAR добавить полей, что бы можно было задать все возможные варианты адресации ваших переменных

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

так мы не получим адрес переменной.

static int32_t* vars[] = {
#define VAR(nm) &nm,
#include "var_defs.inc"
 NULL
};

у меня в скрипте не реальные имена а символы.

я делал так чтоб получить адрес переменной

static const SCRIPT_VAR script_vars[] = 
{
    { "flimsw",    6,    VAR_TYPE_FLSW },
    { "rlimsw",    6,    VAR_TYPE_RLSW },
    { "pos",       3,    VAR_TYPE_POS  },
    { "cur",       3,    VAR_TYPE_CUR  },
    { "dir",       3,    VAR_TYPE_DIR  },
    { "pwm",       3,    VAR_TYPE_PWM  },
    { "mot",       3,    VAR_TYPE_MOT  },
    { "com",       3,    VAR_TYPE_COM  },
    { "var",       3,    VAR_TYPE_UVAR },
};

void * COMP_ParseVar(char * var, uint8_t *var_type, uint8_t *var_num)
{
    uint32_t num, i, len;
    
    for (i = 0; i < script_vars_len; i++)
    {
        //command found
        if(strncmp(var, script_vars[i].name, script_vars[i].str_len)==0)
        {
            len = script_vars[i].str_len;
            if (strlen(var) < (len+1))
                return 0;
            
            num = atoi(&var[len]);
            if (num < MAX_MOTORS)
            {
                *var_num = num;
                *var_type = script_vars[i].var_type;
                
                switch (script_vars[i].var_type)
                {
                    case VAR_TYPE_FLSW: return &motor_rt_params[num].flimsw_state; break;
                    case VAR_TYPE_RLSW: return &motor_rt_params[num].rlimsw_state; break;
                    case VAR_TYPE_POS:  return &motor_rt_params[num].position;     break;
                    case VAR_TYPE_CUR:  return &motor_rt_params[num].current;      break;
                    case VAR_TYPE_DIR:  return &motor_rt_params[num].direction;    break;
                    case VAR_TYPE_PWM:  return &motor_rt_params[num].speed;        break;
                    case VAR_TYPE_MOT:  return &motor_rt_params[num].action;       break;
                    case VAR_TYPE_COM:  return &g_command;                         break;
                    case VAR_TYPE_UVAR: return &user_vars[num];                    break;
                    case VAR_TYPE_CONST: return &user_consts[num];                 break;
                }
            }
            else
                return 0;
        }
    }
   
    return 0;
}

 

Изменено пользователем jenya7

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Тогда вам нужно первым делом поделить все переменные на 4 группы (по способу адресации): те, что лежат в motor_rt_params, user_* и g_command

Для них сделать 4 разных макроса VAR (либо физичекси в виде разных макросов либо в виде дополнительного параметра в самом VAR), и обеспечить конвертирование макросов в реальные адреса.

 

Для начала - сколько у вас мотороров, пользовательских констант и переменных? (Уместится в 280 штук в сумме?)

Далее (если вы не хотите выписывать руками ы var_defs.inc все переменные со всеми возможными индексами) надо изменить парсинг имён переменных в лексере (что бы номер брать отдельно от имени)

Ну и решить какие именно 30 символов вам более дороги (что бы поместить их в начало списка - для них байткод короче на 1 байт на переменную)

 

PS. bison в VS запустился?

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, xvr said:

Тогда вам нужно первым делом поделить все переменные на 4 группы (по способу адресации): те, что лежат в motor_rt_params, user_* и g_command

Для них сделать 4 разных макроса VAR (либо физичекси в виде разных макросов либо в виде дополнительного параметра в самом VAR), и обеспечить конвертирование макросов в реальные адреса.

 

Для начала - сколько у вас мотороров, пользовательских констант и переменных? (Уместится в 280 штук в сумме?)

Далее (если вы не хотите выписывать руками ы var_defs.inc все переменные со всеми возможными индексами) надо изменить парсинг имён переменных в лексере (что бы номер брать отдельно от имени)

Ну и решить какие именно 30 символов вам более дороги (что бы поместить их в начало списка - для них байткод короче на 1 байт на переменную)

 

PS. bison в VS запустился?

 

для моторов 6*8, ну еще разных переменных около 20.

а если передать дефайнами?

#define POS0 (@motor_rt_params[0].position)

или

#define POS(N) (@motor_rt_params[N].position)

VS не видит bison.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

18 minutes ago, jenya7 said:

ну еще разных переменных около 20.

Они тоже в виде массивов заданы, или самыми разными именами?

И кто такие user_vars и user_consts?

 

30 minutes ago, jenya7 said:

а если передать дефайнами?

Там и так какие то дефайны появятся. Не хотелось бы все 48 штук (6*8) руками писать.

31 minutes ago, jenya7 said:

VS не видит bison. 

Попробуйте задать в VS настройках проекта (правая кнопка мыши на script_gramm.y -> Свойства -> Настраиваемые инструменты -> Общее -> Командная строка) явный путь к bison

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

мне кажется инерпритатор не возьмет выражение типа VAR = 5-3. он не приравняет аккумулятивно. я не вижу механизма который сначала вычисляет правую часть равенства.

Изменено пользователем jenya7

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

6 hours ago, jenya7 said:

мне кажется инерпритатор не возьмет выражение типа VAR = 5-3. он не приравняет аккумулятивно. я не вижу механизма который сначала вычисляет правую часть равенства.

 

Возмёт. Более того, компилятор из этого сделает VAR = 2 (разумеется это должно быть внутри какого то then)

Исходник: if 1 then PWM0 = 5-3

Байткод:

# if 1 then PWM0 = 5-3
0000: 01              Const 1
0001: E2 02           JumpF +2 (=> 0005)
0003: 02              Const 2
0004: A1              SetVar #1 (PWM0)

0005: FF              Stop

 

PS. Всё ещё жду ответ на вопрос - И кто такие user_vars и user_consts? И есть ли наиболее используемые переменные (или все используются одинаково часто)?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, xvr said:

Возмёт. Более того, компилятор из этого сделает VAR = 2 (разумеется это должно быть внутри какого то then)

Исходник: if 1 then PWM0 = 5-3

Байткод:


# if 1 then PWM0 = 5-3
0000: 01              Const 1
0001: E2 02           JumpF +2 (=> 0005)
0003: 02              Const 2
0004: A1              SetVar #1 (PWM0)

0005: FF              Stop

 

PS. Всё ещё жду ответ на вопрос - И кто такие user_vars и user_consts? И есть ли наиболее используемые переменные (или все используются одинаково часто)?

user_consts я убрал а user_vars это массив переменных int user_vars[8]. он нужен для промежуточных вычислений и всяких флагов (меток).

 

хм. как то хитро. не вижу чтоб на стак легли 5 и 3 и операции между ними, но я так понимаю что  JumpF +2 (=> 0005) делает 5-3?

 

Изменено пользователем jenya7

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...