Смотрите, какую штуку нашел. Виртуальная машина и компилятор ПХП для встройки в свой Си код. Все в одном файле, как мы любим. 60 тысяч строк - как мы не любим, но что уж поделать.
А вот список встроенных функций (довольно внушительный):
Смотрите, какую штуку нашел. Виртуальная машина и компилятор ПХП для встройки в свой Си код. Все в одном файле, как мы любим. 60 тысяч строк - как мы не любим, но что уж поделать.
А вот список встроенных функций (довольно внушительный):
Из коробки для ESP32 не компилируется, но если чуть-чуть поправить, то компилируется.
Вот патчик, чтобы компилировалось.
diff -urN OLD/ph7.c NEW/ph7.c
--- OLD/ph7.c 2026-06-11 15:29:42.793868800 +0700
+++ NEW/ph7.c 2026-06-11 15:24:04.916947600 +0700
@@ -79,6 +79,7 @@
/* This file was automatically generated. Do not edit (except for compile time directive)! */
#ifndef _PH7_H_
#define _PH7_H_
+
/*
* Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language.
* Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/
@@ -126,7 +127,11 @@
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* $SymiscID: ph7.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable <chm@symisc.net> $ */
+
+#define ESP32
+
#include <stdarg.h> /* needed for the definition of va_list */
+
/*
* Compile time engine version, signature, identification in the symisc source tree
* and copyright notice.
@@ -257,13 +262,14 @@
* By default we will assume that we are compiling on a UNIX systems.
* Otherwise the OS_OTHER directive must be defined.
*/
-#if !defined(OS_OTHER)
-#if !defined(__UNIXES__)
-#define __UNIXES__
-#endif /* __UNIXES__ */
-#else
-#endif /* OS_OTHER */
+# if !defined(OS_OTHER)
+# if !defined(__UNIXES__)
+# define __UNIXES__
+# endif /* __UNIXES__ */
+# else
+# endif /* OS_OTHER */
#endif /* __WINNT__/__UNIXES__ */
+
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */
typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */
@@ -11564,7 +11570,7 @@
}
/*
* void var_dump(expression,....)
- * var_dump РїС—Р… Dumps information about a variable
+ * var_dump пїЅ Dumps information about a variable
* Parameters
* One or more expression to dump.
* Returns
@@ -13603,9 +13609,9 @@
* and the current working directory before failing. The include()
* construct will emit a warning if it cannot find a file; this is different
* behavior from require(), which will emit a fatal error.
- * If a path is defined РїС—Р… whether absolute (starting with a drive letter
+ * If a path is defined пїЅ whether absolute (starting with a drive letter
* or \ on Windows, or / on Unix/Linux systems) or relative to the current
- * directory (starting with . or ..) РїС—Р… the include_path will be ignored altogether.
+ * directory (starting with . or ..) пїЅ the include_path will be ignored altogether.
* For example, if a filename begins with ../, the parser will look in the parent
* directory to find the requested file.
* When a file is included, the code it contains inherits the variable scope
@@ -19984,8 +19990,39 @@
#ifdef __WINNT__
#include <Windows.h>
#elif defined(__UNIXES__)
-#include <sys/utsname.h>
+# if (__has_include(<sys/utsname.h>))
+# include <sys/utsname.h>
+# else
+struct utsname {
+
+ char *sysname;
+ char *nodename;
+ char *release;
+ char *version;
+ char *machine;
+
+};
+
+static int uname(struct utsname *out) {
+
+ if (out == NULL)
+ return -1;
+
+ out->sysname = "FreeRTOS";
+ out->nodename = "local";
+ out->release = "6";
+ out->version = "7";
+#ifdef __XTENSA__
+ out->machine = "Xtensa-LX6/LX7";
+#else
+ out->machine = "RISCV";
#endif
+
+ return 0;
+}
+# endif
+#endif
+
/*
* string php_uname([ string $mode = "a" ])
* Returns information about the host operating system.
@@ -24589,7 +24626,7 @@
#include <unistd.h>
#include <sys/uio.h>
#include <sys/stat.h>
-#include <sys/mman.h>
+//#include <sys/mman.h>
#include <sys/file.h>
#include <pwd.h>
#include <grp.h>
@@ -24799,7 +24836,11 @@
{
struct stat st;
int rc;
+#ifdef ESP32
+ rc = stat(zPath,&st);
+#else
rc = lstat(zPath,&st);
+#endif
if( rc != 0 ){
return -1;
}
@@ -24980,6 +25021,8 @@
return rc == 0 ? PH7_OK : -1;
}
/* int (*xMmap)(const char *,void **,ph7_int64 *) */
+#ifdef ESP32
+#else
static int UnixVfs_Mmap(const char *zPath,void **ppMap,ph7_int64 *pSize)
{
struct stat st;
@@ -25011,6 +25054,8 @@
{
munmap(pView,(size_t)nSize);
}
+#endif
+
/* void (*xTempDir)(ph7_context *) */
static void UnixVfs_TempDir(ph7_context *pCtx)
{
@@ -25135,8 +25180,13 @@
UnixVfs_Getenv, /* int (*xGetenv)(const char *,ph7_context *) */
UnixVfs_Setenv, /* int (*xSetenv)(const char *,const char *) */
UnixVfs_Touch, /* int (*xTouch)(const char *,ph7_int64,ph7_int64) */
+#ifdef ESP32
+ 0,
+ 0,
+#else
UnixVfs_Mmap, /* int (*xMmap)(const char *,void **,ph7_int64 *) */
UnixVfs_Unmap, /* void (*xUnmap)(void *,ph7_int64); */
+#endif
UnixVfs_link, /* int (*xLink)(const char *,const char *,int) */
UnixVfs_Umask, /* int (*xUmask)(int) */
UnixVfs_TempDir, /* void (*xTempDir)(ph7_context *) */
@@ -42548,7 +42598,9 @@
#ifdef __WINNT__
#include <Windows.h>
#elif defined(__UNIXES__)
-#include <sys/utsname.h>
+# if (__has_include(<sys/utsname.h>))
+# include <sys/utsname.h>
+# endif
#endif
/*
* PHP_OS
Вот порт под ЕСПшки. Альфа-преальфа.
Программу , вот , исполнил, важную.
На пхп, на ЕСП32.
Вот такую:
const char *prog = "\r\n"
"<?php\r\n"
" echo 'Welcome guest'.PHP_EOL;\r\n"
" echo 'Current system time is: '.date('Y-m-d H:i:s').PHP_EOL;\r\n"
" echo 'and you are running '.php_uname();\r\n"
"?>\r\n";
Временно разломал поддержку ESP32. Дело в том, что я отлаживаюсь на Windows/Cygwin и мне пока удобнее так. Потом починю взад.
Исходный код был одним файлом на 2МиБ , я его разобрал на кучку файлов. Теперь стало хоть возможно вести разработку. Но и после разбивки - там по 3-5 тысяч строк на файл ![]()
Нашел три критичные баги. Одна совсем позорная. Надо будет внимательно ВСЕ sizeof() в коде посмотреть - косяки встречаются с вылазом за край массива.
Собирается в Linux/Windows(Cygwin) командой make. Пока, по крайней мере. Компилируется долго. На выходе - бинарник-исполнятор-компилятор PHP кода (inter или inter.exe, смотря в кого веруете). Можно поиграться со скриптами:
Добавил каталожек scripts/ , там около семидесяти PHP файлов-тестов, которые можно скормить этому самому исполнятору, поглазеть, чего на экран выводится. Там и жосткий ОО с абстракными классами и JSON и прочее:
Вырезал весь legacy код относящийся к Windows.
Переписал кое-какую специфику (типа несуществующих\нефункциональных на ESP32 функций а-ля chdir() или getcwd())
Добавил пару настроек в виртуальную машинку - задание рабочего каталога и каталога для временных файлов (/tmp).
Думаю, еще около двух месяцев займет.
Ну и все. Останется веб сервер написать. Я знаю, что есть C++ WebAsyncServer или как его там. Но у нас будет свой, со своими дырками.
Что получится в результате: Компактный и быстрый HTTPS+PHP сервер с быстрой отдачей статики. Use-case: ну, например, веб-морда.
Если вы никогда не писали на PHP, но писали на Си или Си++, то изучение PHP у вас займет один вечер. Или меньше.
Пример использования в своем скетче:
/* Пример использования PHP движка PH7 в своем коде в Ардуино ИДЕ, на ESP32
* ВАЖНО: tar -cf filesystem.tar scripts; потом прошейте TAR архив в свою ESPшку по
* смещению 0x310000 (см. partitions.csv). Установите в настройках IDE (Partition scheme->Custom)
*
*/
#include <Arduino.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "tarfs.h" // Arduino Library Manager: "TARFS"
#include "ph7.h" // Arduino Library Manager: "PH7-ESP32" (not yet :)
/*
* Когда виртуальная машина выводит что-либо (вызывая, echo или printf ), весь ее вывод попадает в эту функцию.
* Все, что делает эта функция - перенаправляет весь вход на экран (на stdout)
*/
static int output_callback(const void *pOutput,unsigned int nOutputLen,void *pUserData /* Unused */) {
return write(1,pOutput,nOutputLen) < 0 ? PH7_ABORT : PH7_OK;
}
/* Запустить PHP скрипт с таким-то именем. Скрипты должны быть расположены на смонтированной файловой
* системе TARFS, т.к. другие файловые системы не поддерживают вызов mmap(), которые необходим для работы PH7
*
* Показан полный цикл. В реальном коде создавать\уничтожать Engine каждый раз нет необходимости.
* На одной Engine может висеть несколько виртуальных машин, в функции-примере ниже используется лишь
* одна машина
*/
int engine_test(const char *path) {
ph7 *pEngine; /* PH7 engine */
ph7_vm *pVm; /* Compiled PHP program */
int rc;
rc = ph7_init(&pEngine);
if( rc != PH7_OK ){
Serial.printf("Error while allocating a new PH7 engine instance\r\n");
return -1;
}
// Ошибки -> на экран
ph7_config(pEngine,PH7_CONFIG_ERR_OUTPUT, output_callback, 0);
// Можно компилировать файлики. Файлик может быть любого размера (лишь бы влазил во флешку)
// при компиляции файл в память _не_ загружается.
//
rc = ph7_compile_file(pEngine, path , &pVm, 0);
if( rc == PH7_OK ) {
// Настроим нашу VM:
// Сообщения -> на экран
// Если PHP скрипту нужна tmp директория, то вот она:
ph7_vm_config(pVm, PH7_VM_CONFIG_TEMPDIR,"/ffat/tmp" );
ph7_vm_config(pVm, PH7_VM_CONFIG_OUTPUT, output_callback, 0 );
// Ну, вроде все. Запускаем скомпилированный код, без параметров.
// Параметры тоже можно передать, но в примере лень писать.
ph7_vm_exec(pVm,0);
// Уничтожаем виртуальную машину, Уничтожаем сам движок
ph7_vm_release(pVm);
ph7_release(pEngine);
return 0;
}
return -1;
}
void setup() {
Serial.begin(115200);
// монтируем файловую систему из раздела "tarfs" флешки. Там должен быть прошит TAR архив с примерами
// (создать архив: cd examples; tar -cf tarfs.tar scripts)
//
tarfs_init();
tarfs_mount("tarfs",0,0,0);
}
void loop() {
delay(1000);
engine_test("/scripts/hello_world.php");
}
Перевод выжимки из документации, об особенностях PH7.
Помимо поддержки стандартных возможностей PHP 5.3, PH7 предоставляет ряд расширений и особенностей.
Какие-то расширения языка появились в поздних версиях PHP, какие-то не появились вовсе.
Да, PH7 и официальный PHP имеют различия - это сделано намеренно. Ниже - список отличий\расширений PH7 от Zend PHP Engine
PH7 поддерживает перегрузку (overloading) функций и методов. Можно определить несколько функций или методов с
одним именем, различающихся набором принимаемых аргументов. У аргументов можно указывать тип, и перегрузка будет
осуществляться не только по количеству аргументов но и по их типу:
function printz(int $value);
function printz(string $value);
function printz(int $value, int $value2);
PH7 поддерживает указание типов аргументов в сигнатурах функций и методов (type hinting). Обычный PHP позволяет указывать тип аргумента функции лишь в том случае, когда аргумент - объект.
Для простых скалярных типов указывать тип не нужно. PH7 позволяет как указывать так и не указывать. Если тип указан, то при вызове функции будет сделано принудительное приведение типа к типу аргумента функции:
function fn(int $value) {
}
fn("225"); // при вызове строка "255" будет приведена к типу `int`
В язык добавлена поддержка выражений с оператором запятой (comma expressions), позволяющим последовательно
вычислять несколько выражений в одном выражении и писать прочий компактный и нечитаемый код.
eq и nePH7 предоставляет операторы eq и ne для строгого сравнения значений, в частности - строк.
Семантика - как у strcmp(), никаких больше “строки 125TEST и 125 равны”
Приоритеты операторов в PH7 реализованы последовательно и улучшены по сравнению с некоторыми, скажем так, особенностями
стандартного PHP. Так, в частности, наш любимый тернарный оператор работает как в Си\Си++, а оператор квадратные скобки [] теперь можно применять прямо к вызову функции, которая возвращает массив:
$x = func_returning_array($arg)[1]; // Второй элемент массива, который вернула функция
func_returning_array($arg)[1](); // Второй элемент массива, который вернула функция - является функцией и вот так ее можно вызвать.
PH7 содержит полноценную объектно-ориентированную подсистему с поддержкой классов, объектов, методов,
свойств и других механизмов объектной модели. И да, перегрузка методов тут тоже доступна, как и перегрузка конструкторов
Аргументы функций могут иметь в качестве значений по умолчанию произвольные сложные выражения.
Например:
function fn() {
static $value = rand();
return $value++;
}
Переменная $value будет проинициализирована 1 раз. Значения по умолчанию так же могут быть сложными: аргументом по умолчанию может быть результат вызова функции
function test($name = 'PH7 '.rand_str(4),$age = 10 * 2 + 5) {
echo "Name = $name\n";
echo "Age = $age\n";
}
/* Вызываем без аргументов, будут подставлены значения по умолчанию */
test();
/* .. с аргументом */
test('Me');
[]Благодаря этой возможности для извлечения элемента массива больше не нужна временная переменная. Элемент можно получить непосредственно из результата explode():
echo explode(' ', 'Alan Mathison Turing')[1]; // Mathison
То же самое работает с результатом функции:
function test() {
return array('Machine' => 'Turing');
}
echo test()['Machine']; // Turing
Можно также непосредственно вызвать анонимную функцию, хранящуюся в элементе возвращённого массива:
function test() {
return array('Anon' => function() { echo "Hello World\n"; });
}
test()['Anon'](); // Hello World
Анонимные функции можно хранить в массиве, извлекать их в переменную или вызывать непосредственно через разыменование массива:
$a = array(
'add' => function($x, $y) { return $x + $y; },
'mul' => function($x, $y) { return $x * $y; },
'div' => function($x, $y) { return $x / $y; }
);
$add = $a['add'];
echo $add(100, 200); // 300
echo $a['add'](50, 20); // 70
echo $a['mul'](10, 20); // 200
echo $a['div'](50, 2); // 25
В данном примере массив содержит три анонимные функции, выполняющие сложение, умножение и деление.
Функцию можно вызвать непосредственно из массива либо предварительно присвоить её переменной.
Все целочисленное - 64 бита. На всякий случай, чтобы не переполнялось. Ну и чтобы уж очень быстро не работало.
PH7 имеет встроенную поддержку UTF-8, подключения внешних библиотек не нужно.
Статические переменные и константы могут инициализироваться произвольными сложными выражениями, включая вызовы функций.
PH7 не завершает выполнение программы из-за фатальных ошибок в обычном понимании. Например, вызов неопределённой функции не приводит к немедленному прекращению выполнения программы.
Варнинг появится - да. Можно перехватить эту ситуацию и обработать - да. Грохнется ли скрипт? Нет. В таких случаях PH7 просто считает, что случился NULL. вызвали несуществующую функцию? вернется NULL.
$x = non_existent_function(); // $x == NULL, если функция не определена
PH7 передаёт массивы в функции и возвращает их по ссылке. При присваивании существующего массива другой переменной обе переменные указывают на один экземпляр массива. То же происходит при передаче массива в функцию.
(Если нужно передать именно копию или скопировать массив , то копию массива можно получить с помощью стандартной функции array_copy($src).)
Переменная содержит не сам массив, а идентификатор его экземпляра. При передаче, возврате или присваивании копируется этот идентификатор, поэтому разные переменные обращаются к одному массиву. В терминах C/C++ это аналогично двум указателям на одну структуру, выделенную через malloc().
Пример:
$a = array('sample', 50, 'value');
$b = $a;
unset($b[1]);
var_dump($a);
После $b = $a обе переменные указывают на один массив. Поэтому удаление элемента через $b сразу отражается при обращении к $a.
Если вам ближе Си\Си++, то переменные $a и $b в примере выше - это указатели.
Стандартные управляющие конструкции могут содержать одну инструкцию без использования фигурных скобок.
Например:
function fn()
echo 'Hello from fn() :)';
Анонимные функции можно вызывать непосредственно после их создания, не сохраняя их предварительно в переменной.
Аргументы анонимных функций могут иметь значения по умолчанию, в том числе заданные сложными выражениями.
Анонимные функции также могут содержать статические переменные.
Анонимные функции могут быть элементами массива.
Такие языковые конструкции, как echo, print, include и другие, могут использоваться в контексте вызова функции.
Например:
$my_echo = 'echo';
$my_echo("Hello World\n"); // Прокатит в PH7, а в PHP - Нет. echo - не функция, а 'language construct'
Правила вычисления логических выражений в PH7 реализованы последовательно и предсказуемо, как в Си
Встроенные функции сортировки, включая sort(), usort(), ksort() и многие другие, используют алгоритм сортировки слиянием (merge sort).
PH7 предоставляет встроенный механизм обработки исключений.
Управление памятью осуществляется с помощью сборщика мусора, основанного на подсчёте ссылок
PH7 может работать в потокобезопасном режиме, включаемом директивой времени компиляции. Реализация полностью реентерабельна.
PH7 содержит компилятор, преобразующий исходный код в байткод для виртуальной машины PH7.
Код можно сохранить и исполнить потом.
PH7 поставляется с более чем 470 встроенными функциями. В состав движка входят, в частности:
Для работы этих возможностей не требуется подключать внешние библиотеки.
PH7 содержит встроенный парсер HTTP-запросов.
Функция implode() может принимать переменное количество аргументов, причём аргументы не обязательно должны быть массивами.
explode() не возвращает пустые элементы, содержащие только последовательность пробельных символов.
В PH7 добавлены функции implode_recursive() и join_recursive() для рекурсивного объединения элементов.
$_HEADERВ PH7 добавлен суперглобальный массив $_HEADER, содержащий MIME-заголовки HTTP-запроса.
А почему не perl?
Перл - не нужен.
А из этого мы сделаем МикроПитон^H^H^H^H МикроПХП. Я сейчас биндинги пишу. Чтобы можно было вот так делать:
require ("Arduino.php");
require ("HardwareSerial.php");
function setup() {
Serial.begin(115200);
}
function loop {
Serial.println("Hello from PHP code :)");
}
Все в одном будет - и шелл, и интерпретатор и виртуальная машина. Прям на ЕСП32 можно будет программки писать. Редактор - на веб страничке. Код пхпшный можно запускать как из браузера так и из консоли. Классы , типа HardwareSerial или, там, I2C написаны на самом PHP.:
Некий продвинутый аналог микропитона.
Среда для обучения программированию микроконтроллеров - пиши, запускай, прошивать ничего не нужно.
Самая большая проблема при обработке строк - регулярные выражения. Они в perl встроены. 90% операций - сравнение строк. PHP в топку - он для дебилов.
Ну я притащил уже регулярки. Не PCRE2 , конечно, но все нужное есть. Включая ссылки на подстроки.
Я перл просто не знаю. Я незнаю, насколько он применим для написания скетчей. Вроде бы он больше для парсинга выражений, чем дженерик язык. Хотя повторюсь - я его не знаю, может и подходит.
PHP был выбран из-за синтаксиса. Он практически такой же, как и в Си\Си++, используемый ардуинщиками. Если ребенок научится программировать на МикроПХП, то он без проблем перейдет на Си\Си++ позже. И наоборот - программисты на Си\Си++ поймут ПХП и без изучения. По крайней мере код прочитать - смогут.
На перле, как и на ПХП, можно писать все. И проблемы у них общие - так как по сути это интерпретатор, то приложение должно содержать все разом:
поэтому требует очень жирное железо даже для самых простейших скриптов, типа блинка
Название языка официально расшифровывается как Practical Extraction and Report Language («практический язык для извлечения данных и составления отчётов»)[9], а в шутку — как Pathologically Eclectic Rubbish Lister («патологически эклектичный перечислитель мусора»)[9];
разработчики языка не считают его название акронимом[10].
Perl имеет все фишки, которые есть в python.
Microperl — это предельно миниатюрная версия интерпретатора языка программирования Perl, созданная для работы наустройствах с жесткими ограничениями по памяти и ресурсам, которая собирается без использования стандартного скрипта конфигурации.
В принципе для встраиваемых систем нужно:
Я предложил бы forth, но он однозначный. Но при этом forth имеет реализации с assembler инструкциями с ограничением по обратным ссылкам.
Вот наш первый Блинк, вышел вполне себе ничего, не комом.
<?php
require('HardwareSerial.php');
// Светодиодик на ножке номер 2
const LED = 2;
function setup() {
pinMode(LED, INPUT | OUTPUT);
$Serial->begin(115200);
// Ждем, пока прочихается
while(!$Serial) { // object to boolean - расширение языка PHP
delay(250);
}
}
function loop() {
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
}
?>
А это что за конструкция?
Установить режим ввод-вывод. Можно было бы только OUTPUT обойтись, вы правы. Осталось, потому, что я смотрел что происходит с константами, как они ресолвятся и что попадает в функцию pinMode().
Или вы еще о чем-то? Я стараюсь сделать так, чтобы было максимально похоже на Ардуино API.
Я о том, что как раз в Ардуино ИДЕ либо то, либо другое. А у вас сразу оба ))
Ааа… Ну вот у нас - сразу оба, да. На ЕСП32 так можно.
Не работал с ЕСП, поэтому не знал. Спасибо за науку!
Собсна, INPUT | OUTPUT будет всегда равно OUTPUT, так что и Ардуино IDE проглотит не поморщившись ))