Парсеры в Coresky - energy-coresky/air GitHub Wiki

В папке w2 (второе крыло) имеется 6 файлов, в каждом декларируется по одному классу: PHP, XML, YML, ZML, CSS и JS. Все эти парсеры используются для подсветки синтаксиса в инструментах разработчика и классе Show, а также для других целей Coresky. Этот код, также, потенциально, довольно сильно востребован и для кода приложений.

Класс PHP

Этот код расширяет возможности модуля токенизации Tokenizer. Класс PHP, также можно использовать для стандартного переформатирования файлов:

$minified = PHP::file(DIR_S . '/sky.php', 0);
# output minified PHP code
echo $minified;
# beautifier: output structured code with tabs in 4 space
echo new PHP($minified);

Во время инициализации объекта класса PHP, в конструктор нужно передать PHP код и указать количество пробелов для одного стандартного отступа. Ноль для создания minified файлов. 4 - стандартное значение по умолчанию. В конструкторе делается токенизация, с помощью token_get_all(..), а массив токенов помещается в публичное свойство PHP::$tok, хотя перебор токенов удобнее делать с помощью метода PHP::tok(..). Если синтаксис PHP неверный, токенайзер может выбросить исключение, которое перехватывается с помощью блока try-catch, а ошибку можно "увидеть" в PHP::$warning:

$php = PHP::file('some.php');
if (PHP::$warning)
    return PHP::$warning;
$out = '';
for ($y = $php->tok(); $y; $y = $new) {
    $new = $php->tok($y->i + 1);
    $out .= $y->str;
}

Если PHP в консоле и веб имеют разные версии, PHP::$warning, также может содержать предупреждение о несовпадении версий (кеш при этом перекомпилируется и возможно нормальное продолжение работы). Если token_get_all(..) выбросил исключение, делается повторный запуск, без флага TOKEN_PARSE и также возможно нормальное продолжение работы, но возможны неточности для парсера "rank".

По сути, класс PHP содержит два парсера: первый используется для бьютифаера - PHP::nice() и подобных задач. Второй - PHP::rank(), для отчета глобальных определений в инструментах разработчика или других "Reflection" подобных задач. В продукте "Earth", имеется раздел - песочница, для рассмотрения деталей работы парсеров PHP и других.

Парсер "nice"

Парсер собирает информацию, необходимую для решения задачи beautifier. Во время второго прохода по всем токенам, в методе PHP::__toString(), эта информация используется для минификации или стандартного переформатирования кода PHP. "Nice", в основном, манипулирует только содержимым пробельных символов, т.е. изменяет токены T_WHITESPACE. Но также заменяет синтаксис с ключевыми словами array, list на синтаксис с квадратными скобками. Перед закрывающей квадратной скобкой может быть добавлена или удалена запятая. Целочисленные ключи, в начале массива, начиная с 0 удаляются, но только так, чтобы не изменилось содержимое массива:

# input:
list ($a, $b, $c, ) = array (0 => 1, 1 => 2, 9 => 3, 2 => 22,);
# output:
[$a, $b, $c] = [1, 2, 9 => 3, 2 => 22];

Перед использованием бьютифаера, иногда может потребоваться изменить значение PHP::$autocomma, которое, по умолчанию равно true. Если текущая используемая версия PHP более или равно 8.0, и при этом форматируется код, который будет использоваться на более ранней версии, например 7.4, это может привести к фатальной ошибке. Чтобы этого не произошло, следует установить PHP::$autocomma = false;. Начиная с версии PHP 8.0, разрешается указывать необязательную запятую, перед закрывающей круглой скобкой. Когда этот параметр равен true, бьютифаер будет работать как и в случае квадратных скобок:

function test(
    \VeryLong\Parameters\Type $firstParameter,
    \VeryLong\Parameters\Type $secondParameter,
    \VeryLong\Parameters\Type $thirdParameter, # last comma may be added automatically
) { ... }

