DragonScript - компилируемый язык программирования со строгой статической типизацией и СИ-подобным синтаксисом. Данный язык программирования разрабатывается как средство решения задачи промышленной автоматизации. Основные цели: безопасность, достаточная производительность, удобство, простота.

Рассмотрим ключевые особенности DragonScript.

 

Типы данных

Для более гибкого управления расходом памяти, в DragonScript применяются переменные различной разрядности: 

  • byte (int8) - целое знаковое число, диапазон -128..127, занимает 1 байт;
  • short (int16) - целое знаковое число, диапазон -32768..32767, занимает 2 байта;
  • int (int32) - целое знаковое число, диапазон -2^31..2^31 - 1, занимает 4 байта;
  • long (int64) - целое знаковое число, диапазон -2^63..2^63 - 1, занимает 8 байт;
  • float - число с плавающей запятой одинарной точности, занимает 4 байта;
  • double - число с плавающей запятой двойной точности, занимает 8 байт;
  • bool - логическое значение, занимает 1 байт.

Для упрощения правил преобразования типов, беззнаковые числа введены не были. Этот подход был позаимствован у Java.

При размещении данных типов в оперативной памяти виртуальной машины никакой дополнительный расход памяти происходить не будет, т.е. если в программе определены 10 переменных типа int, то в ОЗУ будет занято 10*4=40 байт.

В DragonScript имеется поддержка пользовательских типов данных, которые называются union. Эти типы данных отдаленно похожи на структуры в языке С/С++, и позволяют объединить несколько переменных в единый объект. В силу упрощения языка, union имеют ограничение на типы элементов: один union может состоять только из базовых типов, описанных выше, и не может включать в себя другие union-ы. Данная единица языка пока находится в стадии разработки и ее подробное описание выйдет позже.

 

Управляемый пользовательский код и кроссплатформенность

Пользовательское приложение должно быть изолировано от остального программного окружения ПЛК, и, при возникновении ошибки в пользовательском коде (например, попытка записи в неразрешенные области памяти, переполнение стека), системная часть ПО должна сохранить работоспособность. Это позволит реализовать различные механизмы восстановления после ошибки, такие как перевод выходов ПЛК в безопасное состояние, отправка отчета на пульт диспетчера, перезапуск пользовательского приложения. 

В относительно простых и дешевых микроконтроллерах, на которых строят ПЛК, отсутствуют аппаратные блоки, например, как блок управления памятью (MMU), которые позволяют  реализовать данную функциональность, поэтому, в качестве среды выполнения пользовательского кода используется виртуальная машина (ВМ). ВМ берет на себя роль гипервизора и среды выполнения пользовательского приложения. Кроме того, ВМ позволяет реализовать кроссплатформенность генератора машинного кода компилятора DragonScript, т.е. нет необходимости вносить изменения в компилятор при смене аппаратной платформы, на которой построен ПЛК.

 

Безопасная работа с массивами

В DragonScript, при выполнении обращения к элементу массива по индексу, выполняется проверка выхода индекса за границы массива. Это позволяет исключить целый класс ошибок, связанных с порчей данных в переменных, расположенных в окрестности массива. Также, в DragonScript отсутствует арифметика указателей.

Проверка выхода за границы массива влечет за собой два накладных расхода:

  1. дополнительный расход памяти на хранение длины массива, а именно на каждый массив приходится дополнительно 2 байта данных, т.е. массив, состоящий из 10 элементов типа byte будет занимать 12 байт;
  2. дополнительный код, выполняющий проверку выхода за границы массива при обращении к элементу по индексу.

Ограничение, накладываемое на массивы, заключается в том, что один массив может иметь максимум 65535 элементов, однако, для поставленных задач, это ограничение не является существенным.

 

Производительность

Код, написанный на компилируемом языке программирования, выполняется намного быстрее, нежели код интерпретируемого языка. Поэтому, для получения максимально возможной скорости выполнения пользовательского приложения, исходные тексты программы на DragonScript компилируются в байт-код, который в дальнейшем будет выполняться на виртуальной машине. Система команд виртуальной машины оптимизирована для выполнения скомпилированного кода на DragonScript, и, в большинстве случаев, одна операция на DragonScript компилируется в одну инструкцию виртуальной машины, что положительным образом сказывается на производительности.

Благодаря механизму системных вызовов, речь о котором пойдет далее, есть возможность ускорения работы критических участков кода. Например, алгоритмы быстрого преобразования Фурье (FFT), цифровые фильтры, и др., можно реализовать на языке СИ, и включить в состав Runtime ПЛК, и, при необходимости, вызывать данный код из пользовательского приложения на DragonScript.

 

Удобство для программиста

DragonScript должен быть максимально комфортным языком программирования, при этом быть достаточно быстрым для работы в виртуальной машине на микроконтроллере.

DragonScript имеет СИ-подобный синтаксис. Данный синтаксис является наиболее комфортным и знакомым для большинства программистов: множество языков, ориентированных на решения разных задач, имеют СИ-подобный синтаксис, например, C++, Java, C#, php, JavaScript, Perl, и многие другие. 

Еще одним немаловажным фактором является поддержка модульности. DragonScript позволяет разбить программу на отдельные модули. Способ организации модулей похож на стиль, принятый в языке Pyton. Для того, чтобы подключить какой-либо модуль к текущему модулю используется директива use. Пример, есть некий модуль с именем testModule:

//Файл testModule.dgc

public byte func1()
{
  return -1;
}

 

Подключим его в модуле main и вызовем из main функцию func1, размещенную в testModule

//Файл main.dgc
use testModule

void main()
{
  byte a;
  a = testModule.func1();
}

Для модулей в DragonScript не требуется написание отдельных заголовочных файлов, свойственных C/C++. Компилятор сам генерирует всю необходимую информацию во время компиляции.

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

 

Расширяемость

В DragonScript и виртуальной машине реализован гибкий механизм вызова СИ-кода из кода на DragonScript, который называется "системные вызовы". Это позволяет осуществлять обмен данными между бай-кодом и внешним миром, например, обращение к железу из пользовательского приложения (чтение состояния входов ПЛК, установка значений выходов), ускорение работы алгоритмов (FFT или PID-регулятор), и многое другое.

Работают системные вызовы следующим образом. В исходный код Runtime ПЛК добавляется обработчик для системного запроса с необходимым id. Далее, пишется код обработчика, который выполняет необходимые действия и возвращает результат в код, выполняющийся на ВМ. Для того, чтобы использовать данный системный запрос, в исходном коде пользовательского приложения необходимо выполнить его объявление. Это выполняется с помощью ключевого слова native.  Небольшой пример:

//Файл main.dgc
native bool InputGetState(byte inputID) @ 0x10;
native void OutputSetState(byte outputID, bool value) @ 0x11;

void main()
{
  while(true)
  {
    if(InputGetState(5))
      OutputSetState(11, true);
    else
      OutputSetState(11, false);
  }
}

В этом примере объявлены два системных запроса: InputGetState с id 0x10, и OutputSetState с id 0x11. Подобные объявления можно описать в отдельном модуле, который будет специфичен для конкретного типа ПЛК.