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

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

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

 

14 hours ago, xvr said:

Нашёл один свой движок, но вам он похоже не поможет - слишком полнофункциональный :( (И большой)

 

Вот пример того, что ему можно дать на вход:

(Он встраивался в WEB сервер)

Вот выхлоп (в виде листинга) первой функции:

 

Давайте лучше ваш случай рассмотрим.

Что может быть в условии IF и в его теле?

Все IF'ы выполняются последовательно или до первого сработавшего?

Где выполняется трансляция программы (в МК или на хосте)?

Куда записывается результат? (В FLASH, внешний DataFLASH или ещё куда)?

 

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

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

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

Quote

IF DIR=2 AND POS0>POS1 AND POS0-POS1>20 THEN PWM0-=100 AND PWM1+=100

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

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

Для начала я сделал лексер

Spoiler

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;
}

 

после лексера я получаю

token.name - "IF"
token.type - If
token.name - "DIR"
token.type - Alphanumeric
token.name - "="
token.type - Compare
token.name - "2"
token.type - Number
token.name - "AND"
token.type - Logic

и так далее.

 

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

 

 

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

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


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

А Вам точно не подойдёт ни один из множества готовых встраиваемых скриптовых движков?

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


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

1 hour ago, jenya7 said:

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

А можно ссылку с его профиля исправить на его тему, в продолжение которой вы открыли эту)

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


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

1 hour ago, arhiv6 said:

А Вам точно не подойдёт ни один из множества готовых встраиваемых скриптовых движков?

я копался но так и не нашел чего то удобоваримого ''из коробки''.

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


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

2 hours ago, haker_fox said:

А можно ссылку с его профиля исправить на его тему, в продолжение которой вы открыли эту)

исправил.

2 hours ago, arhiv6 said:

А что Вы подразумеваете под "удобоваримостью" ?

Ну скажем LWIP, FreeRTOS - легко портируется, куча примеров и документации. на таком уровне не нашел движка. все как то витают на абстрактном леере.

и со списком этим я как то работал. там такие монстры. а мне надо что то заточенное под эмбедед.

 

и еще замечание. все примеры скриптов которые я видел - это создание переменных и операции с ними.

а мне нужно работать со своими переменными - данные в них я получаю от слейвов по CAN и мне нужно отслеживать эти данные и если нужно модифицировать данные и отсылать слейвам обратно.

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

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


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

возьмите пример калькулятора из книжки Страуструпа по плюсам, в него очень легко можно добавить логические операции и тернарный оператор ? :

переменные, чтение/запись которых можно привязать к своим функциям там уже впринципе есть.

 

ну а если не хочется каждый раз строку интерпретировать, можно "скомпилировать" в промежуточный байт-код https://github.com/rswier/c4

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


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

1 hour ago, _pv said:

возьмите пример калькулятора из книжки Страуструпа по плюсам, в него очень легко можно добавить логические операции и тернарный оператор ? :

переменные, чтение/запись которых можно привязать к своим функциям там уже впринципе есть.

 

ну а если не хочется каждый раз строку интерпретировать, можно "скомпилировать" в промежуточный байт-код https://github.com/rswier/c4

спасибо. посмотрю. я конечно не хочу все время гонять строковое выражение. это не оптимально по скорости. в моем варианте я ''расфасовываю'' его в структуру. проблема что структура получается очень большая.

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


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

В идеале вам нужно компилятор скрипта в байткод оставить на хосте (там писать проще, чем на МК), а на МК гнать уже готовый байт код. Если это подойдёт могу рассказать как писать компилятор

Quote

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

Т.е. цепочка простых условий соединённых через AND, после THEN - цепочка присваиваний (возможно с арифметикой). В таком случае можно сделать ещё более простой байткод:

Quote

IF DIR=2 AND POS0>POS1 AND POS0-POS1>20 THEN PWM0-=100 AND PWM1+=100

	Mark label_end
	ReadVar DIR
	Const 2
	CheckEQ
	ReadVar POS0
	ReadVar POS1
	CheckGT
	ReadVar POS0
	ReadVar POS1
	Sub
	Const 20
	CheckGT
	Const -100
	AddVar PWM0
	Const 100
	AddVar PWM1
label_end:

21-29 байтов

 

Команда Mark задаёт точку выхода, команды Check* проверяют верхушку стека (2 ячейки) и если условие не выполняется - переходят на метку из последнего Mark

 

Если подход устравивает, можно будет двигаться дальше

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


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

1 hour ago, xvr said:

В идеале вам нужно компилятор скрипта в байткод оставить на хосте (там писать проще, чем на МК), а на МК гнать уже готовый байт код. Если это подойдёт могу рассказать как писать компилятор

Т.е. цепочка простых условий соединённых через AND, после THEN - цепочка присваиваний (возможно с арифметикой). В таком случае можно сделать ещё более простой байткод:

17-25 байтов

 

Команда Mark задаёт точку выхода, команды Check* проверяют верхушку стека (2 ячейки) и если условие не выполняется - переходят на метку из последнего Mark

 

Если подход устравивает, можно будет двигаться дальше

да конечно. почему бы не компилить скрипт на хосте. это наверное даже лучше.

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

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


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

Just now, jenya7 said:

да конечно. почему бы не компилить скрипт на хосте. это наверное даже лучше.

Ок, тогда предлагаю взять готовый тулз для построения компиляторов (flex+bison например)

 

Ещё хочется определится с набором поддерживаемой арифметики - какие операции понадобятся? В частности нужна ли логика (кроме AND в теле IF)?

Сколько переменных с которыми надо работать (по порядку величины)?

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


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

1 hour ago, xvr said:

Ок, тогда предлагаю взять готовый тулз для построения компиляторов (flex+bison например)

 

Ещё хочется определится с набором поддерживаемой арифметики - какие операции понадобятся? В частности нужна ли логика (кроме AND в теле IF)?

Сколько переменных с которыми надо работать (по порядку величины)?

в IF из логики -AND и OR. из операторов + - * % > < >= <= == !=. не думаю что понадобиться что то еще.

 

flex+bison? они не идут в виде готовых тулзов. или я не там смотрю?

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

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


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

Используйте Pawn. Синтаксис си-подобный, позволяет компилировать исходный код в байт-код на строне хоста.

4 часа назад, jenya7 сказал:

и еще замечание. все примеры скриптов которые я видел - это создание переменных и операции с ними. 

https://iu4.ru/konf/2014_ts/02_tom01.pdf, страница 50 - "Использование скриптового движка Pawn для реализации системы управления сервоприводом" на примере stm32 показано как импортировать в прошивку виртуальную машину и управлять сервами из скрипта. Такой пример подойдёт?

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


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

1 hour ago, arhiv6 said:

Используйте Pawn. Синтаксис си-подобный, позволяет компилировать исходный код в байт-код на строне хоста.

https://iu4.ru/konf/2014_ts/02_tom01.pdf, страница 50 - "Использование скриптового движка Pawn для реализации системы управления сервоприводом" на примере stm32 показано как импортировать в прошивку виртуальную машину и управлять сервами из скрипта. Такой пример подойдёт?

в свое время я портировал Pawn в свой проект. но как то не пошло. может стоит вернуться, взглянуть еще раз.

пример очень примитивный. он по сути не обращается к переменным напрямую.

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

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


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

31 minutes ago, jenya7 said:

в IF из логики -AND и OR.

Если есть OR, то финт с одной точкой выхода не пойдёт, увы. Надеюсь, что логические операции внутри арифметики не нужны?

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


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

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

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

Гость
Ответить в этой теме...

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

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

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

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

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

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