Генератор "rank"

Ориентирован для быстрой работы в один проход. Этот код выполнен в виде генератора, частично повторяет функционал модуля PHP Reflection, но без загрузки кода в исполняемое пространство PHP. Парсер организован так, что может быть расширен до получения полного Reflection функционала. Для токенов, возвращаемых функцией token_get_all(..), вычисляется дополнительное свойство rank, которое содержит информацию о ранге. Для токенов T_STRING (и некоторых других), вычисляется факт декларирования или использования кода функции, класса, метода, трейта, константы и т.д. Пример использования - задекларированные классы в файле w2/mvc.php:

$php = PHP::file(DIR_S . '/w2/mvc.php');
echo "declared classes: ";
foreach ($php->rank() as $y)
    'CLASS' !== $y->rank or print $y->str . ' ';
# output:
# declared classes: MVC_BASE Guard Model_m Model_t Controller MVC

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

Если нужно получить параметры функций, классов, можно воспользоваться методом PHP::get_close(..). С помощью этого метода, можно получить индекс закрывающей скобки в общем массиве токенов. Собрать строку, можно с помощью метода PHP::str(..)

$php = PHP::file(DIR_S . '/w2/php.php');
foreach ($php->rank() as $y) {
    if ($y->open) # next not ignored token is open parenthesis
        echo $y->str,
        $php->str($y->open, /*get close parenthesis index*/$php->get_close($y)),
        PHP_EOL;
}
# output:
# file($name, $pad = 4)
# PHP(file_get_contents($name), $pad)
. . .

$y возвращается оператором yield и содержит информацию по текущему токену:

$y->i # текущий индекс в массиве PHP::$tok
$y->tok # int токен, например T_CLASS, 0 для одно символьных токенов
$y->str # строковое представление токена
$y->line # номер строки в файле для текущего токена
$y->rank # ранг токена-имени, когда $y->tok == T_STRING, false для других токенов
$y->is_def # true для деклараций имен, false для использований
$y->next # следующий токен без учета игнорируемых, string|int
$y->open # если $y->next === '(', то здесь индекс ->i этой скобки
$y->curly # 1 для {, -1 для }, 0 для остальных (без учёта скобок в интерполируемых строках)
$y->new # следующий токен, stdClass

$prev # содержит предыдущий токен без учета игнорируемых, int

Публичные свойства и методы класса PHP:

$php->tok # массив, возвращаемый token_get_all(..)
$php->count # кол-во элементов $php->tok
$php->parse_error # true, если token_get_all(.. , TOKEN_PARSE) выбросил исключение
$php->ns # текущее пространство имен, пустая строка для глобального
$php->top # импортируемые с помощью use, классы, функции, константы в текущий $php->ns
$php->curly # агрегированный счетчик фигурных скобок (без учёта скобок в интерполируемых строках)
$php->in_str # текущая позиция токена в интерполируемой строке или нет (true/false)
$php->in_par # текущая позиция токена в заголовке определения функции или метода
$php->pos # позиция в файле: в классе, функции, методе и т.д.

$php->rank() # генератор "rank"
$php->nice() # парсер "nice"
$php->get_modifiers($y, $i = 4) # получить массив модификаторов
$php->ignore(int $index) # проверить, игнорируемый ли токен
$php->str(int $from, int $to) # собрать строку "от" и "до" индекса
$php->get_close(stdClass $y) # найти индекс закрывающей скобки, если открывающая в $y->open
$php->get_real(stdClass $y) # получить fully-qualified-name для класса, функции, константы
PHP::file(string $name, $pad = 4): PHP # загрузить файл в конструктор

Всем именам переопределяется T_STRING, например, вместо T_NAME_RELATIVE. В $y->rank декларации указываются строками: CLASS, INTERFACE, TRAIT, ENUM, FUNCTION, METHOD, CONST, а использование кода числами integer, в соответствии с константами токенайзера:

