Введение
Часто при разработке сложных программных систем используется более одного языка программирования — даже в рамках одного файла с исходным кодом. В таком случае принято говорить об основном (или исходном) языке и одном или нескольких встроенных языках. Из строковых выражений основного языка динамически формируются программы на отличном от него языке, которые потом интерпретируются специальными, работающими во время исполнения компонентами, такими как базы данных или веб-браузеры. Большинство языков программирования общего назначения могут играть роль как основного, так и встроенного языка. Ниже приведены примеры использования встроенных языков.
Выполнение кода на JavaScript из кода, написанного на Java:
import javax.script.*;
public class InvokeScriptFunction {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
// JavaScript code in a String
String script = "function hello(name) {print('Hello, ' + name);}";
// evaluate script
engine.eval(script);
}
}
Код с использованием динамического SQL:
CREATE PROCEDURE [dbo].[MyProc] @TABLERes VarChar(30)
AS
EXECUTE (
'INSERT INTO ' + @TABLERes + ' (sText1)' + ' SELECT ''Additional condition: '' + sName'
+ ' from #tt where sAction = ''1000000''')
GO
Использование нескольких различных встроенных в PHP языков (MySQL, HTML):
<?php
// Embedded SQL
$query = 'SELECT * FROM my_table';
$result = mysql_query($query);
// HTML markup generation
echo "<table>n";
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
echo "t<tr>n";
foreach ($line as $col_value) {
echo "tt<td>$col_value</td>n";
}
echo "t</tr>n";
}
echo "</table>n";
?>
Встроенные языки позволяют компенсировать недостаток выразительности языков общего назначения в случае использования их в контексте специфичном для предметной области. Однако использование такого подхода сопряжено с рядом трудностей. Динамически формируемые выражения обычно конструируются из строковых констант и выражений основного языка посредством конкатенации в циклах, ветках условных операторов или рекурсивных процедурах, причем эти структуры могут вкладываться в друг друга, что порождает множество различных вариантов. Фрагменты кода на встроенных языках воспринимаются компилятором исходного языка как простые строки, не подлежащие анализу. Таким образом, стандартные средства не позволяют проводить даже простой синтаксический анализ динамически формируемых выражений. Невозможность статической проверки корректности формируемого выражения приводит к высокой вероятности возникновения ошибок во время выполнения программы.
Распространенной практикой при написании кода является использование интегрированных сред разработки (IDE), производящих подсветку синтаксиса и автодополнение, сигнализирующих о синтаксических ошибках, предоставляющих возможность проводить различный рефакторинг. Все эти функции значительно упрощают процесс разработки и отладки приложений. Полезными могут оказаться инструменты, проводящие анализ множества выражений, которые динамически формируются из строковых выражений основного языка во время выполнения программы. Данный процесс назовем статическим анализом динамически формируемых выражений или абстрактным
анализом.
Существующие инструменты
На данный момент существует ряд инструментов для работы с динамически формируемыми выражениями в конкретных языках, хорошо показавшие себя на практике. Инструменты различаются используемыми подходами а так же простотой добавления новых языковых расширений. Ниже приведен краткий обзор возможностей инструментов.
PhpStorm
PhpStorm — интегрированная среда разработки для PHP, которая осуществляет подсветку и автодополнение встроенного кода на HTML, CSS, JavaScript, SQL. Однако такая поддержка осуществляется только в случаях, когда строка получена без использования каких-либо строковых операций (например, конкатенации). В качестве примера рассмотрим программу, представленную на рисунке 1. Операторы “.” и “.=” являются операторами конкатенации и конкатенации с присваиванием. Правая часть присваивания переменной $hello1
распознана и подсвечена как выражение на языке HTML. Однако про переменную $string
такого сказать нельзя.
Рис. 1. Фрагмент кода на PHP в PHPStorm.
Также PHPStorm для каждого встроенного языка предоставляет отдельный текстовый редактор.
К недостаткам данного инструмента следует отнести то, что если в запросах на встроенных языках содержатся ошибки, то никаких уведомлений о них выведено не будет (см. $error на рисунке).
IntelliLang
IntelliLang — плагин к средам разработки PHPStorm и IntelliJ IDEA, позволяющий подсвечивать и указывать об ошибках во встроенных языках (HTML, SQL, XML, JavaScript) в указанных средах разработки.
Пример работы IntelliLang приведён на рисунке 2.
Рис. 2. Фрагмент с кода на встроенном языке Java в IDEA.
Для среды разработки IDEA плагин Intellilang также предоставляет отдельный текстовый редактор для работы со встроенным языком (по аналогии PHPStorm).
К недостаткам плагина следует отнести то, что для каждого строковой переменной вручную нужно указывать язык. Для примера рассмотрим рисунок 3. Отметим переменную html
как выражение на языке HTML. После этого будут подсвечены только теги непосредственно в правой части определения. При этом правая часть переменной body
будет по-прежнему восприниматься как обычная строка, хотя переменная участвует в формировании выражения, отмеченного как HTML.
Рис. 3: Строка “<html>”
отмечена как строка языка HTML, а body
не отмечено
Alvor
Alvor — это плагин к среде разработки Eclipse, предназначенный для статической валидации SQL-выражений, встроенных в код на Java. Он не требует указания явного атрибута языка, с помощью которого составляются динамически формируемые выражения. Структура инструмента представлена на рисунке 4].
Рис. 4 Структура инструмента Alvor
Множество формируемых в точке выполнения запроса строковых выражений представляется абстрактной строкой — в инструменте Alvor под этим понимается регулярное выражение. Затем осуществляется абстрактный анализ, который сообщает о наличии лексических и синтаксических ошибок. Если регулярное выражение порождает несколько ошибочных строк, то указывается только одна из них (см. рисунок 5).
Рис.5 Подсветка ошибок в Eclipse IDE
Данный инструмент осуществляет статическую проверку SQL-запросов, полученных с помощью конкатенаций и условных выражений в интерактивном режиме, проводит межпроцедурный анализ. Alvor поддерживает несколько диалектов SQL (PLSQL, MySQL), однако добавление новых языков требует ручного изменения уже существующего кода.
Java String Analyzer
Java String Analyzer — инструмент, который отвечает на вопрос о синтаксической корректности динамически формируемых выражений, встроенных в Java. Структура инструмента JSA представлена на рисунке 6.
Рис. 6 Структура инструмента JSA
JSA позволяет обрабатывать несколько встроенных языков, меняя при этом только front-end инструмента, который отвечает за представление входных данных в виде flow-графа. Back-end инструмента позволяет записать flow-граф в виде контексно-свободной грамматики, которая затем аппроксимируется регулярной грамматикой и уже по ней строится конечный автомат. Этот автомат для каждого строкового выражения в Java является аппроксимацией его возможных значений. По входной грамматике по аналогии с предыдущим строится конечный автомат. Сравниваются два полученных автомата, и если они совпадают, то считается, что динамически формируемое выражение синтаксически корректно.
PHP String Analyzer
PHP String Analyzer — это инструмент для статической проверки динамически формируемых выражений, порождаемых программами на PHP. Встроенными языками могут быть HTML и XML. Инструмент основан на идеях алгоритма JSA. Авторы инструмента предложили заменить регулярную аппроксимацию на контекстно-свободную. Результат работы также является ответом на вопрос о синтаксической корректности выражений встроенного языка.
Заключение
Проблема статического анализа встроенных языков может не иметь применения при разработке новых продуктов, так как уже достаточно хорошо развиты более безопасные средства метапрограммирования и порождающего программирования, различные ORM. Однако при поддержке, сопровождении или миграции сравнительно старых систем проблема встаёт очень остро. При этом не обязательно рассматривать действительно древние системы, написанные на COBOL. Взаимодействие систем на C++, Java с базами данных до сравнительно недавнего времени было организовано с помощью выполнения строковых выражений.
Однако выяснено, что область сложная и интересная, многие текущие инструменты слабо приспособлены для расширения и сложных анализов, что послужило причиной более глубокого изучения области и сподвигло к работам над созданием платформы для работы со встроенными языками. В дальнейшем будет рассказано о некоторых теоретических аспектах анализа встроенных языков и некоторых практических результатах в этой области.
Автор: rsdpisuy