Вроде как PHP встраиваемый

Смотрите, какую штуку нашел. Виртуальная машина и компилятор ПХП для встройки в свой Си код. Все в одном файле, как мы любим. 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 тысяч строк на файл :expressionless:

Нашел три критичные баги. Одна совсем позорная. Надо будет внимательно ВСЕ 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 у вас займет один вечер. Или меньше.

Заработало. С mmap(), как и хотелось.

На скриншоте - ESPShell, в крмандной строке которого запускаем .php файлик, расположенный на TARFS.

Результат работы вываливается на экран.

HTML теги уже были в .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.

Много буковок

Возможности 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 и ne

PH7 предоставляет операторы 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-битная целочисленная арифметика

Все целочисленное - 64 бита. На всякий случай, чтобы не переполнялось. Ну и чтобы уж очень быстро не работало.

Нативная поддержка UTF-8

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.
Код можно сохранить и исполнить потом.

470 c хвостиком встроенных функций

PH7 поставляется с более чем 470 встроенными функциями. В состав движка входят, в частности:

  • XML-парсер с поддержкой пространств имён;
  • обработка INI-файлов;
  • чтение и запись CSV;
  • Arduino API - можно вызывать всякие Serial.begin() и прочие digitalRead(). Работаем над этим.
  • кодирование и декодирование UTF-8;
  • распаковка ZIP-архивов;
  • кодирование и декодирование JSON;
  • генерация случайных чисел и строк;
  • встроенный файловый ввод-вывод

Для работы этих возможностей не требуется подключать внешние библиотеки.

Встроенный HTTP-парсер

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, созданная для работы наустройствах с жесткими ограничениями по памяти и ресурсам, которая собирается без использования стандартного скрипта конфигурации.

В принципе для встраиваемых систем нужно:

  • assembler,
  • интерпретатор,
  • dynamic runtime loader.

Я предложил бы 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 проглотит не поморщившись ))