Интерпретатор (Interpreter) — шаблон проектирования
30 января 2020
Шаблон предлагает реализовать собственный язык запросов и его интерпретатор. Это позволяет писать выражения, формулы, фильтры, шаблоны, анализировать их, переводить в программные действия и выполнять обработки.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 4 Кб)
В шаблоне используются следующие понятия:
В примере задачи, правило, написанное на нашем языке, будет выглядеть так:
Код скрипта:
В нашем правиле, записанном в переменной $rule, к терминальным выражениям относятся фразы:
А к нетерминальным выражениям относятся фразы:
Результат работы скрипта test.php
ПРИМЕР ЗАДАЧИ: является ли числом, обязательность заполнения поля, проверка величины значения — больше или меньше).
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 4 Кб)
В шаблоне используются следующие понятия:
- Терминальное выражение (Terminal expression) — неделимые части грамматики будущего языка. К терминальным выражениям относятся цифры, слова и любые другие элементы, которые будут считываться интерпретатором без необходимости разделения/декомпозиции элемента на составные части. Под каждый тип терминального выражения создаётся свой класс с методом interpret($context) для интерпретации выражения;
- Нетерминальное выражение (Nonterminal expression) — составные части грамматики будущего языка. К нетерминальным выражениям относятся конструкции с участием знаков математических операций, операторы и другие элементы, которые интерпретатор может разбить на простые или терминальные выражения;
- Контекст — глобальный буфер, хранящий промежуточные вычисления, значения переменных и констант. Используется при интерпретации выражений.
В примере задачи, правило, написанное на нашем языке, будет выглядеть так:
$rule = 'Это число, обязательное, значение > 10, значение <= 50';
test.php
Файл содержит тестовый скрипт, в котором задаётся массив потенциальных значений текстового поля, затем указывается правило для проверки этих значений. Далее происходит обход массива и проверка значений на соответствие правилу с помощью метода Field::checkValue($value, $rule) класса полей Field.Код скрипта:
<?
//Подключение класса поля
require_once(__DIR__.'/field.class.php');
//Массив значений, которые будем проверять
$arValues = array(
5,
15,
'',
'Привет',
50
);
//Правило проверки значения
$rule = '"Это число, обязательное, значение > 10, значение <= 50';
//Обходим каждый элемент массива и проверяем значение на соответствие правилу
foreach ($arValues as $value) {
$isValid = Field::checkValue($value, $rule);
if ($isValid == true) {
echo 'Значение «'.$value.'» корректное<br>';
} else {
echo 'Значение «'.$value.'» некорректное<br>';
}
}
?>
field.class.php
Содержит класс Field для работы с текстовыми полями. В рамках демонстрации класс содержит один статический метод checkValue($value, $rule), используемый для проверки значения $value на соответствие правилу $rule. Так как правила состоят из выражений, соединённые запятыми, то метод разбивает правило на выражения, используя в качестве разделителя символ запятой , и затем для каждого выражения подбирает класс-выражение.<?
/**
* Класс текстового поля
*/
class Field
{
/**
* Проверяет корректность значения по установленному правилу
*
* @param string $value - значение текстового поля
* @param string $rule - правило проверки поля
* @return - вернёт 'true', если значение корректное и соотвествует правилу, в противном случае вернёт 'false'
*/
public static function checkValue($value, $rule = '')
{
//Подключение классов интерпретатора
require_once(__DIR__.'/interpreter.classes.php');
if ($rule) {
//Создаём контекст и помещаем в него значение
$context = new Context();
$context->setVariable('Значение', $value);
//Убираем из правила ненужные пробелы и приводим строку к нижнему регистру
$rule = mb_strtolower(trim($rule), 'utf-8');
/*
Разбиваем правило на части и для каждой части подбираем соответствующий ей объект выражения.
Затем этот объект добавляем в массив выражений для последующего составления конечного (общего) выражения
*/
$arExpressions = array();
$arStrExpressions = explode(',', $rule);
foreach ($arStrExpressions as $strExpression) {
//Убираем в подправиле повторяющиеся пробелы
$strExpression = trim(preg_replace('/\s+/', ' ', $strExpression));
//Выражение "Это число"
if (mb_strpos($strExpression, 'это число', 0, 'utf-8') !== false) {
$arExpressions[] = new IsNumberExpression(
new VariableExpression('Значение')
);
}
//Выражение "Обязательное"
else if (mb_strpos($strExpression, 'обязательное', 0, 'utf-8') !== false) {
$arExpressions[] = new NotEmptyExpression(
new VariableExpression('Значение')
);
}
//Выражение для проверки значения на меньше или равно <=
else if (mb_strpos($strExpression, '<=', 0, 'utf-8') !== false) {
$partsExpression = explode(' ', $strExpression);
$arExpressions[] = new CompareSmallerOrEqualExpression(
new VariableExpression('Значение'),
new NumberExpression($partsExpression[2])
);
}
//Выражение для проверки значения на меньше <
else if (mb_strpos($strExpression, '<', 0, 'utf-8') !== false) {
$partsExpression = explode(' ', $strExpression);
$arExpressions[] = new CompareSmallerExpression(
new VariableExpression('Значение'),
new NumberExpression($partsExpression[2])
);
}
//Выражение для проверки значения на больше или равно >=
else if (mb_strpos($strExpression, '>=', 0, 'utf-8') !== false) {
$partsExpression = explode(' ', $strExpression);
$arExpressions[] = new CompareBiggerOrEqualExpression(
new VariableExpression('Значение'),
new NumberExpression($partsExpression[2])
);
}
//Выражение для проверки значения на больше >
else if (mb_strpos($strExpression, '>', 0, 'utf-8') !== false) {
$partsExpression = explode(' ', $strExpression);
$arExpressions[] = new CompareBiggerExpression(
new VariableExpression('Значение'),
new NumberExpression($partsExpression[2])
);
}
}
//Итоговое выражение
$finalExpression = new MultiplicationExpression($arExpressions);
return (bool)$finalExpression->interpret($context);
} else {
return true;
}
}
}
?>
interpreter.classes.php
Содержит класс контекста (окружение и буфер) и классы выражений:- Context — контекст (окружение и буфер, хранящий промежуточные вычисления, значения переменных и констант). Используется при интерпретации выражений (вычисления значений выражений);
- Expression — абстрактный класс выражения. В классе объявлен абстрактный метод interpret($context), выполняющий интерпретацию (вычисление) выражения и возвращает подсчитанное значение. В дочерних классах этот метод определён, так как у каждого выражения своя логика вычисления результата выражения;
- StringExpression — выражение-строка. Метод interpret($context) возвращает исходную строку;
- NumberExpression — выражение-целочисленное число. Метод interpret($context) возвращает исходное число;
- VariableExpression — выражение-переменная. Метод interpret($context) возвращает значение переменной;
- IsNumberExpression — выражение «Это число», проверяет значение выражения на число. Метод interpret($context) вернёт 1 (единицу), если значение выражения является числом, в противном случае вернёт 0 (ноль);
- NotEmptyExpression — выражение «Не пусто», проверяет значение на непустоту. Метод interpret($context) вернёт 1 (единицу), если значение выражения непустое, в противном случае вернёт 0 (ноль);
- CompareBiggerExpression — выражение-сравнение «Больше». Cравнивает левое и правое выражение с помощью знака «Больше» >. Метод interpret($context) вернёт 1 (единицу), если значение левого выражения больше правого, в противном случае вернёт 0 (ноль);
- CompareBiggerOrEqualExpression — выражение-сравнение «Больше или равно». Cравнивает левое и правое выражение с помощью знака «Больше или равно» >=. Метод interpret($context) вернёт 1 (единицу), если значение левого выражения больше или равно правому, в противном случае вернёт 0 (ноль);
- CompareSmallerExpression — выражение-сравнение «Меньше». Cравнивает левое и правое выражение с помощью знака «Меньше» <. Метод interpret($context) вернёт 1 (единицу), если значение левого выражения меньше правого, в противном случае вернёт 0 (ноль);
- CompareSmallerOrEqualExpression — выражение-сравнение «Меньше или равно». Cравнивает левое и правое выражение с помощью знака «Меньше или равно» <=. Метод interpret($context) вернёт 1 (единицу), если значение левого выражения меньше или равно правому, в противном случае вернёт 0 (ноль);
- MultiplicationExpression — выражение-умножение. Метод interpret($context) обходит выражения, получает их значения и перемножает между собой, а в конце возвращает результат всех перемножений.
<?
/**
* Класс текстового поля
*/
class Field
{
/**
* Проверяет корректность значения по установленному правилу
*
* @param string $value - значение текстового поля
* @param string $rule - правило проверки поля
* @return - вернёт 'true', если значение корректное и соотвествует правилу, в противном случае вернёт 'false'
*/
public static function checkValue($value, $rule = '')
{
//Подключение классов интерпретатора
require_once(__DIR__.'/interpreter.classes.php');
if ($rule) {
//Создаём контекст и помещаем в него значение
$context = new Context();
$context->setVariable('Значение', $value);
//Убираем из правила ненужные пробелы и приводим строку к нижнему регистру
$rule = mb_strtolower(trim($rule), 'utf-8');
/*
Разбиваем правило на части и для каждой части подбираем соответствующий ей объект выражения.
Затем этот объект добавляем в массив выражений для последующего составления конечного (общего) выражения
*/
$arExpressions = array();
$arStrExpressions = explode(',', $rule);
foreach ($arStrExpressions as $strExpression) {
//Убираем в подправиле повторяющиеся пробелы
$strExpression = trim(preg_replace('/\s+/', ' ', $strExpression));
//Выражение "Это число"
if (mb_strpos($strExpression, 'это число', 0, 'utf-8') !== false) {
$arExpressions[] = new IsNumberExpression(
new VariableExpression('Значение')
);
}
//Выражение "Обязательное"
else if (mb_strpos($strExpression, 'обязательное', 0, 'utf-8') !== false) {
$arExpressions[] = new NotEmptyExpression(
new VariableExpression('Значение')
);
}
//Выражение для проверки значения на меньше или равно <=
else if (mb_strpos($strExpression, '<=', 0, 'utf-8') !== false) {
$partsExpression = explode(' ', $strExpression);
$arExpressions[] = new CompareSmallerOrEqualExpression(
new VariableExpression('Значение'),
new NumberExpression($partsExpression[2])
);
}
//Выражение для проверки значения на меньше <
else if (mb_strpos($strExpression, '<', 0, 'utf-8') !== false) {
$partsExpression = explode(' ', $strExpression);
$arExpressions[] = new CompareSmallerExpression(
new VariableExpression('Значение'),
new NumberExpression($partsExpression[2])
);
}
//Выражение для проверки значения на больше или равно >=
else if (mb_strpos($strExpression, '>=', 0, 'utf-8') !== false) {
$partsExpression = explode(' ', $strExpression);
$arExpressions[] = new CompareBiggerOrEqualExpression(
new VariableExpression('Значение'),
new NumberExpression($partsExpression[2])
);
}
//Выражение для проверки значения на больше >
else if (mb_strpos($strExpression, '>', 0, 'utf-8') !== false) {
$partsExpression = explode(' ', $strExpression);
$arExpressions[] = new CompareBiggerExpression(
new VariableExpression('Значение'),
new NumberExpression($partsExpression[2])
);
}
}
//Итоговое выражение
$finalExpression = new MultiplicationExpression($arExpressions);
return (bool)$finalExpression->interpret($context);
} else {
return true;
}
}
}
?>
В нашем правиле, записанном в переменной $rule, к терминальным выражениям относятся фразы:
- Число — соответствует классу IsNumberExpression;
- обязательное — соответствует классу NotEmptyExpression;
А к нетерминальным выражениям относятся фразы:
- значение > 10
Данное выражения при интерпретации будет разбито на три терминальных выражения:- значение — соответствует классу выражения-переменной VariableExpression;
- > — соответствует классу CompareBiggerExpression;
- 10 — соответствует классу NumberExpression.
- значение <= 50
Данное выражения при интерпретации будет разбито на три терминальных выражения:- значение — соответствует классу выражения-переменной VariableExpression;
- <= — соответствует классу CompareSmallerOrEqualExpression;
- 50 — соответствует классу NumberExpression.
Результат работы скрипта test.php
Значение «5» некорректное
Значение «15» корректное
Значение «» некорректное
Значение «Привет» некорректное
Значение «50» корректное