T_CLASS # использование класса
T_FN # использование метода класса
T_VAR # использование свойства класса
T_FUNCTION # использование функции
T_CONST # использование константы
T_LIST # self, parent, static или базовый тип: int float bool string и т.д.
T_GOTO # имена меток goto или именованные аргументы функций (>= PHP 8.2)

$php->pos - окончание области детектируется по символу }, возможные значения:

PHP::_ANOF # анонимн. функция в глобальной области
PHP::_FUNC # функция имеющая имя
PHP::_CLASS # класс/трейт/интерфейс/перечисление
PHP::_METH # метод класса/трейта/интерфейса, всегда (PHP::_METH | PHP::_CLASS)
0 # не входит ни в одну выше перечисленную позицию

$php->in_par - только для заголовков деклараций. Окончание области детектируется по символу { или => для стрелочных функций, возможные значения:

PHP::_ANOF # анонимн. функция
PHP::ARRF # стрелочная функция
PHP::_FUNC # функция имеющая имя
PHP::_METH # метод класса/трейта/интерфейса
0 # не входит ни в одну выше перечисленную позицию

Генератор открыт для усовершенствований. Например, декларация свойств классов не указывается в $y->rank, но их можно найти так:

$pos = -1;
foreach ($php->rank() as $y) {
    if ('CLASS' == $y->rank) {
        echo "class `$y->str` has properties:\n";
        $pos = $php->pos;
    }
    if (T_VARIABLE == $y->tok && $php->pos == $pos)
        echo "  " . implode(' ', $php->get_modifiers($y, 2)) . " $y->str\n";
    if ($php->pos & PHP::_CLASS)
        continue;
    $pos = -1;
}

Класс XML

С помощью класса Coresky XML, возможно манипулирование XML/HTML данными, наподобие как в браузерах, с помощью Javascript DOMParser.parseFromString(..). Во время парсинга, может теряться информация о пробельных символах и кавычках, только в части перечислений атрибутов тегов, что для сути XML не имеет значения. Но tokenizer класса XML не теряет никакую информацию и используется, в том числе, для подсветки синтаксиса PHP/XML/HTML данных в классе Show. XML строка преобразовывается в связанную иерархическую структуру объектов типа stdClass, где каждый объект - один узел (node). Текстовые узлы, состоящие только из пробельных символов сохраняются. Такая архитектура позволяет делать любые манипуляции с XML, в том числе переформатирование, как это умеет делать tidy:

$xml = new XML('<div id=test>hello</div>');
$xml->pad = '  '; # base indent for beautifier
$id = $xml->byId('test'); # like .getElementById(..)
$id->inner('<b>word!</b>'); # like .innerHTML
echo $xml;
# output:
<div id="test">
  <b>word!</b>
</div>
# Example2:
$xml = new XML(file_get_contents('big.html'));
$q = $xml->query('div.clsname li a:last');
$q->inner(html('<b>word!</b>')); # like .innerTEXT
echo $xml;

Класс не сигнализирует об ошибках XML, подобно тому как браузеры пытаются визуализировать любой HTML, в том числе тот, который содержит ошибки и неточности. Класс XML имеет важные свойства:

  • ->in - строка, представляющая XML. Источник данных для ->parse(..) и приемник для ->__toString()

  • ->root - корневой узел, связанной иерархической структуры. Здесь, источник-приемник наоборот

  • ->selected - массив, выбранных узлов селекторами

Методы класса:

  • конструктор - инициализирует класс и подготавливает переменные

  • ->tokens(..) - генератор выдает токены, разбивая текстовый XML на входе

  • ->parse(..) - парсер конвертирует токены в связанные объекты PHP

  • ->__toString() - обратное действие: объекты PHP преобразовывается в строку XML

  • ->clone(..) - клонирует выбранный узел (с дочерней иерархией), с целью вставки в новое место

  • ->walk(..) - метод для стандартного прохода (сверху вниз, слева направо) по иерархической структуре, используется для разных целей

  • ->byId(..) - селектор подобно .getElementById( ) JS

  • ->byTag(..) - селектор подобно .getElementsByTagName( ) JS

  • ->byClass(..) - селектор подобно .getElementsByClassName( ) JS

  • ->query(..) - селектор подобно .querySelector( ) и .querySelectorAll( ) JS

  • ->inner(..) - заменяет внутреннюю часть, подобно .innerHTML. Для текстовой версии, необходимо использовать функцию Coresky html(..)

  • ->outer(..) - заменяет внешнюю часть, как .outerHTML

  • ->remove(..) - удаляет узлы вместе с дочерней иерархией

Coresky SAW

Для упрощения сложного есть только одна стратегия - "разделяй и властвуй". Но не всегда бывает просто и её применить. Если имеется сложный алгоритм, который плохо делится на методы - используйте трейт SAW ("Пила").

Про синтаксис Coresky Yaml, читайте в статье "Конфигурации в Coresky". Кроме стандартного использования, Yaml возможно использовать совместно с пилой Coresky, смотрите код трейта в файле w2/saw.php. "Пила" используется в самом Coresky, в парсере "nice" (бьютифаере).

Пилу удобно использовать в следующем случае: представьте, что нужно написать PHP код, состоящий из длинной сложной последовательности условий if\else. Упростить написание такого кода поможет пила, которая работает в три этапа:

  • сначала, из возможных вариаций, генерируется Yaml, с помощью препроцессора. Этот же препроцессор используется в компиляторе представлений Jet, синтаксис один и тот-же;
  • полученный Yaml компилируется, результат помещается в кэш при необходимости. После второго этапа, на выходе можно получить PHP код в Clоsure или такой же код, который сохраняется в кэш-файле функцией yml(..);
  • выполняется код, возможно из кэш. Замыкание дублируется со связанным объектом и областью видимости вызываемого класса (выполняется Closure::bind(..));

Когда удобно применить пилу:

  • сложный алгоритм плохо делится на методы из-за обилия переменных или по другим причинам. Разделение можно сделать с помощью маркеров Yaml;
  • нужен высокопроизводительный код, который изменяется с интервалом, скажем, 1 час. Можно использовать препроцессор Saw, чтобы сделать код изоморфным;
  • код имеет много иерархических данных, которые удобно поместить в Yaml;

Имеется три режима работы SAW:

  • ifelse - для генерации кода Closure на базе условий if, else
  • switch - для генерации кода Closure на базе оператора switch
  • yaml - для генерации кода массива PHP подобно функции yml(..)

Чтобы использовать SAW, нужно включить трейт в класс: use SAW; и вызвать метод трейта: $this->_saw(..). В первом параметре передается массив настроек для ветра, а в последующих, параметры вызываемого Closure. Возможные ключи массива настроек:

'file' => имя Yaml файла - строка или массив (с указанием ware)
'marker' => маркер, если источник - часть файла
'yaml' => inline Yaml, в этом случае, ключ `file` не нужен
'cache' => имя кэш, если используется кэширование
'static' => true, если нужно дополнительно кэшировать в статической переменной
'proc' => массив переменных для условий препроцессора, если нет - без препроцессора
'vars' => массив переменных для "echo", т.е. переменной части исходного Yaml

Для генерации Closure, исходный Yaml файл, после действий препроцессора, должен содержать на верхнем уровне иерархии три ключа: head, body, tail. Каждая секция может содержать секцию code, которая вставляется в начало компилированного PHP кода без преобразований. Секция head обязательно должна содержать подсекцию param, для указания входных параметров Closure. А секция body обязательно должна содержать подсекцию rules, для указания списка условий и действий из которых будет сгенерирован код для switch или условий if\else.

Для условий препроцессора, можно использовать, как и в Jet условия начинающиеся с символа решетка: #if(..), #elseif(..), #else. Все условия, должны заканчиваться словом #end.

Если производится коррекция Yaml с помощью массива переменных для "echo", то нужно помнить о специальных последовательностях символов. Например, если нужно определить в JSON нотации хэш в хэше, вставьте между символами { хотя бы пробел: { {. Потому что с помощью такой последовательности {{$var_or_expression}} производится подстановка переменного выражения в компилированный Yaml.

ZML - универсальный описатель

В программировании иногда требуется упаковать большой объем данных в файл, в своем собственном формате в виде цепочки блоков данных. Примеры: веб-приложение как один файл, патчи к таким приложениям, blockchain-данные, алгоритмические описания и т.п. Для многих таких случаев, удобно использовать класс ZML. В данный момент, ZML формат в Coresky используется для кодирования SKY-приложений для standalone утилиты moon.php, которые упаковываются в файлы с расширением *.sky. Файлы с таким расширением, содержат необходимую иерархию директорий, все необходимые PHP-файлы и файлы других типов, а также содержимое базы данных MySQL, и некоторый другой функционал, необходимый для развертывания приложений в новом месте.

В будущем, в Coresky, возможное использование ZML:

  • патчи SKY-приложений для moon.php
  • общее описание произвольных алгоритмов с возможностью импорта и экспорта из/в разные языки программирования.

Файлы ZML имеют следующую общую базовую структуру: заголовок - набор данных в формате bang, пустая строка и последовательность блоков данных. Каждый блок данных имеет командную строку плюс произвольное количество строк с данными блока. Например, список директорий и один файл, упакованный для *.sky файла:

DIRS: 83
main main/mvc main/mvc/view main/w3 public public/img public/m public/m/etc main/w2
FILE: .env 12
APP_DEV true

Здесь первая командная строка с директивой DIRS, отделяется, в конце, двоеточием. После пробела число 83 - количество байт в последующем блоке данных. Вторая директива FILE, определяет имя файла и содержимое файла.

Тип ZML файла указывается, в первой строке заголовка (ключ type) и от этого значения, зависит вся последующая трактовка упакованных данных. Однако все ZML-файлы имеют общую базовую структуру, а класс ZML содержит ряд методов упрощающих работу с произвольными ZML-форматами данных. Внешние продукты (wares) могут работать как расширения для класса ZML.

Примеры ZML ключа type:

type app
type patch
type algo

Первый - app поддерживается в Open SkyProject и утилитой moon.php

Все ZML-файлы должны завершаться директивой END. Файлы ZML могут быть большими, и выполнение END гарантирует, что достигнут конец файла.

Парсер CSS

Функционал парсера находится в w2/css.php. Токенайзер этого класса используется для подсветки синтаксиса в Show:css(..).

Парсер JS

Функционал парсера находится в w2/js.php. Токенайзер этого класса используется для подсветки синтаксиса в Show:js(..).

В данный момент в разработке. Поэкспериментировать со всеми парсерами Coresky, можно в продукте Earth.

Coresky Diff

Типичное использование для вывода в консоль:

$new = file_get_contents('new');
$old = file_get_contents('old');
[$out, $add, $sub, $z] = Show::diffx($new, $old);
echo $out;

Код Coresky Diff удивительно мал (всего 37 строк), но не плох. Вместо алгоритма LCS, используется вероятностная составляющая. Иногда (редко), Coresky Diff не находит самую длинную общую последовательность при сравнении двух текстовых файлов, но это не значит, что с его помощью будет построен неверный патч. Даже при сложном сравнении, Coresky Diff работает, в основном симметрично. Алгоритм Diff Horde Project (http://www.horde.org/) в сложных случаях работает точнее, так как включает идеи академического LCS. Пока что, Coresky Diff, остается таким как есть, так как хотелось бы поэкспериментировать именно с этим алгоритмом.

Парсер Markdown

Функционал парсера находится в w2/md.php. В данный момент в разработке.

⚠️ **GitHub.com Fallback** ⚠️