Meridian Sheets — спецификация приложения
Документ приложения по разделу 8 библии (
docs/00-foundation.md). Версия 0.1 · 2026-10-03. Собственный формат:.msx(шаблон.mst, плоский XML.msxf), пространство имёнurn:meridian:office:sheets:1, MIMEapplication/vnd.meridian.sheets. Цвет приложения--app-sheets#1E8E5A, значок — сетка 2×2. Крейты ядра:kernel/crates/meridian-grid(модель книги, хранение ячеек),meridian-calc(лексер, парсер, байткод, граф зависимостей, пересчёт, каталог функций),meridian-pivot(сводные),meridian-chart(общий Meridian Chart),meridian-numfmt(числовые форматы),meridian-solver(подбор параметра, Solver),meridian-codecs-{ooxml,odf,msbinary,csv}; оболочка —shell/src/sheets/(QML/C++); скрипты —meridian-script. Фаза по библии: 2 (6–16 мес от старта); совместная работа — фаза 6; потоки данных и богатые типы — P1/P2.
Содержание
- Назначение и аналоги
- Функции (по группам, P0/P1/P2)
- Экраны
- Модель данных (Rust)
- Алгоритмы · 5.23 Каталог функций
- MeridianScript API для Sheets
- Форматы
- Горячие клавиши
- Критерии готовности 1.0
- Риски
- Вопросы к библии
1. Назначение и аналоги
1.1 Назначение
Meridian Sheets — электронные таблицы пакета: ввод и правка табличных
данных, формулы (500+ функций, динамические массивы, LAMBDA),
форматирование и условное форматирование, структурированные таблицы,
сортировка/фильтры/проверка данных, сводные таблицы и диаграммы
(Meridian Chart), импорт внешних данных, анализ «что если» (подбор
параметра, Solver, сценарии, таблицы данных), печать, скрипты
MeridianScript вместо VBA. Единая модель сетки (grid-model)
и движок формул (calc-engine) из Meridian Kernel
используются также таблицами в Write, Slides, Notes и ответами
Forms.
Главные сценарии: финансовые модели и бюджеты; реестры и списки (клиенты, склад, задачи); отчёты из выгрузок (CSV/БД → сводная → диаграмма); обработка больших выгрузок (1–10 млн строк); учебные расчёты; формы Forms → лист ответов.
1.2 Аналоги
| Продукт | Сильные стороны | Слабые стороны | Что берём |
|---|---|---|---|
| Microsoft Excel 365 | эталон функций и форматов, динамические массивы, сводные, Power Query, скорость | VBA, непрозрачный пересчёт, «_xlfn.» в старых версиях, подписка, нет Linux | терминологию, грамматику формул, горячие клавиши, коды форматов, объектную модель для скриптов |
| LibreOffice Calc | открытый, ODF, Solver с несколькими движками, Linux | медленный на >200k строк, нет spill-массивов, устаревший интерфейс | Solver (симплекс+эволюционный), approxEqual в сравнениях, импорт ODS |
| Google Sheets | совместная работа, простые скрипты (Apps Script), QUERY, IMPORT* | облако, лимит 10 млн ячеек, слабая печать, нет офлайна | модель скриптов onEdit, функции FILTER/SORT/UNIQUE «по
умолчанию», обсуждения в ячейках |
| Apple Numbers | свободный холст, красивые диаграммы | слабая совместимость, мало функций | визуальная подача диаграмм, категории в таблицах |
| Gnumeric | точная статистика | узкая аудитория | тестовый корпус статистических функций |
| WPS / OnlyOffice | близость к Excel, кроссплатформенность | закрытые/ограниченные скрипты | ориентир по совместимости xlsx |
1.3 Чем будем лучше Excel и Calc
| Область | Excel | Calc | Meridian Sheets |
|---|---|---|---|
| Скорость на больших данных | 1 048 576 строк; сортировка/фильтр 1M строк — секунды; пересчёт многопоточный | заметно замедляется после ~200k строк, однопоточный ввод/вывод | колоночные чанки 256×256 с SIMD-агрегатами, многопоточный пересчёт независимых компонент графа (rayon), ленивый импорт CSV; цели: открыть 100 МБ xlsx < 5 с, пересчёт 1M формул < 1 с, сортировка 1M строк < 1 с, «большой режим» 2^31 строк (§4.14) |
| Динамические массивы | spill есть, но в файлах _xlfn./_xlpm., в
старых версиях @-мусор |
только CSE-массивы (Ctrl+Shift+Enter), spill нет | spill — базовая модель (§5.6); пользователь никогда не видит
_xlfn; при экспорте пишем метаданные
cm/xda, чтобы Excel 365 читал как
динамические, а Excel 2016 — как CSE |
| Автоматизация | VBA (небезопасно, Windows/Mac), Office Scripts только в облаке | Basic/Python, слабый API | MeridianScript (TypeScript, QuickJS-ng в песочнице), запись действий в читаемый скрипт, UDF с типами, расписания, кнопки в ленте; транслятор простых VBA-макросов (§6.5) |
| Прозрачный пересчёт | стрелки влияющих/зависимых, «Вычислить формулу» по шагам | то же, слабее | панель «Зависимости» с графом и причиной пересчёта, профайлер формул
(время по ячейкам), объяснение ошибки цепочкой (#REF! ←
удалена строка 12 в 14:03), режим «почему это значение» (§3.4
SH-16) |
| Типы данных | число + формат; даты как сериалы; богатые типы только в 365 | число + формат | значение хранит тип (дата, время, длительность, валюта с кодом ISO, процент, дробь) поверх f64 — формат и тип не теряются при копировании/формулах; ошибки несут причину; богатые типы (P2) открытые |
| Ошибки циклов и spill | сообщение + 0 | сообщение Err:522 |
список циклов в строке состояния, подсветка цикла, итерации по
компоненте; #SPILL! показывает, что мешает, и предлагает
сдвинуть |
| Формулы | без подсветки скобок в старых версиях, нет форматирования | нет | редактор формул: подсветка диапазонов, автодополнение с подписями на русском, форматирование длинных формул (многострочный режим), LET/LAMBDA-именованные формулы с документацией |
| Платформы | Windows, macOS (урезанная), веб | Win/mac/Linux | один код, одинаковое поведение на трёх ОС (Qt 6 + Rust) |
1.4 Границы версии 1.0
Не входит в 1.0: Power Pivot/DAX и модели данных с миллиардами строк; полноценный аналог Power Query (потоки данных — ограниченный набор шагов, P1/P2); совместное редактирование (фаза 6); богатые типы «География/Акции» (P2, нужен поставщик данных); выполнение VBA (только трансляция простых макросов); сводные по OLAP-кубам (P2).
2. Функции
Приоритеты: P0 — без этого нельзя выпустить 1.0; P1 — в 1.x; P2 — позже.
2.1 Ввод и правка
| Функция | Детали | Фаза |
|---|---|---|
| Ввод в ячейку и в строке формул | Enter/Tab/Shift+Enter/Shift+Tab; F2 — правка на месте; Esc — отмена; автозавершение текста по столбцу (как Excel AutoComplete) | P0 |
| Маркер заполнения | перетаскивание угла выделения; Ctrl меняет «копировать ↔︎ серия»; двойной клик — заполнение до конца соседнего столбца; параметры автозаполнения после перетаскивания (копировать ячейки / заполнить / только форматы / без форматов / дни / рабочие дни / месяцы / годы) | P0 |
| Автозаполнение серий | числа (линейный тренд), даты (день/месяц/год шаг), текст+число, дни недели и месяцы (локаль), пользовательские списки, формулы (§5.11) | P0 |
| Заполнение по образцу (Flash Fill) | Ctrl+E: вывод преобразования по 1–3 примерам
(разбиение/склейка/регистр/извлечение чисел и дат) — синтез программ по
DSL (§5.11.3) |
P1 |
| Прогрессия (диалог) | арифметическая/геометрическая/даты/автозаполнение, шаг, предел, тренд | P0 |
| Многострочный текст | Alt+Enter; автоперенос | P0 |
| Специальные вставки | §2.13 и §5.19 | P0 |
| Вставка/удаление строк, столбцов, ячеек | со сдвигом; на выделении нескольких диапазонов; с сохранением форматов (как у соседей сверху/слева) | P0 |
| Перемещение перетаскиванием | рамка выделения; Ctrl — копия; Shift — вставка со сдвигом | P0 |
| Отмена/повтор | неограниченная глубина в пределах сеанса (журнал операций), объединение серий ввода; история в панели (P1) | P0 |
| Автозамена и автоисправление | общие с Lingo; специфика: автоматические гиперссылки, «1/2» → дата (с отключением), ведущие нули (подсказка «сохранить как текст») | P0 |
| Проверка орфографии в ячейках | Lingo; F7 — диалог, подчёркивание только в режиме правки | P1 |
| Ввод данных через форму | диалог «Форма данных» по структурированной таблице | P1 |
| Голосовой/рукописный ввод | нет (не цель) | — |
2.2 Форматирование ячеек
| Функция | Детали | Фаза |
|---|---|---|
| Числовые форматы | Общий, Числовой, Денежный, Финансовый, Дата, Время, Процентный,
Дробный, Экспоненциальный, Текстовый, Дополнительный (индексы, телефоны
по локали), Все форматы с кодами Excel
(#,##0.00;[Red]-#,##0.00, [$-419],
[h]:mm, условия, цвета, *, _) —
§5.10 |
P0 |
| Длительности и единицы | формат длительности [h]:mm:ss,
d "дн." h "ч"; единицы измерения как суффикс формата |
P0/P1 |
| Выравнивание | по горизонтали (общее/лево/центр/право/заполнение/по ширине/по центру выделения/распределённое), по вертикали, отступ, ориентация текста (−90…90°, вертикально), перенос по словам, автоподбор ширины (уменьшать текст), объединение ячеек (объединить и центрировать / по строкам / объединить / отменить) | P0 |
| Шрифт | гарнитура, размер, начертание, подчёркивание (одинарное/двойное/учётное), зачёркивание, верхний/нижний индекс, цвет; форматирование части текста ячейки (rich text runs) | P0 |
| Границы | 13 стилей линий, цвет, пресеты (все/внешние/толстая внешняя), диагонали, рисование границ | P0 |
| Заливка | цвет, узор (18 узоров), градиент (линейный/радиальный) | P0 (узоры), P1 (градиент) |
| Защита (атрибуты ячейки) | защищаемая ячейка, скрыть формулы — действуют при защите листа | P0 |
| Условное форматирование | правила: значение ячейки (между/равно/…), формула, текст содержит, дата (вчера/на этой неделе/…), пустые/ошибки, повторяющиеся/уникальные, первые/последние N (и %), выше/ниже среднего (± σ); гистограммы (сплошные/градиентные, отрицательная ось, min/max авто/число/процент/процентиль/формула), цветовые шкалы (2 и 3 цвета), наборы значков (20 наборов Excel + свои SVG, пороги, обратный порядок, только значок); приоритет правил, «остановить, если истина», область применения, диспетчер правил | P0 (все типы правил), P1 (свои наборы значков) |
| Темы и цвета темы | палитра темы документа (как Office: 12 цветов + оттенки), шрифты темы; совместимость с theme1.xml | P0 |
| Формат по образцу | кисть; двойной клик — многократно | P0 |
| Автоформат / стили таблиц | §2.3 | P0 |
| Очистка форматов / всего / содержимого / примечаний / гиперссылок | команда «Очистить» с подпунктами | P0 |
2.3 Стили ячеек и структурированные таблицы
| Функция | Детали | Фаза |
|---|---|---|
| Стили ячеек | именованные (Обычный, Заголовок 1–4, Итог, Ввод, Вычисление, Контрольная, Пояснение, Акцент 1–6 с оттенками, Денежный, Процентный, Плохой/Хороший/Нейтральный); наследование от «Обычный»; создание/изменение/объединение из другой книги | P0 |
Структурированные таблицы (Ctrl+T) |
имя таблицы, строка заголовков с автофильтром, чередование строк/столбцов, строка итогов (функции SUBTOTAL 101–111 по выбору), первая/последняя колонка выделены, автоматическое расширение при вводе под/справа, вычисляемые столбцы (формула применяется ко всему столбцу), изменение размера маркером, преобразование в диапазон, 60 стилей таблиц Excel + свои стили | P0 |
| Структурированные ссылки | Таблица1[Столбец], [@Столбец],
Таблица1[#Заголовки], [#Итоги],
[#Данные], [#Все], [@], диапазоны
столбцов [[Кол1]:[Кол3]], экранирование спецсимволов
'['; автодополнение при вводе [ |
P0 |
| Срезы для таблиц | срезы (slicers) по столбцам таблицы, стили, многостолбцовый вид | P1 |
| Таблицы данных «что если» | §2.5 | P1 |
2.4 Формулы
| Функция | Детали | Фаза |
|---|---|---|
| Грамматика Excel | операторы
+ - * / ^ & = <> < > <= >= : , (пробел)
и #, @; приоритеты и ассоциативность как в
Excel (унарный минус выше ^: -2^2 = 4;
% постфикс); ссылки A1 и R1C1; 3D-ссылки
Лист1:Лист3!A1; внешние ссылки
[Книга.xlsx]Лист!A1 (чтение, обновление по запросу);
структурированные ссылки; локализованные разделители (; в
русской локали, , в английской; десятичный разделитель по
локали) — §5.2 |
P0 |
| Имена функций на языке интерфейса | ввод и отображение СУММ/SUM по локали
интерфейса, хранение — всегда каноническое английское; переключатель
«показывать английские имена»; таблица локализации в
meridian-calc/locale/<lang>.toml (все 500+ имён для
ru, de, fr, es, it, pt, pl, tr, nl, sv, da, fi, no, cs, hu, ja, zh,
ko) |
P0 (ru, en), P1 (остальные) |
| Динамические массивы и spill | любая формула, вернувшая массив, разливается; оператор
A1#; @ — неявное пересечение;
#SPILL! с диагностикой; CSE-формулы при импорте сохраняются
как «унаследованные массивные» {=...} |
P0 |
| LET / LAMBDA | LET с до 126 пар; LAMBDA с рекурсией через
имя; хелперы MAP, REDUCE, SCAN, BYROW, BYCOL, MAKEARRAY, ISOMITTED;
именованные LAMBDA в Диспетчере имён с описанием и подписью параметров
(автодополнение показывает параметры) |
P0 |
| Именованные диапазоны и формулы | область книги/листа, имена-константы, имена-формулы
(=OFFSET(...)), Диспетчер имён, создание из выделения (по
заголовкам), «Применить имена», вставка имени (F3), автодополнение |
P0 |
| Пространства имён функций | расширения регистрируют функции с префиксом: MS.
(встроенные Meridian-расширения, напр. MS.REGEX),
SCRIPT. (UDF из скриптов книги),
ADDIN.<id>. (плагины); Excel-совместимые алиасы без
префикса, если конфликтов нет; при экспорте в xlsx UDF превращаются в
_xludf. и кэшированные значения |
P0 |
| Автодополнение и подсказки | список функций при вводе (локализованный + английский), подпись аргументов, подсказка текущего аргумента, Ctrl+Shift+A — вставка имён аргументов, Ctrl+A — Мастер функций | P0 |
| Подсветка ссылок | цветные рамки диапазонов во время правки; подсветка парных скобок; перетаскивание рамки меняет ссылку | P0 |
| Многострочный редактор формул | расширяемая строка формул, автоформатирование (отступы по
вложенности) по Ctrl+Shift+F, комментарии в формуле через
N("...") сохраняются; разворачивается в панель |
P0 |
| Вычисление по шагам | диалог «Вычислить формулу» (F9 в строке формул для фрагмента) | P0 |
| Влияющие/зависимые ячейки | стрелки на листе, переход по двойному клику на стрелке; панель «Зависимости» с графом | P0 |
| Режимы пересчёта | автоматически / автоматически кроме таблиц данных / вручную; F9, Shift+F9 (лист), Ctrl+Alt+F9 (всё), Ctrl+Shift+Alt+F9 (перестроить граф и всё) | P0 |
| Итеративные вычисления | включение, макс. итераций (100), относительная погрешность (0.001) — по книге | P0 |
| Точность как на экране | опция книги: значения округляются до отображаемого формата (необратимо, с предупреждением) | P1 |
| Система дат 1904 | флаг книги; автоматически при импорте старых Mac-xls | P0 |
| Внешние ссылки | связи с другими книгами: диалог «Изменить связи», обновление при открытии (по политике), кэш значений | P1 |
| Проверка ошибок (фоновая) | зелёный треугольник: формула не согласована с соседними, число как текст, ссылка на пустые, незащищённая формула, формула пропускает смежные ячейки, 2-значный год; правила включаются по отдельности | P0 (базовые 5), P1 (все 9) |
| Аудит книги | «Проверка совместимости», «Инспектор документа» (скрытые листы/имена/личные данные), поиск формул с внешними ссылками | P1 |
2.5 Данные
| Функция | Детали | Фаза |
|---|---|---|
| Сортировка | до 64 уровней, по значению/цвету ячейки/цвету шрифта/значку УФ, по возрастанию/убыванию/пользовательскому списку, с учётом регистра, по строкам/столбцам, обнаружение заголовка, сортировка внутри выделения с предупреждением о соседних данных; стабильная, ICU-локаль (§5.12) | P0 |
| Автофильтр | кнопки в заголовках: список значений с поиском (деревом по датам),
текстовые/числовые/датовые фильтры (равно, содержит, между, выше
среднего, первые 10, вчера/на этой неделе/квартал…), по цвету
ячейки/шрифта/значку, повторное применение (Ctrl+Alt+L),
очистка; на структурированной таблице и на диапазоне |
P0 |
| Расширенный фильтр | диапазон условий (И по строке, ИЛИ между строками, вычисляемые
критерии =C2>AVERAGE(C:C)), фильтр на месте или
копирование в другое место, только уникальные |
P0 |
| Удаление дубликатов | по выбранным столбцам, учёт заголовков, отчёт о числе удалённых; «выделить дубликаты» без удаления | P0 |
| Проверка данных | типы: любое, целое, десятичное, список (из диапазона/перечня/формулы, с раскрывающимся списком и поиском в нём), дата, время, длина текста, другой (формула), регулярное выражение (расширение, сохраняется в xlsx как «другой» с кэшем); игнорировать пустые; подсказка при вводе; сообщение об ошибке (остановка/предупреждение/сведения); обвести неверные данные; очистить обводку; найти ячейки с проверкой | P0 (regex — P1) |
| Текст по столбцам | мастер: с разделителями / фиксированной ширины, разделители, объединять последовательные, ограничитель строк, формат столбцов (общий/текст/дата порядок/пропустить), предпросмотр | P0 |
| Мгновенное заполнение | §2.1 | P1 |
| Консолидация | функция (сумма, количество, среднее…), диапазоны из нескольких листов/книг, по позиции или по подписям (верхняя строка/левый столбец), связи с исходными данными (структура) | P1 |
| Подбор параметра | целевая ячейка, значение, изменяемая ячейка; метод секущих с бисекцией (§5.15) | P0 |
| Поиск решения (Solver) | целевая (макс/мин/значение), изменяемые ячейки, ограничения
(<=, =, >=, int, bin, dif),
методы: Симплекс LP (с ветвями и границами для
целочисленных), ОПГ-нелинейный (GRG),
Эволюционный (дифференциальная эволюция); параметры
(точность, итерации, время, мультистарт), отчёты (результаты,
устойчивость, пределы) — §5.16 |
P1 (симплекс и GRG), P2 (эволюционный, отчёты устойчивости) |
| Таблицы данных «что если» | с одной и двумя переменными ({=TABLE(row;col)}), режим
«автоматически кроме таблиц» |
P1 |
| Диспетчер сценариев | сценарии как наборы значений изменяемых ячеек, отчёт «структура» и «сводная таблица» | P1 |
| Прогноз (лист прогноза) | экспоненциальное сглаживание ETS (AAA), сезонность авто/ручная, доверительный интервал, диаграмма + таблица (функции FORECAST.ETS*) | P1 |
| Группировка и структура | группировать строки/столбцы (до 8 уровней), автоструктура по формулам, промежуточные итоги (диалог: при каждом изменении в…, функция, итоги под данными, замена текущих) | P0 |
| Проверка на дубликаты, пропуски, выбросы (Assist) | панель Assist: «очистить данные» — предложения; требует редакции Pro | P2 |
| Связи с данными (запросы) | список подключений книги, обновить все, свойства (обновлять при открытии, каждые N минут) | P1 |
2.6 Сводные таблицы и сводные диаграммы
| Функция | Детали | Фаза |
|---|---|---|
| Создание сводной | из диапазона/таблицы, из внешнего подключения (Base/CSV/Parquet), из другой сводной (общий кэш); на новый/существующий лист; рекомендуемые сводные (эвристики по типам полей) | P0 (P1 — рекомендуемые) |
| Поля | области Фильтры/Столбцы/Строки/Значения; перетаскивание в панели; несколько полей значений; «Σ Значения» как поле | P0 |
| Агрегаты | сумма, количество, среднее, максимум, минимум, произведение, количество чисел, стандартное отклонение (выборка/генеральная), дисперсия (выборка/генеральная), число уникальных (distinct count) | P0 |
| «Дополнительные вычисления» | % от общего итога / итога столбца / строки / родительской строки / родительского столбца / родительского итога; отличие от; приведённое отличие; с нарастающим итогом (и в %); ранг по возрастанию/убыванию; индекс | P0 |
| Группировка | даты (секунды…годы, автоматически при добавлении поля даты: Годы/Кварталы/Месяцы), числовые интервалы, ручная группировка выделенных элементов; разгруппировать | P0 |
| Сортировка и фильтры в сводной | по подписям, по значениям (с выбором поля значений), первые 10, фильтр по подписи/значению/дате, ручной список значений | P0 |
| Макет и стили | компактная/структура/табличная форма, повторять подписи, промежуточные итоги вверху/внизу/нет, общие итоги, пустые строки между группами, стили сводных (85 стилей Excel + свои), чередование, заголовки полей | P0 |
| Вычисляемые поля и элементы | формулы над полями (= Выручка - Себестоимость) и над
элементами (= Q1 + Q2), порядок решения, список формул |
P0 (поля), P1 (элементы) |
| Срезы и временные шкалы | срезы по полям (многовыбор, стили, подключение к нескольким сводным через общий кэш), временная шкала по датам (годы/кварталы/месяцы/дни) | P0 (срезы), P1 (шкала) |
| Детализация | двойной клик по значению — лист с исходными записями (drill-through); показать детали по элементу | P0 |
| Обновление | по команде, при открытии, с сохранением ширин/форматов; изменение источника; отложенное обновление макета | P0 |
| GETPIVOTDATA | генерация при клике по сводной в формуле (отключаемо) | P0 |
| Сводные диаграммы | диаграмма, связанная с кэшем; кнопки полей на диаграмме; типы: гистограмма, график, круговая, линейчатая, с областями, точечная (ограничено) | P0 |
| Модель данных / связи между таблицами | несколько таблиц, связи, меры (DAX-подобное подмножество) | P2 |
| Сводные по OLAP/кубу | функции CUBE*, подключение к SSAS/XMLA | P2 |
2.7 Диаграммы
Движок — Meridian Chart (общий для всех приложений; спецификация в
docs/algorithms/chart-engine.md); здесь — что доступно из
Sheets.
| Функция | Детали | Фаза |
|---|---|---|
| Типы | гистограмма (обычная/с накоплением/нормированная, объёмные варианты), линейчатая, график (с маркерами, сглаженный), с областями, круговая, кольцевая, точечная, пузырьковая, биржевая (4 вида), поверхностная, лепестковая, комбинированная; современные: каскадная (waterfall), воронка, дерево (treemap), солнечные лучи (sunburst), гистограмма распределения (histogram) и Парето, «ящик с усами» (box & whisker), карта (choropleth, P2) | P0 (классические + комбинированная), P1 (современные), P2 (карта, объёмные) |
| Рекомендуемые диаграммы | эвристика по форме данных (как Excel) | P1 |
| Элементы | заголовок, подписи осей, легенда, подписи данных (из ячеек), таблица данных, линии тренда (линейная, экспоненциальная, логарифмическая, полиномиальная до 6, степенная, скользящее среднее; уравнение и R²), планки погрешностей, линии сетки, оси (вторичная ось, логарифмическая, обратный порядок, формат чисел), стрелки/аннотации | P0 |
| Данные | ряд = формула =SERIES(имя;категории;значения;порядок);
диапазоны с подсветкой на листе; пустые ячейки (пропуск/ноль/линия),
скрытые строки; переключение строка/столбец; изменение выделением на
листе |
P0 |
| Оформление | 16 стилей, цветовые схемы темы, быстрые макеты, форматы
заливки/линии/тени/прозрачности; шаблоны диаграмм
(.mcht) |
P0 |
| Спарклайны | линия, гистограмма, выигрыш/проигрыш; группы, ось, маркеры (макс/мин/первая/последняя/отрицательные), стили; в ячейках, совместимо с xlsx | P0 |
| Интерактивность | наведение показывает значение, клик по точке — выделение ячейки; масштабирование | P1 |
| Экспорт | PNG/SVG/PDF диаграммы, копирование как изображение/объект в Write и Slides (один движок) | P0 |
2.8 Импорт данных и потоки данных
| Функция | Детали | Фаза |
|---|---|---|
| Мастер импорта текста (CSV/TSV/TXT) | автоопределение кодировки, разделителя, кавычек, десятичного разделителя, типов столбцов; предпросмотр; «сохранить ведущие нули»; потоковый импорт (ГБ-файлы) с прогрессом; импорт в лист или в поток данных | P0 |
| JSON | из файла/URL; выбор узла-массива, разворачивание вложенных объектов
в столбцы (a.b.c), массивы в строки |
P1 |
| XML | по XPath/схеме: таблица из повторяющихся элементов; карты XML (xlsx
xmlMaps) — только чтение |
P1 |
| Parquet / Arrow / Feather | через arrow-rs, с типами; большие файлы → «большой
режим» |
P1 |
| Базы данных | через коннекторы Meridian Base (SQLite, PostgreSQL, MySQL, MS SQL, ODBC): таблица/запрос SQL → лист/сводная; требует редакции с Base | P1 |
| Веб-таблицы | из URL: список таблиц HTML, предпросмотр, выбор | P1 |
| Из буфера обмена | HTML-таблицы, TSV, с сохранением форматов | P0 |
| Из Forms | лист ответов, живое обновление | P1 (фаза 5) |
| «Потоки данных» (Data Flows) | повторяемые шаги: источник → фильтр строк → выбор/переименование/типы столбцов → разделить/объединить столбцы → замена значений → удалить дубликаты → группировка → слияние (join) двух потоков → добавление (union) → свод/развёртка (pivot/unpivot) → вычисляемый столбец (формула Sheets) → назначение (лист/таблица/сводная); шаги записываются в JSON и в MeridianScript, повторяются по «Обновить»; редактор шагов с предпросмотром | P1 (источник, фильтр, столбцы, типы, дубликаты, назначение), P2 (join/union/pivot/unpivot, группировка, M-подобные выражения) |
| Обновление | по команде, при открытии, по расписанию (скрипты), фоновое с прогрессом | P1 |
2.9 Защита
| Функция | Детали | Фаза |
|---|---|---|
| Защита листа | пароль (опц.), разрешённые действия (выделять заблокированные/незаблокированные, форматировать ячейки/столбцы/строки, вставлять столбцы/строки/гиперссылки, удалять, сортировать, фильтровать, сводные, объекты, сценарии); «Разрешить изменение диапазонов» с паролями по диапазонам | P0 |
| Защита книги | структура (листы), окна; пароль | P0 |
| Шифрование файла | пароль на открытие: .msx — AES-256-GCM + Argon2id; xlsx
— Agile Encryption (OOXML) чтение и запись |
P0 (чтение xlsx), P1 (запись) |
| Пометить как окончательный, только чтение | атрибуты документа | P1 |
| Подпись | цифровая подпись книги (X.509, ГОСТ — через Meridian ID/Enterprise) | P1 |
| Скрытые листы, очень скрытые (veryHidden) | как в Excel, доступны из скриптов | P0 |
2.10 Общие книги и совместная работа (фаза 6)
| Функция | Детали | Фаза |
|---|---|---|
| Совместное редактирование | CRDT (Yrs) по операциям над ячейками/строками/листами (§4.15); курсоры и выделения соавторов цветом; офлайн-правки сливаются | P2 (фаза 6) |
| Общий доступ | ссылка, роли (просмотр/комментирование/правка), Meridian Cloud или on-prem | P2 |
| История версий | снимки, сравнение версий с подсветкой изменённых ячеек, восстановление | P2 |
| Сравнение книг | локальный инструмент «Сравнить книги» (как Spreadsheet Compare) — без облака | P1 |
| Отслеживание изменений | журнал изменений по ячейкам (кто/когда/было/стало), принять/отклонить | P2 |
2.11 Печать
| Функция | Детали | Фаза |
|---|---|---|
| Параметры страницы | ориентация, размер, поля, центрирование, колонтитулы (коды
&[Page], &[Pages],
&[Date], &[Tab],
&[File], &[Path], рисунки), разные для
первой/чётных страниц |
P0 |
| Область печати | задать/добавить/убрать; несколько областей; печать выделенного | P0 |
| Сквозные строки и столбцы | повторять заголовки на каждой странице | P0 |
| Масштаб | %, «вписать в N страниц в ширину × M в высоту», автоподбор | P0 |
| Разрывы страниц | ручные, страничный режим с перетаскиванием разрывов | P0 |
| Сетка, заголовки строк/столбцов, примечания (в конце/как на листе), ошибки (как есть/пусто/--/#N/A), черновое качество, чёрно-белое | опции листа | P0 |
| Порядок страниц | вниз-затем-вправо / вправо-затем-вниз | P0 |
| Предпросмотр | backstage «Печать» с живым предпросмотром, печать всей книги / листа / выделения; экспорт PDF/A, PDF/UA с тегами таблиц | P0 |
2.12 Окна
| Функция | Детали | Фаза |
|---|---|---|
| Закрепление областей | верхняя строка, первый столбец, по текущей ячейке; индикатор линии | P0 |
| Разделение окна | на 2/4 области с независимой прокруткой, перетаскиваемые разделители | P0 |
| Несколько окон одной книги | «Новое окно» (Книга:2), независимый
лист/масштаб/выделение, «Рядом» с синхронной прокруткой, «Упорядочить
всё» |
P0 |
| Масштаб | 10–400 %, по выделению, Ctrl+колесо, трекпад; масштаб на лист | P0 |
| Представления (custom views) | сохранённые наборы: скрытые строки/столбцы, фильтры, параметры печати | P1 |
| Режимы просмотра | Обычный, Разметка страницы, Страничный; «Фокус» (только холст) | P0 |
| Тёмная тема сетки | отдельная настройка «тёмная сетка» (цвета ячеек по умолчанию адаптируются, явные — нет) | P1 |
2.13 Буфер обмена, поиск и замена
| Функция | Детали | Фаза |
|---|---|---|
| Копировать/вырезать/вставить | с адаптацией ссылок (§5.19); «живая» бегущая рамка; многократный буфер (панель, 24 элемента) | P0 (панель — P1) |
| Специальная вставка | всё / формулы / значения / форматы / примечания / проверка / всё с исходной темой / без рамок / ширины столбцов / формулы и форматы чисел / значения и форматы чисел / все условные форматы объединить; операции (нет/сложить/вычесть/умножить/разделить); пропускать пустые; транспонировать; вставить связь; как рисунок/связанный рисунок; из других приложений: текст, HTML, RTF, изображение | P0 |
| Найти и заменить | в формулах/значениях/примечаниях; по листу/книге; по
строкам/столбцам; учитывать регистр, ячейка целиком, подстановочные
* ? ~, регулярные выражения (Rust regex,
группы $1 в замене); искать по формату; «Найти всё» —
список с переходом; заменить всё с отчётом |
P0 (regex — P1) |
| Перейти (F5 / Ctrl+G) | адрес/имя; Выделить группу ячеек: примечания, константы (числа/текст/логические/ошибки), формулы (…), пустые, текущая область, текущий массив, объекты, отличия по строкам/столбцам, влияющие/зависимые, последняя ячейка, только видимые, условные форматы, проверка данных | P0 |
2.14 Примечания, обсуждения, гиперссылки
| Функция | Детали | Фаза |
|---|---|---|
| Примечания (notes) | классические жёлтые заметки (xlsx legacyDrawing),
автор, показать/скрыть все, формат, печать |
P0 |
| Обсуждения (threaded comments) | цепочки с ответами, @упоминания (с аккаунтом), решить/открыть
заново, панель «Комментарии» со списком по листу/книге, фильтр
«мои/нерешённые»; совместимо с xlsx threadedComments |
P0 (без @упоминаний), P1 (@упоминания, уведомления — фаза 6) |
| Гиперссылки | на URL, файл, место в книге (лист!ячейка, имя), почту; функция HYPERLINK; подсказка; Ctrl+клик; стили «Гиперссылка/Открытая гиперссылка»; удаление, автоссылки | P0 |
| Вставка объектов | изображения (в ячейку — IMAGE() и «в ячейку», и поверх
сетки), фигуры (через Draw-движок), текстовые поля, Math-формулы,
значки, снимок экрана, SmartArt-подобные схемы (P2), элементы управления
форм (кнопка, флажок, список, счётчик, полоса прокрутки — с привязкой к
ячейке) |
P0 (изображения, фигуры, кнопки/флажки), P1 (остальные элементы), P2 (схемы) |
2.15 Типы данных
| Тип | Хранение | Отображение / особенности | Фаза |
|---|---|---|---|
| Число | f64 + NumKind::Plain |
формат «Общий» — 11 символов (§5.10.4) | P0 |
| Дата, время, дата-время | f64 сериал (1900/1904) +
NumKind::Date/Time/DateTime |
тип переносится через копирование и формулы (=A1+1 —
дата); парсер ввода по локали (03.10.2026,
3 окт, 10/3 по локали, ISO 8601); 1900-02-29
как в Excel |
P0 |
| Длительность | f64 дни + NumKind::Duration |
[h]:mm:ss, d.hh:mm;
TIME-арифметика не теряет дни |
P0 |
| Валюта | f64 + NumKind::Currency(iso) |
код ISO 4217 из ввода (₽, $,
€, 1 000 руб.), формат по локали; сумма разных
валют → #VALUE! с пояснением (опция «не проверять
валюты») |
P0 (без проверки), P1 (проверка) |
| Процент | f64 + NumKind::Percent |
ввод 12% → 0,12; формат |
P0 |
| Дробь | f64 + NumKind::Fraction(denom_hint) |
1/2 при формате дроби; ввод 0 1/2 |
P0 |
| Логическое | Value::Bool |
ИСТИНА/ЛОЖЬ по локали, ввод
TRUE/ИСТИНА |
P0 |
| Текст | Value::Text(Arc<str>), общие строки |
rich text с runs; ведущий апостроф — «текст принудительно» | P0 |
| Ошибка | Value::Error(ErrorKind, Option<Arc<ErrorInfo>>) |
14 ошибок Excel + причина (§5.7) | P0 |
| Массив | Value::Array(Arc<Array>) |
только результат формул (spill) и константы {1;2} |
P0 |
| Богатые типы | Value::Rich(Arc<RichValue>): запись с полями,
значком, провайдером |
«География», «Акции», «Валюты», свои записи из JSON
(.field доступ A1.Население), карточка по
клику; совместимость с xlsx richData |
P2 (чтение xlsx — P1) |
| Изображение в ячейке | Value::Rich(Image) |
IMAGE(), вставка в ячейку, альтернативный текст |
P1 |
2.16 Макросы и скрипты
| Функция | Детали | Фаза |
|---|---|---|
| MeridianScript (TypeScript) | редактор скриптов во вкладке «Скрипты» (панель/отдельное окно с
подсветкой, автодополнением по .d.ts, отладчиком: точки
останова, просмотр переменных), скрипты книги (scripts/ в
пакете) и личные (Hub), запуск из ленты/кнопки/горячей
клавиши/события/расписания; песочница QuickJS-ng с лимитами (время,
память), разрешения (файлы, сеть, буфер) по запросу |
P0 (запуск, редактор, события), P1 (отладчик, расписание) |
| Запись действий | «Записать» → действия пользователя → читаемый TypeScript с относительными/абсолютными ссылками (переключатель), объединение однотипных операций | P0 |
| Пользовательские функции (UDF) | @customFunction в скрипте книги → доступна в формулах
как SCRIPT.ИМЯ/ИМЯ; типы аргументов, массивы,
асинхронные (потоковые) функции с #БУДЕТ… состоянием
(#GETTING_DATA) |
P0 (синхронные), P1 (асинхронные) |
| Python | опциональный плагин CPython 3.12 с тем же API (pyo3) | P2 |
| VBA | vbaProject.bin сохраняется без изменений при round-trip
xlsm; транслятор простых макросов VBA → TypeScript (подмножество:
Range/Cells/Worksheets/For…Next/If/With; остальное — комментарии
// TODO VBA:); выполнение VBA — нет |
P1 (транслятор) |
| Магазин скриптов | установка подписанных скриптов из магазина Meridian (фаза 7) | P2 |
| Политики | корпоративные: запрет скриптов, только подписанные, список доверенных издателей | P1 |
2.17 Доступность
| Функция | Детали | Фаза |
|---|---|---|
| Экранные дикторы | Qt Accessibility → UIA (Windows), NSAccessibility (macOS), AT-SPI2 (Linux): роль «таблица», ячейки с адресом/значением/формулой/примечанием, объявление изменений выделения, заголовки строк/столбцов | P0 |
| Клавиатура | все команды с клавиатуры; навигация по ленте (Alt/F10 + подсказки клавиш как в Office); фокусное кольцо 2 px | P0 |
| Высокий контраст и масштаб | режим высокой контрастности ОС; масштаб интерфейса 100–300 %; минимальный размер шрифта сетки | P0 |
| Проверка доступности | панель: отсутствующие alt-тексты, объединённые ячейки в таблицах данных, контраст заливки/текста, пустые заголовки таблиц | P1 |
| Уменьшенное движение | уважение к системной настройке (анимации выделения/прокрутки) | P0 |
| Диктовка и чтение вслух | через системные средства; «Прочитать ячейки» — команда | P1 |
3. Экраны
Идентификаторы: SH-01…SH-09 — главное окно и его
постоянные части; SH-10…SH-19 — боковые панели;
SH-20…SH-59 — диалоги; SH-60…SH-69 —
контекстные меню; SH-70+ — всплывающие элементы. Макет —
mockups/sheets.html, по одному
<section class="screen"> на идентификатор.
3.1 SH-01 Главное окно
Каркас — по §5.3 библии. Отличия Sheets: над холстом — строка формул (SH-02); под холстом — ярлычки листов (SH-03) в одной строке с горизонтальной полосой прокрутки; строка состояния (SH-04) показывает статистику выделения.
┌──────────────────────────────────────────────────────────────────────────────────┐
│ [▦] Бюджет 2026.msx — Meridian Sheets [⌘K Поиск команд] [☁ Сохранено] [👤] │
├──────────────────────────────────────────────────────────────────────────────────┤
│ Файл Главная Вставка Макет страницы Формулы Данные Рецензирование Вид Скрипты │ Таблица │
│ [Буфер][Шрифт][Выравнивание][Число][Стили][Ячейки][Редактирование] [⌄] │
├──────────┬───────────────────────────────────────────────────────────────────────┤
│ B4 ▾│ fx │ =SUM(B2:B3) [⌃ развернуть]│ ← SH-02
├──────────┴──┬────┬────────┬────────┬────────┬────────┬─────────────┬──────────────┤
│ Навигатор │ │ A │ B │ C │ D │ … │ Поля сводной │
│ (SH-14, │ 1 │ Статья │ План │ Факт │ Откл. │ │ / Свойства │
│ опц.) │ 2 │ Аренда │ 120000 │ 118500 │ -1500 │ │ (SH-10…SH-16)│
│ │ 3 │ Зарпл. │ 540000 │ 552300 │ 12300 │ │ │
│ │ 4 │ Итого │▌660000▐│ │ │ │ │
├─────────────┴────┴────────┴────────┴────────┴────────┴─────────────┴──────────────┤
│ ◂ ▸ │ Бюджет │ Данные │ Сводная │ ⊕ │ ◂═══════════════▸ │ ← SH-03
├──────────────────────────────────────────────────────────────────────────────────┤
│ Готово │ Среднее: 330 000 Количество: 2 Сумма: 660 000 │ ⚠ Циклы: 0 │ ▦ ▤ ▥ │ ─●─ 100 % │ ← SH-04
└──────────────────────────────────────────────────────────────────────────────────┘
Элементы холста: заголовки строк/столбцов (клик — выделить,
перетаскивание границы — размер, двойной клик — автоподбор), угол
«выделить всё», линии закрепления, рамка активной ячейки с маркером
заполнения (квадрат 6×6 px цвета --app-sheets), бегущая
рамка копирования, цветные рамки ссылок при правке формулы, значки
автофильтра в заголовках, кнопки структуры (+/−) слева/сверху,
индикаторы примечаний (красный треугольник) и обсуждений (фиолетовый),
треугольник проверки ошибок (зелёный), раскрывающиеся списки проверки
данных, объекты поверх сетки (диаграммы, рисунки, фигуры).
3.2 Ribbon — вкладки и группы
| Вкладка | Группы (команды) |
|---|---|
| Файл (backstage) | Создать (шаблоны Hub), Открыть, Сведения (защита, проверка, свойства, версии), Сохранить, Сохранить как, Экспорт (PDF, CSV, HTML, JSON, Parquet, изображение), Печать (SH-55), Поделиться (фаза 6), Параметры (SH-54), Аккаунт/Лицензия |
| Главная | Буфер обмена (Вставить▾, Вырезать, Копировать, Формат по образцу) · Шрифт (гарнитура, размер, Ж/К/Ч, границы▾, заливка▾, цвет текста▾) · Выравнивание (по вертикали ×3, ориентация▾, по горизонтали ×3, отступы, перенос, объединить▾) · Число (формат▾, денежный▾, %, разделитель, +/− разряды) · Стили (условное форматирование▾, форматировать как таблицу▾, стили ячеек▾) · Ячейки (вставить▾, удалить▾, формат▾) · Редактирование (Σ автосумма▾, заполнить▾, очистить▾, сортировка и фильтр▾, найти и выделить▾) · Assist (кнопка панели, Pro) |
| Вставка | Таблицы (сводная▾, рекомендуемые сводные, таблица) · Иллюстрации (рисунки▾, фигуры▾, значки, снимок) · Диаграммы (рекомендуемые, гистограмма▾, график▾, круговая▾, линейчатая▾, с областями▾, точечная▾, другие▾, сводная диаграмма) · Спарклайны (линия, гистограмма, выигрыш/проигрыш) · Фильтры (срез, временная шкала) · Ссылки (гиперссылка) · Комментарии (обсуждение, примечание) · Текст (надпись, колонтитулы, объект Math, подпись) · Символы (формула, символ) |
| Макет страницы | Темы (темы▾, цвета▾, шрифты▾, эффекты▾) · Параметры страницы (поля▾, ориентация▾, размер▾, область печати▾, разрывы▾, подложка, печатать заголовки) · Вписать (ширина, высота, масштаб) · Параметры листа (сетка: вид/печать; заголовки: вид/печать) · Упорядочение (переместить вперёд/назад, область выделения, выровнять, группировать, повернуть) |
| Формулы | Библиотека функций (вставить функцию, автосумма▾, недавние▾, финансовые▾, логические▾, текстовые▾, дата и время▾, ссылки и массивы▾, математические▾, другие▾) · Определённые имена (диспетчер имён, присвоить имя▾, использовать в формуле▾, создать из выделенного) · Зависимости формул (влияющие, зависимые, убрать стрелки▾, показать формулы, проверка ошибок▾, вычислить формулу, окно контрольного значения, панель «Зависимости») · Вычисление (параметры▾, пересчёт, пересчёт листа) |
| Данные | Получить и преобразовать (получить данные▾: из файла/БД/веб/буфера; потоки данных, из таблицы) · Запросы и подключения (обновить всё▾, подключения, свойства) · Сортировка и фильтр (А→Я, Я→А, сортировка, фильтр, очистить, повторить, дополнительно) · Работа с данными (текст по столбцам, мгновенное заполнение, удалить дубликаты, проверка данных▾, консолидация, связи) · Прогноз (анализ «что если»▾: сценарии/подбор/таблица данных; лист прогноза) · Структура (группировать▾, разгруппировать▾, промежуточные итоги, показать/скрыть детали) · Анализ (Поиск решения) |
| Рецензирование | Правописание (орфография, тезаурус, статистика) · Доступность · Язык (перевести — Assist) · Примечания (создать, предыдущее, следующее, показать все) · Обсуждения (создать, панель) · Защита (защитить лист, книгу, разрешить диапазоны, снять общий доступ) · Сравнить книги · Отслеживание (фаза 6) |
| Вид | Режимы (обычный, разметка, страничный, представления, фокус) · Показать (линейка, сетка, строка формул, заголовки) · Масштаб (масштаб, 100 %, по выделению) · Окно (новое окно, упорядочить, закрепить▾, разделить, скрыть/показать, рядом, синхронная прокрутка, переключить) · Скрипты (запись) |
| Скрипты | Код (редактор скриптов, записать действия, остановить, выполнить▾, отладчик) · Книга (скрипты книги, события, расписания) · Кнопки (кнопка в ленте, кнопка на листе, назначить) · Безопасность (разрешения, подписи, политики) |
| Таблица (контекст) | Свойства (имя, размер) · Инструменты (сводная, дубликаты, в диапазон, срез) · Параметры стиля (заголовок, итоги, чередование строк/столбцов, первый/последний столбец, фильтр) · Стили |
| Сводная таблица / Анализ (контекст) | Сводная (имя, параметры) · Активное поле (параметры, развернуть/свернуть) · Группировать (выделение, поле, разгруппировать) · Фильтр (срез, шкала, подключения) · Данные (обновить▾, источник▾) · Действия (очистить, выделить, переместить) · Вычисления (поля/элементы/порядок/список формул, «показать как») · Сервис (сводная диаграмма) · Показать (список полей, кнопки ±, заголовки) |
| Конструктор сводной (контекст) | Макет (промежуточные итоги▾, общие итоги▾, макет отчёта▾, пустые строки▾) · Параметры стиля · Стили |
| Диаграмма / Конструктор (контекст) | Макеты (добавить элемент▾, экспресс-макет▾) · Стили · Данные (строка/столбец, выбрать данные) · Тип (изменить тип) · Расположение (переместить) |
| Диаграмма / Формат (контекст) | Текущий фрагмент (выбор, формат, сброс) · Стили фигур · Стили WordArt · Упорядочение · Размер |
| Рисунок / Фигура (контекст) | Изменение (удалить фон, коррекция, цвет, эффекты, сжать, заменить, сброс) · Стили · Доступность (alt) · Упорядочение · Размер · Обрезка |
Компактный режим: одна строка 40 px с самыми частыми командами,
вкладки открываются как выпадающие панели. Командная палитра
Ctrl+K ищет команды, функции («СУММЕСЛИ» → вставить),
имена, листы, скрипты.
3.3 Постоянные части окна
| ID | Элемент | Описание |
|---|---|---|
| SH-02 | Строка формул | Именное поле (адрес активной ячейки/имя; ввод
адреса → переход; ввод имени → создание; выпадающий список имён и
таблиц) · кнопки ✗ ✓ fx · поле формулы: однострочное с
разворачиванием (Ctrl+Shift+U) в многострочный редактор с
подсветкой синтаксиса (функции, ссылки цветом рамок, строки, числа,
ошибки), автодополнением (имена функций по локали, имена,
структурированные ссылки, поля LAMBDA), подсказкой аргументов,
форматированием формулы (Ctrl+Shift+F), счётчиком скобок; в
многострочном режиме — номера строк; высота запоминается на книгу |
| SH-03 | Ярлычки листов | кнопки прокрутки, ярлычки (цвет, значок защиты/скрипта/скрытых
фильтров), ⊕ добавить, двойной клик — переименовать,
перетаскивание (Ctrl — копия, в другое окно — перенос между книгами),
контекстное меню SH-63; при >20 листах — кнопка «Все листы» (список с
поиском, SH-14) |
| SH-04 | Строка состояния | режим (Готово / Ввод / Правка / Укажите / Запись макроса ●); статистика выделения (Среднее · Количество · Количество чисел · Минимум · Максимум · Сумма — набор настраивается, клик копирует значение); индикатор пересчёта («Вычисление: 45 %» с кнопкой остановки) и циклов; Caps/Num/Scroll Lock, режим Конец, фиксированный десятичный формат; «Фильтр: найдено 1 234 из 50 000»; кнопки режимов просмотра; масштаб (ползунок, %, клик → диалог) |
| SH-05 | Заголовки строк/столбцов | номера/буквы (A…XFD) или R1C1-числа; подсветка активных; перетаскивание границы с подсказкой «Ширина: 12,43 (92 px)»; в «большом режиме» — номера до 2 147 483 647 с группировкой разрядов |
| SH-06 | Навигационная панель (слева, опц.) | по умолчанию скрыта; содержит SH-14 (Навигатор) |
| SH-07 | Область объектов | диаграммы/рисунки поверх сетки с маркерами размера/поворота, привязка «перемещать и изменять с ячейками / только перемещать / не перемещать» |
| SH-08 | Окно контрольного значения (Watch) | плавающая/пристыкованная таблица: книга, лист, имя, ячейка, значение, формула |
| SH-09 | Уведомления над сеткой | жёлтая полоса: «Защищённый просмотр», «Скрипты отключены — Включить», «Внешние связи — Обновить», «Файл содержит VBA — Перевести в MeridianScript», «Книга пересчитана в другой версии» |
3.4 Боковые панели (правая зона, вкладки с переключением, ширина 280–480 px)
| ID | Панель | Содержимое |
|---|---|---|
| SH-10 | Свойства диаграммы | дерево элементов диаграммы → секции: Заливка и линия, Эффекты, Размер и свойства, Параметры ряда/оси/подписей (как «Формат области диаграммы» Excel); контекстно следует за выделенным элементом |
| SH-11 | Поля сводной таблицы | поиск по полям, список полей с флажками (иерархии дат), 4 области с перетаскиванием, меню поля (параметры, переместить, удалить), «Отложить обновление макета» |
| SH-12 | Условное форматирование | правила для выделения/листа/книги в порядке приоритета, добавить/изменить/удалить/дублировать, стрелки порядка, предпросмотр, «остановить, если истина», область «применяется к» с выбором на листе |
| SH-13 | Проверка данных | то же для правил проверки: условие, подсказка, сообщение, область; список ячеек с нарушениями |
| SH-14 | Навигатор | дерево: листы (с цветами, скрытые серым), имена (с областью), таблицы, сводные, диаграммы, объекты, обсуждения; поиск; двойной клик — переход; перетаскивание листов |
| SH-15 | Поиск и замена | режим панели для «Найти всё»: результаты по листам (адрес, значение, формула), фильтр по области, замена с предпросмотром |
| SH-16 | Зависимости и профайлер | граф влияющих/зависимых для активной ячейки (2–3 уровня, кликабельные узлы), «почему пересчиталось», список самых дорогих формул последнего пересчёта (время, число вызовов, доля), циклы; кнопка «Объяснить ошибку» — цепочка от первопричины |
| SH-17 | Комментарии (обсуждения) | список цепочек по листу/книге, фильтры, ответить/решить, переход к ячейке |
| SH-18 | Assist | «Формула по описанию» (вставляет формулу с объяснением), «Объясни формулу», «Очистить данные», «Создай сводную по вопросу», перевод текста ячеек; требует редакции Pro и согласия на отправку данных (локальная модель — без отправки) |
| SH-19 | Скрипты | список скриптов книги/личных, кнопки запуска, журнал выполнения (console), ошибки с переходом в редактор; редактор открывается отдельным окном SH-50 |
| SH-19a | Область выделения | список объектов листа (показать/скрыть, порядок) |
| SH-19b | Буфер обмена | до 24 последних элементов (текст/таблица/изображение) |
| SH-19c | Стили (ячеек/таблиц/сводных) | галерея с предпросмотром и правкой стиля |
3.5 Диалоги
| ID | Диалог | Вкладки / содержимое |
|---|---|---|
| SH-20 | Формат ячеек (Ctrl+1) |
Число (категории слева; справа параметры: число десятичных, разделитель, отрицательные (с цветом), символ валюты, тип даты/времени с локалью, тип дроби, «Все форматы» с полем кода, образцом и списком, кнопкой «Удалить» и подсказкой по коду) · Выравнивание (горизонтальное/вертикальное, отступ, перенос, автоподбор, объединение, ориентация с циферблатом и градусами, направление текста) · Шрифт · Граница (пресеты, стиль/цвет линии, схема с кликабельными сторонами и диагоналями) · Заливка (цвет фона, узор + цвет узора, градиент, «Другие цвета») · Защита (защищаемая, скрыть формулы). Для rich-текста вкладка Шрифт применяется к выделенному фрагменту |
| SH-21 | Мастер функций (Вставить функцию, Shift+F3) |
поиск по описанию (ru/en), категория, список с кратким описанием и подписью; шаг 2 «Аргументы функции»: поля аргументов с кнопками выбора диапазона (сворачивание диалога), значения аргументов, результат формулы, ссылка на справку |
| SH-22 | Диспетчер имён (Ctrl+F3) |
таблица (имя, значение, диапазон/формула, область, примечание, тип: диапазон/константа/LAMBDA), фильтр (область, с ошибками, определённые/табличные), Создать/Изменить/Удалить, поле «Диапазон» с выбором на листе; редактор LAMBDA с подписью параметров и описанием |
| SH-23 | Создание имени | имя, область (книга/лист), примечание, диапазон/формула |
| SH-24 | Сортировка | уровни (столбец/строка, сортировка по: значения/цвет ячейки/цвет шрифта/значок, порядок: А→Я/Я→А/список/цвет сверху-снизу), добавить/удалить/копировать уровень, стрелки, Параметры (регистр, строки/столбцы, метод: обычный/по символам), «Мои данные содержат заголовки» |
| SH-25 | Пользовательские списки | списки для сортировки и автозаполнения, импорт из диапазона |
| SH-26 | Расширенный фильтр | обработка (на месте/копировать), исходный диапазон, диапазон условий, поместить в, только уникальные |
| SH-27 | Пользовательский автофильтр | два условия с И/ИЛИ, операторы, подсказка про ? * |
| SH-28 | Удаление дубликатов | выделить всё/снять, список столбцов, «данные содержат заголовки» |
| SH-29 | Проверка данных | Параметры (тип, условие, значения/источник, игнорировать пустые, список допустимых значений, «распространить на все ячейки с тем же условием») · Подсказка при вводе · Сообщение об ошибке (вид, заголовок, текст) |
| SH-30 | Диспетчер правил условного форматирования | список правил (правило, формат-образец, применяется к, остановить если истина), Создать/Изменить/Удалить/Дублировать, порядок, область показа (текущее выделение/этот лист/лист N/таблица/сводная) |
| SH-31 | Создание/изменение правила форматирования | тип правила (6 типов, как Excel: по значениям всех ячеек (шкалы/гистограммы/значки), только ячейки с…, первые/последние, выше/ниже среднего, уникальные/повторяющиеся, формула); параметры типа; кнопка «Формат…» → SH-20 (без вкладки Защита); предпросмотр |
| SH-32 | Параметры страницы | Страница (ориентация, масштаб/вписать, размер, качество, номер первой страницы) · Поля (с центрированием) · Колонтитулы (готовые, настраиваемые с кодами, разные для первой/чётных, масштабировать, выравнивать с полями) · Лист (область печати, сквозные строки/столбцы, печатать: сетка/ч-б/черновой/заголовки/примечания/ошибки; порядок) |
| SH-33 | Колонтитул (настраиваемый) | три секции, кнопки вставки кодов, формат текста, рисунок |
| SH-34 | Защита листа | пароль, флажки разрешённых действий (15 пунктов) |
| SH-35 | Защита книги | структура, окна, пароль |
| SH-36 | Разрешить изменение диапазонов | список диапазонов с паролями, разрешения пользователей (Enterprise: по группам Meridian ID) |
| SH-37 | Подбор параметра | установить в ячейке, значение, изменяя значение ячейки; окно результата (статус, итерации, текущее значение, Шаг/Пауза) |
| SH-38 | Поиск решения | целевая функция, макс/мин/значение, изменяемые ячейки, ограничения (список, Добавить/Изменить/Удалить/Сбросить), «неотрицательные», метод (Симплекс/ОПГ/Эволюционный), Параметры (точность, итерации, время, сходимость, мультистарт, популяция, случайное начальное, показывать итерации), Загрузить/Сохранить модель; окно результатов с выбором отчётов |
| SH-39 | Добавить ограничение | ссылка на ячейку, оператор
(<=,=,>=,int,bin,dif),
ограничение |
| SH-40 | Мастер импорта текста | шаг 1: кодировка (автоопределение + список), формат
(разделители/фиксированная ширина), начать со строки, «данные с
заголовками», предпросмотр сырых строк; шаг 2: разделители (таб,
;, ,, пробел, другой), «считать
последовательные одним», ограничитель строк, предпросмотр таблицы (для
фиксированной ширины — линии разрыва); шаг 3: формат столбцов
(общий/текст/дата с порядком/пропустить), Дополнительно
(десятичный/тысячный разделитель, ведущий минус); назначение: лист
(куда), новая таблица, поток данных |
| SH-41 | Импорт данных (универсальный, «Получить данные») | навигатор источников: файл (CSV/TSV, JSON, XML, Parquet, xlsx-лист, ODS), база (через Base: список таблиц/запрос), веб (URL, список таблиц), буфер; предпросмотр; «Загрузить» / «Преобразовать» (открыть редактор потока SH-42) |
| SH-42 | Редактор потока данных | слева — список шагов (с возможностью вернуться к любому), центр — предпросмотр таблицы (1000 строк), справа — параметры шага; меню шагов (§2.8); «Применить» создаёт/обновляет назначение |
| SH-43 | Создание сводной таблицы | источник (таблица/диапазон, внешний, модель данных P2), размещение (новый лист / существующий: адрес), «добавить в модель данных» (P2) |
| SH-44 | Параметры сводной таблицы | Макет и формат (объединять подписи, отступ, «для ошибок показывать», «для пустых ячеек показывать», автоширина, сохранять форматирование) · Итоги и фильтры · Отображение (кнопки ±, подсказки, заголовки полей, классический макет) · Печать · Данные (сохранять источник, развёртывание, обновлять при открытии, число элементов на поле) · Замещающий текст |
| SH-45 | Параметры поля значений / поля | имя, операция, «дополнительные вычисления» (базовое поле/элемент), числовой формат; для полей строк/столбцов: промежуточные итоги, макет, печать, сортировка |
| SH-46 | Группировка (сводная) | даты: начиная с / по / шаги (секунды…годы, дни по N); числа: начиная с / по / с шагом |
| SH-47 | Вычисляемое поле / элемент | имя, формула с вставкой поля/элемента, список формул |
| SH-48 | Вставка срезов / временной шкалы | список полей с флажками |
| SH-49 | Выбор источника данных диаграммы | диапазон, ряды (добавить/изменить/удалить, порядок), подписи оси, строка/столбец, скрытые и пустые ячейки |
| SH-50 | Редактор скриптов (отдельное окно) | дерево скриптов (книга/личные), редактор с подсветкой TS и
автодополнением по meridian-sheets.d.ts, консоль, отладчик
(точки останова, шаг, переменные, стек), запись действий (кнопка ●),
события (привязка onEdit/onOpen/…), расписания, разрешения скрипта,
подпись |
| SH-51 | Прогрессия | по строкам/столбцам, тип (арифм./геом./даты/автозаполнение), единицы даты, шаг, предел, тренд |
| SH-52 | Консолидация | функция, ссылка с кнопкой выбора, список диапазонов, использовать подписи (верхняя строка/левый столбец), создавать связи |
| SH-53 | Таблица данных «что если» / Диспетчер сценариев / Добавление сценария | подстановка по строкам/столбцам; список сценариев (добавить/удалить/изменить/объединить/отчёт), изменяемые ячейки, значения, защита |
| SH-54 | Параметры Sheets | Общие (число листов в новой книге, шрифт/размер по умолчанию,
направление Enter, автозавершение, Ctrl+Enter) · Формулы (режим
пересчёта, итерации, R1C1, автодополнение, локализованные имена функций,
точность как на экране, 1904, проверка ошибок — правила) · Правка
(перетаскивание, маркер, автозамена, фиксированный десятичный, расширять
форматы таблиц) · Данные (параметры сводных по умолчанию, импорт CSV по
умолчанию, отключить автоопределение дат) · Язык и регион (локаль
разделителей, формат даты, валюта) · Вид (сетка, цвет сетки, нули,
строки формул/состояния, заголовки, разрывы, объекты, тёмная сетка) ·
Печать · Сохранение (формат по умолчанию: .msx/xlsx,
автосохранение, интервал журнала) · Скрипты и безопасность (политика
скриптов, доверенные издатели, внешние ссылки, внешние данные) ·
Пользовательские списки · Доступность · Дополнительно |
| SH-55 | Печать (backstage) | принтер и свойства, копии, что печатать (листы/книга/выделение/область), страницы, разобрать, ориентация, размер, поля, масштаб (вписать), двусторонняя; предпросмотр с перелистыванием, показом полей (перетаскиваемых), сетка |
| SH-56 | Перейти / Выделить группу ячеек | адрес, список имён; группы (§2.13) |
| SH-57 | Найти и заменить | две вкладки (Найти / Заменить), Параметры (область: лист/книга; просматривать: по строкам/столбцам; искать в: формулы/значения/примечания; регистр; целиком; формат ▾; regex); «Найти всё» разворачивает список |
| SH-58 | Вставка гиперссылки | связать с: файлом/веб-страницей, местом в документе (дерево листов + имена), новым документом, электронной почтой; текст, подсказка |
| SH-59 | Специальная вставка | группы «Вставить», «Операция», «Пропускать пустые», «Транспонировать», «Вставить связь» |
| SH-59a | Переместить/скопировать лист | в книгу (список открытых/новая), перед листом, «создавать копию» |
| SH-59b | Параметры автозаполнения / Параметры вставки | всплывающие кнопки с меню после операции |
| SH-59c | Изменить связи | список связей (источник, тип, обновление, состояние), обновить, изменить источник, открыть, разорвать, запрос при запуске |
| SH-59d | Параметры автофильтра по дате/числу/тексту | меню фильтра в заголовке: сортировка, по цвету, фильтры типа, поиск, дерево значений |
| SH-59e | Диалог «Циклическая ссылка» / «#SPILL!» | список ячеек цикла с переходом; для spill — что мешает, «Сдвинуть мешающие ячейки» |
| SH-59f | Сравнение книг | две книги, результат — список различий (ячейка, было/стало, тип), подсветка на листах |
| SH-59g | Статистика книги | листы, ячейки с данными, формулы, таблицы, сводные, диаграммы, скрипты, время последнего пересчёта |
3.6 Контекстные меню
| ID | Контекст | Пункты |
|---|---|---|
| SH-60 | Ячейка/диапазон | Вырезать, Копировать, Параметры вставки (значки), Специальная вставка…, Вставить ячейки… (или Вставить скопированные), Удалить…, Очистить содержимое, Быстрый анализ (мини-галерея: форматирование/диаграммы/итоги/таблицы/спарклайны), Фильтр ▸, Сортировка ▸, Вставить обсуждение, Вставить примечание, Формат ячеек…, Выбрать из раскрывающегося списка, Присвоить имя…, Гиперссылка…, Объяснить (Assist); для spill — «Выделить массив»; для ячеек ошибок — «Объяснить ошибку» |
| SH-61 | Заголовок строки/столбца | Вырезать, Копировать, Вставить, Специальная вставка, Вставить, Удалить, Очистить содержимое, Формат ячеек, Ширина столбца/Высота строки, Скрыть, Показать, Группировать, Разгруппировать |
| SH-62 | Структурированная таблица | все из SH-60 + Таблица ▸ (итоги, в диапазон, изменить размер, преобразовать), Вставить ▸ (строки/столбцы таблицы), Удалить ▸, Выделить ▸ (столбец данных/весь столбец/строка), Обновить (если из внешних данных) |
| SH-63 | Ярлычок листа | Вставить…, Удалить, Переименовать, Переместить или скопировать…, Показать код (скрипты листа), Защитить лист…, Цвет ярлычка ▸, Скрыть, Показать…, Выделить все листы, Разгруппировать листы |
| SH-64 | Сводная таблица | Копировать, Формат ячеек, Числовой формат, Обновить, Сортировка ▸, Фильтр ▸, Промежуточный итог «…», Развернуть/свернуть ▸, Группировать…, Разгруппировать, Переместить ▸, Удалить «…», Итоги по ▸, Дополнительные вычисления ▸, Показать детали, Параметры поля значений…, Параметры сводной таблицы…, Показать список полей |
| SH-65 | Диаграмма / элемент | Вырезать, Копировать, Параметры вставки, Сбросить стиль, Шрифт, Изменить тип, Сохранить как шаблон, Выбрать данные, Переместить диаграмму, Повернуть объёмную, Группировать/На передний план/На задний план, Назначить скрипт, Формат области…; для ряда: Добавить подписи, Линия тренда, Формат ряда |
| SH-66 | Рисунок / фигура | Вырезать/Копировать/Вставить, Изменить текст, Точки, Группировать ▸, Порядок ▸, Назначить скрипт, Гиперссылка, Alt-текст, Размер и свойства, Формат |
| SH-67 | Строка формул | Отменить, Вырезать, Копировать, Вставить, Выделить всё, Вставить функцию, Вставить имя, Форматировать формулу, Вычислить фрагмент (F9), Преобразовать в значение |
| SH-68 | Строка состояния | флажки показываемых элементов (режим, статистика ×6, Caps/Num, фильтр, масштаб, циклы, пересчёт) |
| SH-69 | Ribbon | Добавить на панель быстрого доступа, Настроить ленту…, Свернуть ленту, Компактный режим |
3.7 Мини-панель и всплывающие элементы
| ID | Элемент | Описание |
|---|---|---|
| SH-70 | Мини-панель форматирования | появляется при правом клике/выделении с модификатором: шрифт,
размер, Ж/К, цвет текста, заливка, границы, выравнивание ×3, формат
числа (%, ,, ₽), разряды ±,
объединить; скрывается при отводе мыши |
| SH-71 | Подсказка функции | при вводе формулы: имя (локализованное и английское), подпись аргументов с выделением текущего, краткое описание, ссылка «Справка»; стрелки для перехода к аргументам |
| SH-72 | Список автодополнения | функции/имена/таблицы/поля; Tab — принять |
| SH-73 | Быстрый анализ | значок в углу выделения: Форматирование / Диаграммы / Итоги / Таблицы / Спарклайны с живым предпросмотром |
| SH-74 | Подсказка ячейки | при наведении: примечание, проверка данных (подсказка при вводе), гиперссылка, сообщение об ошибке с «Объяснить», обсуждение (первый ответ) |
| SH-75 | Карточка богатого типа | поля записи, источник, время обновления (P2) |
| SH-76 | Индикатор совместной работы | аватары соавторов, их выделения (фаза 6) |
4. Модель данных
Крейт meridian-grid. Модель не знает об интерфейсе и
форматах файлов; изменения проходят только через операции (§4.15), что
даёт undo, журнал автосохранения и CRDT одной механикой.
4.1 Обзор
Workbook
├─ sheets: Vec<Sheet> ← порядок ярлычков
├─ names: NameTable ← имена книги (+ ссылки на имена листов)
├─ styles: StyleTable ← общие стили ячеек (индексы)
├─ strings: StringPool ← общие строки
├─ formulas: FormulaStore ← скомпилированные формулы (общие)
├─ tables: Vec<Table>, pivots: Vec<Pivot>, charts: Vec<ChartObject>
├─ pivot_caches: Vec<PivotCache>, connections: Vec<DataConnection>, flows: Vec<DataFlow>
├─ theme: Theme, calc: CalcSettings, protection: WorkbookProtection, props: DocProps
├─ scripts: ScriptBundle, history: OpLog (CRDT/undo), external_links: Vec<ExternalLink>
└─ deps: DependencyGraph (в meridian-calc, строится из формул)
Sheet
├─ id: SheetId (стабильный, не меняется при переименовании/перемещении)
├─ cells: CellStore (чанки 256×256)
├─ rows: AxisProps, cols: AxisProps (высоты/ширины/скрытие/группы/стили — RLE)
├─ merges: MergeIndex, cf: Vec<CfRule>, validations: Vec<Validation>
├─ autofilter, sparklines, hyperlinks, notes, threads, objects (диаграммы/рисунки/фигуры/контролы)
├─ view: SheetView (закрепление, масштаб, выделение, разделение), print: PrintSetup, protection
└─ kind: SheetKind { Worksheet, ChartSheet, Macro(legacy), Dialog(legacy, только round-trip) }
4.2 Выбор хранения ячеек: чанки 256×256
Рассмотрены два варианта.
| Критерий | B-дерево по ключу (row, col) |
Чанки 256×256 (решение) |
|---|---|---|
| Память на ячейку | ключ 8 Б + узловые накладные ≈ 40 Б + значение | в плотном чанке 0 Б накладных на ячейку; в разреженном — 1 бит занятости + 2 Б ранга на 64 ячейки |
| Доступ по адресу | O(log N), N — число ячеек | O(1): хэш чанка + rank по битовой карте |
Проход по диапазону (SUM(A:A)) |
обход дерева, промахи кэша | последовательный проход по столбцу внутри чанка (SoA), SIMD-суммирование |
| Вставка строки | перенумерация всех ключей ниже: O(N) | memmove внутри затронутых чанков + перенос одной строки на границе: O(N) с малой константой (16 Б × ячеек ниже; 1M ячеек ≈ 3 мс) |
| Экстремально разреженный лист (1000 ячеек на площади 1M×16k) | лучше | 1000 чанков × ~2 КБ ≈ 2 МБ — приемлемо |
| Параллельный пересчёт/рендер | блокировка дерева | чанки независимы: разделяемый доступ на чтение, запись через буфер результатов |
Совместимость с xlsx (<row> с
<c>) |
— | импорт построчно пишет чанк за чанком, без сортировки |
Решение: чанки 256 строк × 256 столбцов, два
представления внутри чанка — разреженное (битовая карта занятости +
плотный вектор занятых слотов, индексация через rank) и плотное (массив
65 536 слотов), переход при заполненности > 40 % (и обратно < 20
%, с гистерезисом). Чанки лежат в
BTreeMap<ChunkKey, Box<Chunk>> (обход в порядке
строк нужен для сохранения файла и печати; при 262 144 чанках полного
листа дерево мало).
// kernel/crates/meridian-grid/src/store.rs
/// Координаты ячейки / Cell coordinates (0-based)
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CellRef { pub row: u32, pub col: u32 }
/// Ключ чанка: 256×256 / Chunk key: 256×256
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ChunkKey { pub crow: u32, pub ccol: u32 } // crow = row >> 8, ccol = col >> 8
pub const CHUNK: usize = 256;
pub const CHUNK_CELLS: usize = CHUNK * CHUNK; // 65 536
/// Слот ячейки — то, что лежит в чанке / Cell slot stored in a chunk (24 B value + 8 B meta)
#[derive(Clone)]
pub struct CellSlot {
pub value: Value, // 24 Б: тег + f64 | Arc<str> | …
pub style: StyleId, // u32, индекс в StyleTable (0 = Обычный)
pub extra: ExtraId, // u32, 0 = нет; иначе индекс в side-table (формула, примечание, ссылка, spill, rich)
}
pub enum Chunk {
/// Разреженный: битовая карта занятости + rank-таблица + вектор слотов в порядке row-major
/// Sparse: occupancy bitmap + rank table + slot vector in row-major order
Sparse {
occupancy: Box<[u64; 1024]>, // 65 536 бит = 8 КБ
rank: Box<[u16; 1024]>, // число занятых бит до слова i (префиксные суммы) → O(1) индекс
slots: Vec<CellSlot>,
},
/// Плотный: прямой массив / Dense: direct array (всегда 65 536 слотов, ~2 МБ)
Dense { slots: Box<[CellSlot; CHUNK_CELLS]>, count: u32 },
}
impl Chunk {
#[inline]
fn local(r: u32, c: u32) -> usize { ((r & 255) as usize) << 8 | (c & 255) as usize }
pub fn get(&self, r: u32, c: u32) -> Option<&CellSlot> {
let i = Self::local(r, c);
match self {
Chunk::Dense { slots, .. } => slots[i].is_empty().not().then(|| &slots[i]),
Chunk::Sparse { occupancy, rank, slots } => {
let (w, b) = (i >> 6, i & 63);
if occupancy[w] >> b & 1 == 0 { return None; }
// rank(i) = занятых до слова + занятых в слове до бита / popcount prefix
let idx = rank[w] as usize + (occupancy[w] & ((1u64 << b) - 1)).count_ones() as usize;
Some(&slots[idx])
}
}
}
// set/remove: вставка в Vec — O(занятых в чанке); массовые операции идут через ChunkBuilder
// set/remove: Vec insert is O(occupied in chunk); bulk loads go through ChunkBuilder
}
pub struct CellStore {
chunks: BTreeMap<ChunkKey, Box<Chunk>>,
/// Ограничивающий прямоугольник данных (UsedRange) / used-range bounds, кэш
used: Option<Rect>,
/// Карта «есть ли непустые в строке/столбце» для Ctrl+стрелок и UsedRange
row_nonempty: RoaringBitmap, col_nonempty: RoaringBitmap,
}Для колоночных агрегатов (SUM, COUNT,
сортировка) чанк отдаёт итератор по столбцу col_iter(c) — в
плотном представлении это шаг 256 слотов, в разреженном — проход по
словам битовой карты с маской столбца; при импорте больших CSV
используется ChunkBuilder, формирующий плотные чанки
напрямую (без промежуточных вставок).
4.3 Значения
// kernel/crates/meridian-grid/src/value.rs
#[derive(Clone)]
pub enum Value {
Empty,
Number(f64), // число, дата, время, валюта, процент — тип в NumKind (см. extra/стиль)
Text(Arc<str>), // общий текст из StringPool (интернирование: равные строки — один Arc)
Bool(bool),
Error(ErrorKind), // причина (ErrorInfo) — в side-table по ExtraId, чтобы не раздувать enum
Array(Arc<Array>), // результат формулы (spill) или константа {…}; Array = rows×cols Vec<Value>
Rich(Arc<RichValue>), // богатый тип: запись с полями / изображение в ячейке (P1/P2)
}
// size_of::<Value>() == 24 (тег + fat pointer Arc<str>). Вариант уменьшения до 16 Б: ThinArc<str> (triomphe) —
// решение отложено до бенчмарков (§10). / 24 bytes; ThinArc option deferred until benchmarks.
/// Подтип числа — переносится формулами (§5.8.3) / numeric subtype propagated by formulas
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum NumKind { Plain, Date, Time, DateTime, Duration, Percent, Currency(CurrencyId), Fraction, Scientific }
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum ErrorKind {
Null = 1, Div0 = 2, Value = 3, Ref = 4, Name = 5, Num = 6, NA = 7, GettingData = 8,
Spill = 9, Connect = 10, Blocked = 11, Unknown = 12, Field = 13, Calc = 14,
}
/// Текст с форматированием фрагментов / rich text runs — хранится в side-table, Value::Text держит плоский текст
pub struct RichText { pub text: Arc<str>, pub runs: Vec<TextRun> } // TextRun { start: u32, font: FontId }Общие строки. StringPool — интернер
(HashMap<Arc<str>, ()> + weak-подсчёт) с
дедупликацией: текст столбца «Москва» в 100 000 строк хранится один раз;
при экспорте в xlsx пул отображается в sharedStrings.xml
напрямую. Строки короче 16 байт можно хранить inline (SSO) — отложено
(§10).
Стили по индексам. StyleTable хранит
уникальные CellStyle (шрифт, заливка, граница, число,
выравнивание, защита — каждый сам по индексу в своей таблице, как
cellXfs в xlsx); ячейка несёт только StyleId.
Стили строк/столбцов («весь столбец зелёный») — в
AxisProps, разрешение: ячейка → строка → столбец →
«Обычный».
pub struct StyleTable {
pub fonts: Vec<Font>, pub fills: Vec<Fill>, pub borders: Vec<Border>,
pub numfmts: Vec<NumFmt>, // код + скомпилированный формат (§5.10)
pub xfs: Vec<CellXf>, // комбинация индексов + выравнивание + защита; дедупликация по хэшу
pub named: Vec<NamedStyle>, // стили ячеек (Обычный, Заголовок 1, …), xf-наследование
pub table_styles: Vec<TableStyle>, // стили таблиц/сводных (элементы: wholeTable, headerRow, firstRowStripe, …)
xf_index: HashMap<CellXf, StyleId>,
}4.4 Строки и столбцы
/// Свойства оси (строки или столбцы) — RLE по диапазонам, чтобы 1M одинаковых строк занимали одну запись
/// Axis properties (rows or columns), run-length encoded by ranges
pub struct AxisProps {
runs: Vec<AxisRun>, // отсортированы по start, не пересекаются
default_size: f32, // высота 15 pt / ширина 8.43 символа (в единицах §5.17.1)
outline_levels: u8, // макс. уровень структуры (≤ 8)
size_prefix: FenwickTree<f64>, // префиксные суммы размеров для прокрутки/печати (ленивая перестройка)
}
pub struct AxisRun {
pub start: u32, pub end: u32, // включительно
pub size: Option<f32>, // None = по умолчанию; для строк: ручная или автоподбор (флаг)
pub custom_size: bool, // высота задана вручную (не пересчитывать автоподбор)
pub hidden: bool, pub collapsed: bool, pub outline_level: u8,
pub style: Option<StyleId>, // стиль всей строки/столбца
pub page_break: bool,
}4.5 Объединённые ячейки
MergeIndex — список Rect + R-дерево
(rstar) для запросов «в какое объединение входит (r,c)» при
рендере, навигации и вставке. Значение и стиль живут в левой верхней
ячейке; остальные пусты; формулы, ссылающиеся на внутренние ячейки,
видят Empty (как Excel). Операции вставки/удаления строк
корректируют прямоугольники; частичное пересечение объединения с
удаляемым диапазоном — ошибка операции («нельзя изменить часть
объединённой ячейки»), как в Excel.
4.6 Формулы: AST и байткод
Ячейка с формулой:
CellSlot.extra → Extra::Formula { formula: FormulaId, base: CellRef };
Value хранит последний результат (кэш). Формулы хранятся
общими: одна скомпилированная формула для
=B2*C2, =B3*C3, … (относительные ссылки в
R1C1-виде), base — ячейка, относительно которой разрешаются
ссылки. Это копия механизма shared formulas xlsx и даёт
100-кратную экономию для столбцов из 1M формул.
// kernel/crates/meridian-calc/src/ast.rs
pub enum Expr {
Num(f64), Str(Arc<str>), Bool(bool), Err(ErrorKind),
ArrayConst(Arc<Array>),
Ref(RefExpr), // A1, $A$1, Лист!A1, Лист1:Лист3!A1, [Книга]Лист!A1
Range(Box<Expr>, Box<Expr>), // оператор ':' между выражениями (INDEX(..):INDEX(..))
Name(NameId), // определённое имя / LET-переменная / параметр LAMBDA
StructRef(StructRef), // Таблица1[[#Данные];[Сумма]]
Spill(Box<Expr>), // A1#
Call { func: FuncId, args: Vec<Expr> }, // функция (встроенная, UDF, LAMBDA)
Unary(UnOp, Box<Expr>), // -x, +x, x%
Binary(BinOp, Box<Expr>, Box<Expr>), // + - * / ^ & = <> < > <= >= : , (union) ' ' (intersect)
Implicit(Box<Expr>), // @expr — неявное пересечение
Lambda { params: Vec<NameId>, body: Box<Expr> },
Let { bindings: Vec<(NameId, Expr)>, body: Box<Expr> },
Missing, // пропущенный аргумент IF(A1;;B1)
}
/// Ссылка: каждая координата абсолютная или относительная (хранится относительно base формулы)
/// Reference: each coordinate absolute or relative (stored relative to formula base)
pub struct RefExpr {
pub sheet: SheetRange, // Current | One(SheetId) | Span(SheetId, SheetId) | External(LinkId, SheetName)
pub r1: Coord, pub c1: Coord, pub r2: Coord, pub c2: Coord, // Coord::Abs(u32) | Coord::Rel(i32) | Coord::All
pub flags: RefFlags, // row_span (1:1), col_span (A:A), trim (A.:.B — P2), deleted → #REF!
}Байткод — стековая машина с типизированными операндами
Val (как Value, плюс RangeRef —
ленивая ссылка на диапазон, материализуемая только когда функция требует
значения):
pub struct CompiledFormula {
pub code: Vec<Op>, // линейный байткод
pub consts: Vec<Value>,
pub refs: Vec<RefExpr>, // статические ссылки → регистрация в графе зависимостей
pub flags: FormulaFlags, // volatile, has_dynamic_refs (INDIRECT/OFFSET), array_result_possible, is_udf, is_legacy_cse
pub src_hash: u64, // для дедупликации общих формул
}
#[repr(u8)]
pub enum Op {
PushConst(u32), PushRef(u32), PushName(u32), PushStructRef(u32),
Add, Sub, Mul, Div, Pow, Concat, Eq, Ne, Lt, Le, Gt, Ge, Neg, Pos, Percent,
RangeOp, UnionOp, IntersectOp, SpillOp, ImplicitOp,
Call { func: u16, argc: u8 }, // встроенная функция по индексу в каталоге
CallLambda { argc: u8 }, CallUdf { id: u32, argc: u8 },
JumpIfFalse(u32), Jump(u32), // ленивые IF/IFS/IFERROR/SWITCH/AND/OR — короткое замыкание
LetBind(u16), LetLoad(u16), LetPop(u16),
MakeLambda { id: u32 },
Ret,
}4.7 Именованные диапазоны, таблицы, сводные, диаграммы
pub struct NamedItem {
pub id: NameId, pub name: Arc<str>, // имя: буквы/цифры/_/., не похоже на ссылку, уникально в области (без учёта регистра)
pub scope: NameScope, // Workbook | Sheet(SheetId)
pub def: NameDef, // Range(RefExpr) | Formula(FormulaId) | Const(Value) | Lambda(FormulaId, Vec<ParamDoc>) | TableRef(TableId)
pub comment: Option<Arc<str>>, pub hidden: bool, pub builtin: Option<BuiltinName>, // _FilterDatabase, Print_Area, Print_Titles
}
pub struct Table {
pub id: TableId, pub name: Arc<str>, pub sheet: SheetId, pub area: Rect,
pub columns: Vec<TableColumn>, // { name, id, calc_formula: Option<FormulaId>, totals: TotalsKind, dxf_ids }
pub header_row: bool, pub totals_row: bool, pub style: TableStyleRef,
pub style_opts: TableStyleOptions, // row_stripes, col_stripes, first_col, last_col
pub autofilter: Option<AutoFilter>, pub source: TableSource, // Range | Query(ConnectionId) | Flow(FlowId)
}
pub struct PivotCache {
pub id: CacheId, pub source: PivotSource, // Range(SheetId, Rect) | Table(TableId) | Connection(ConnectionId) | Flow(FlowId)
pub fields: Vec<CacheField>, // { name, kind: Text|Number|Date|Bool|Mixed, dict: Vec<Value>, stats: min/max/count, groups: Option<GroupSpec> }
pub records: Vec<Vec<u32>>, // column-major: records[field][row] = индекс в dict (dictionary encoding); числа — dict тоже (сортированный)
pub record_count: u32, pub refreshed_at: Timestamp, pub save_data: bool,
}
pub struct Pivot {
pub id: PivotId, pub name: Arc<str>, pub cache: CacheId, pub sheet: SheetId, pub location: Rect,
pub rows: Vec<PivotField>, pub cols: Vec<PivotField>, pub filters: Vec<PivotField>,
pub values: Vec<DataField>, // { field, name, aggregate: Agg, show_as: ShowAs, numfmt }
pub calc_fields: Vec<CalcField>, pub calc_items: Vec<CalcItem>,
pub layout: PivotLayout, // Compact|Outline|Tabular, subtotals, grand totals, blank rows, repeat labels
pub style: PivotStyleRef, pub options: PivotOptions, pub slicers: Vec<SlicerId>,
pub rendered: Option<PivotRender>, // кэш развёрнутой сетки для рендера/GETPIVOTDATA
}
pub struct ChartObject { // объект на листе; модель диаграммы — в meridian-chart
pub id: ObjectId, pub anchor: Anchor, // TwoCell { from, to, move_resize } | OneCell | Absolute
pub chart: meridian_chart::Chart, // типы, ряды (SeriesRef → RefExpr), оси, оформление
pub pivot_source: Option<PivotId>, // сводная диаграмма
}4.8 Условное форматирование и проверка данных
Оба — «диапазоны-правила»: правило хранит
sqref: Vec<Rect> и условие; индекс по R-дереву листа
даёт для ячейки список правил при рендере/вводе. Формульные правила
компилируются как обычные формулы с base = левая верхняя
ячейка sqref (относительные ссылки сдвигаются для каждой
ячейки — переиспользование общих формул).
pub struct CfRule {
pub id: u32, pub sqref: Vec<Rect>, pub priority: i32, pub stop_if_true: bool,
pub kind: CfKind, pub dxf: Option<DxfId>, // DxfId — дифференциальный стиль (только изменяемые атрибуты)
}
pub enum CfKind {
CellIs { op: CmpOp, f1: FormulaId, f2: Option<FormulaId> },
Expression(FormulaId),
ContainsText { kind: TextMatch, text: Arc<str> }, ContainsBlanks, NotContainsBlanks, ContainsErrors, NotContainsErrors,
TimePeriod(TimePeriod), DuplicateValues, UniqueValues,
Top { rank: u32, percent: bool, bottom: bool }, AboveAverage { below: bool, equal: bool, std_dev: u8 },
DataBar(DataBar), // min/max: Cfvo{Auto|Min|Max|Num|Percent|Percentile|Formula}, color, gradient, negative, axis, direction
ColorScale(Vec<(Cfvo, Color)>), // 2 или 3 точки
IconSet { set: IconSetId, thresholds: Vec<(Cfvo, CmpGte)>, reverse: bool, icons_only: bool, custom: Option<Vec<IconRef>> },
}
pub struct Validation {
pub sqref: Vec<Rect>, pub kind: ValidationKind, // Any | Whole | Decimal | List | Date | Time | TextLength | Custom(FormulaId) | Regex(Arc<str>)
pub op: CmpOp, pub f1: Option<FormulaId>, pub f2: Option<FormulaId>,
pub allow_blank: bool, pub show_dropdown: bool,
pub input: Option<(Arc<str>, Arc<str>)>, pub error: Option<(ErrorStyle, Arc<str>, Arc<str>)>,
}4.9 Прочие структуры листа
| Структура | Хранение |
|---|---|
Примечания (Note) |
HashMap<CellRef, Note { author, rich: RichText, shape: NoteShape, visible }> |
Обсуждения (Thread) |
Vec<Thread { id: Uuid, cell, status, posts: Vec<Post { author: PersonId, time, text, mentions }> }>;
привязка к ячейке сдвигается с операциями |
| Гиперссылки | `HashMap<CellRef, Hyperlink { target: Url |
| Спарклайны | Vec<SparklineGroup { kind, data: RefExpr, location: Rect, axis, markers, colors }> |
| Автофильтр | AutoFilter { area: Rect, columns: Vec<FilterColumn { col, criteria: FilterCriteria }>, sort_state }
+ hidden_rows_by_filter: RoaringBitmap |
| Объекты | Vec<SheetObject>: Chart,
Image { blob: MediaId, crop, alt },
Shape(meridian_scene::Shape),
Control { kind, linked_cell, input_range, script },
MathObject; z-порядок = порядок в векторе |
| Представление | SheetView { active: CellRef, selection: Vec<Rect>, scroll: (u32,u32), zoom: u16, freeze: Option<(u32,u32)>, split, show_gridlines, show_headers, show_zeros, rtl, view_mode } |
| Печать | PrintSetup { areas: Vec<Rect>, titles: (Option<RowSpan>, Option<ColSpan>), page: PageSetup, header_footer, breaks: Vec<Break>, options } |
4.10 Ограничения и «большой режим»
| Параметр | Стандартный режим (= Excel) | Большой режим (P1) |
|---|---|---|
| Строк | 2^20 = 1 048 576 | 2^31 − 1 (адресация u32, чанки те же; заголовки до 2
147 483 647) |
| Столбцов | 2^14 = 16 384 (A…XFD) | 2^14 (расширять не нужно: широкие данные — это Base/Parquet) |
| Длина текста ячейки | 32 767 символов | то же |
| Длина формулы | 8 192 символа, вложенность 64, аргументов 255 | то же |
| Листов | ограничено памятью | то же |
| Уровней отмены | не ограничено (журнал на диске при > 10 000 операций) | то же |
Большой режим включается на книгу при импорте файла с > 2^20 строк
(CSV, Parquet, БД) или вручную; книга помечается флагом
big_mode, экспорт в xlsx/ods обрезает с предупреждением
(§7). В большом режиме сетка та же, но импорт «ленивый»: чанки
материализуются из источника при первом обращении
(ChunkSource::Lazy(ParquetRowGroup)), а агрегатные функции
по целым столбцам идут потоком по источнику. Ограничение памяти: ~40
Б/ячейку → 10M заполненных ячеек ≈ 400 МБ; выше — предупреждение и
предложение открыть в Base.
4.11 Операции, undo и CRDT
Все изменения модели — значения перечисления Op;
Workbook::apply(op) -> Result<Inverse> выполняет и
возвращает обратную операцию; undo — стек обратных операций,
объединяемых в группы (Transaction) по действию
пользователя. Журнал операций пишется в history/ пакета
(автосохранение каждые 2 с — §6 библии) и в фазе 6 отображается в CRDT
Yrs: лист = YMap<cell_key → YMap{v,f,s}> +
YArray порядка строк/столбцов для корректного слияния
вставок.
pub enum Op {
SetCells { sheet: SheetId, cells: Vec<(CellRef, CellPatch)> }, // значения/формулы/стили, пакетно
InsertRows { sheet: SheetId, at: u32, count: u32 }, DeleteRows { sheet: SheetId, at: u32, count: u32 },
InsertCols { .. }, DeleteCols { .. },
InsertCells { sheet, rect, shift: ShiftDir }, DeleteCells { .. },
MoveRange { sheet, from: Rect, to: CellRef, to_sheet: SheetId }, // вырезать-вставить с переадресацией ссылок
SetAxis { sheet, axis: Axis, runs: Vec<AxisRun> }, Merge { sheet, rect }, Unmerge { .. },
SheetAdd { .. }, SheetDelete { .. }, SheetRename { .. }, SheetMove { .. }, SheetProps { .. },
NameSet { .. }, NameDelete { .. }, TableOp(TableOp), PivotOp(PivotOp), ChartOp(ChartOp), ObjectOp(ObjectOp),
CfOp(CfOp), ValidationOp(ValidationOp), FilterOp(FilterOp), SortRows { sheet, rect, permutation: Vec<u32> },
NoteOp(..), ThreadOp(..), HyperlinkOp(..), ProtectionOp(..), WorkbookProps(..),
Batch(Vec<Op>), // атомарная группа (одна запись undo)
}Правила слияния CRDT для таблиц (фаза 6, детали —
docs/03-architecture.md (раздел о совместной работе)):
ячейка — регистр «последний пишущий побеждает» по часам Лэмпорта (LWW) —
одновременная правка одной ячейки двумя людьми разрешается
детерминированно, а «проигравшее» значение показывается в истории;
вставка строк — позиция в YArray (строки получают
стабильные RowId, индексы вычисляются), поэтому
одновременные вставки не затирают друг друга; удаление строки с чужой
одновременной правкой в ней — правка сохраняется в «корзине конфликтов»
и показывается в панели.
5. Алгоритмы
Крейт meridian-calc (кроме §5.17–5.21, которые в
meridian-grid и оболочке).
5.1 Лексер формул
Вход — строка формулы в локали ввода
(FormulaLocale { arg_sep: ';' | ',', decimal: ',' | '.', array_row_sep, array_col_sep, func_names: &LocaleTable, bool_names });
хранение формулы — всегда канонический английский вид (,
аргументы, . десятичная, английские имена), поэтому лексер
применяется дважды: при вводе (локаль пользователя → канон) и при
отображении (канон → локаль).
Токены:
| Токен | Примеры | Примечание |
|---|---|---|
Number |
1, 1.5, 1e-3,
1,5 (ru) |
десятичный разделитель по локали; 1.5 в ru-локали — не
число, а ошибка/текст |
String |
"abc", "a""b" |
удвоенная кавычка — экранирование |
Bool |
TRUE, ИСТИНА |
по локали |
Error |
#N/A, #DIV/0!, #REF!,
#NAME?, #VALUE!, #NUM!,
#NULL!, #SPILL!, #CALC!,
#GETTING_DATA |
локализованные варианты (#Н/Д, #ДЕЛ/0!,
#ССЫЛКА!, #ИМЯ?, #ЗНАЧ!,
#ЧИСЛО!, #ПУСТО!, #ПЕРЕНОС!,
#ВЫЧИС!) |
CellRef |
A1, $A$1, A$1,
XFD1048576, R1C1, R[-1]C[2],
R, C |
A1 и R1C1 различаются режимом книги; в A1-режиме R1C1 —
имя |
ColRef / RowRef |
A:A, $A:$C, 1:1,
3:$7 |
целые столбцы/строки |
SheetPrefix |
Лист1!, 'Мой лист'!,
Лист1:Лист3!, '[Книга.xlsx]Лист'!,
[1]Лист! |
кавычки при пробелах/спецсимволах; '' —
экранирование |
Name |
Налог, _x, Таблица1,
A1. (запрещено), LAMBDA-параметры |
Unicode-буквы, _, \, .; не
может выглядеть как ссылка |
FuncName |
SUM(, СУММ(, MS.REGEX(,
_xlfn.XLOOKUP( |
имя + (;
_xlfn./_xlpm./_xlws./_xludf.
префиксы снимаются при импорте |
StructRef |
Таблица1[Сумма], [@Сумма],
Таблица1[[#Итоги];[Сумма]],
[@[Кол 1]:[Кол 3]] |
скобочная грамматика с экранированием '[,
'], '#, '@ |
Op |
+ - * / ^ & = <> < > <= >= % : # @ |
: — диапазон, пробел — пересечение,
,/; — объединение в скобках |
ArgSep, LParen, RParen,
LBrace, RBrace, ArrayRowSep,
ArrayColSep |
(, ),
{1;2}/{1,2} |
разделители массивов по локали ({1;2\3;4} в
ru-Excel!) |
Whitespace |
значим только как оператор пересечения между ссылками |
pub struct Lexer<'a> { src: &'a str, pos: usize, loc: &'a FormulaLocale, mode: RefMode /* A1 | R1C1 */ }
impl<'a> Lexer<'a> {
pub fn next_token(&mut self) -> Result<Token, LexError> {
self.skip_ws_recording_intersection(); // пробел между двумя ссылочными токенами → Token::Intersect
let c = self.peek()?;
match c {
'"' => self.string(),
'#' if self.at_error_literal() => self.error_literal(),
'#' => Ok(Token::Op(Op::Spill)),
'\'' | '[' => self.sheet_or_external_prefix_or_structref(),
c if c.is_ascii_digit() || (c == self.loc.decimal && self.peek_at(1).is_ascii_digit()) => self.number(),
c if is_name_start(c) => self.ident_or_ref(), // решает: FuncName (если далее '('), CellRef, ColRef, Bool, Name
_ => self.operator_or_punct(),
}
}
}Разбор идентификатора: регулярный вид
^\$?[A-Za-z]{1,3}\$?\d{1,7}$ с проверкой границ
(col ≤ XFD, row ≤ 1048576 или 2^31 в большом
режиме) → CellRef; в R1C1-режиме —
^R(\[-?\d+\]|\d+)?C(\[-?\d+\]|\d+)?$;
TRUE/FALSE и локальные формы → Bool; имя
функции — если следующий непробельный символ ( (таблица
имён по локали, затем английская, затем _xlfn.); иначе —
Name (имена не чувствительны к регистру, нормализуются NFC
+ upper для поиска).
5.2 Парсер (Pratt)
Грамматика Excel с приоритетами (от высшего к низшему):
| Приоритет | Операторы | Ассоциативность | Примечание |
|---|---|---|---|
| 8 | : (диапазон) |
лево | только над ссылочными выражениями |
| 7 | (пересечение), , (объединение — только
внутри скобок/аргумента) |
лево | |
| 6 | унарные - +, @ |
префикс | выше ^: -2^2 = 4 |
| 5 | % |
постфикс | 10%% допустимо |
| 4 | ^ |
лево (2^3^2 = 64) |
в отличие от математики |
| 3 | * / |
лево | |
| 2 | + - |
лево | |
| 1 | & |
лево | |
| 0 | = <> <
> <= >= |
лево | результат Bool |
| — | # (spill) |
постфикс к ссылке/имени | A1#, Имя# |
// kernel/crates/meridian-calc/src/parser.rs — Pratt-парсер / Pratt parser
pub struct Parser<'a> { lex: Peekable<Lexer<'a>>, ctx: &'a ParseCtx /* книга, текущий лист, имена, таблицы, функции */ }
impl<'a> Parser<'a> {
pub fn parse_formula(&mut self) -> Result<Expr, ParseError> {
let e = self.expr(0)?;
self.expect_eof()?; // лишние токены → "#NAME?" при вводе → диалог исправления (как Excel предлагает)
Ok(e)
}
/// Разбор выражения с минимальной силой связывания / parse expression with min binding power
fn expr(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
let mut lhs = self.prefix()?;
loop {
let tok = match self.lex.peek() { Some(t) => t, None => break };
// постфиксные / postfix: % и #
if let Some(bp) = postfix_bp(tok) {
if bp < min_bp { break; }
let t = self.lex.next().unwrap();
lhs = match t { Token::Op(Op::Percent) => Expr::Unary(UnOp::Percent, lhs.into()),
Token::Op(Op::Spill) => Expr::Spill(lhs.into()), _ => unreachable!() };
continue;
}
let Some((lbp, rbp)) = infix_bp(tok) else { break }; // (lbp, rbp): лево-ассоц. — rbp = lbp+1
if lbp < min_bp { break; }
let op = self.lex.next().unwrap();
let rhs = self.expr(rbp)?;
lhs = self.make_binary(op, lhs, rhs)?; // ':' требует ссылочных операндов: A1:B2, INDEX():INDEX(), Имя:Имя
}
Ok(lhs)
}
fn prefix(&mut self) -> Result<Expr, ParseError> {
match self.lex.next() {
Some(Token::Number(n)) => Ok(Expr::Num(n)),
Some(Token::String(s)) => Ok(Expr::Str(s)),
Some(Token::Bool(b)) => Ok(Expr::Bool(b)),
Some(Token::Error(e)) => Ok(Expr::Err(e)),
Some(Token::Op(Op::Minus)) => Ok(Expr::Unary(UnOp::Neg, self.expr(bp::UNARY)?.into())),
Some(Token::Op(Op::Plus)) => Ok(Expr::Unary(UnOp::Pos, self.expr(bp::UNARY)?.into())),
Some(Token::Op(Op::At)) => Ok(Expr::Implicit(self.expr(bp::UNARY)?.into())),
Some(Token::LParen) => { let e = self.union_list()?; self.expect(Token::RParen)?; Ok(e) } // (A1,B2) — объединение
Some(Token::LBrace) => self.array_const(),
Some(Token::FuncName(name)) => self.call(name), // LET/LAMBDA — особые: вводят области имён
Some(Token::CellRef(r)) | Some(Token::ColRef(r)) | Some(Token::RowRef(r)) => Ok(Expr::Ref(r)),
Some(Token::SheetPrefix(sh)) => self.ref_after_sheet(sh), // Лист!A1, Лист1:Лист3!A1:B2, Лист!Имя
Some(Token::StructRef(sr)) => Ok(Expr::StructRef(self.resolve_struct(sr)?)),
Some(Token::Name(n)) => Ok(self.resolve_name(n)?), // имя книги/листа/таблицы/LET-переменная/параметр λ, иначе Name(unresolved) → #NAME?
t => Err(ParseError::Unexpected(t)),
}
}
fn call(&mut self, name: FuncName) -> Result<Expr, ParseError> {
let mut args = Vec::new();
if !self.eat(Token::RParen) {
loop {
args.push(if self.peek_is_sep_or_rparen() { Expr::Missing } else { self.expr(0)? });
if self.eat(Token::ArgSep) { continue; }
self.expect(Token::RParen)?; break;
}
}
match name {
FuncName::Let => self.build_let(args), // пары (имя, значение)…, тело; имена видны в последующих парах
FuncName::Lambda => self.build_lambda(args), // параметры…, тело; при вызове LAMBDA(..)(x) — Call на Lambda
FuncName::Builtin(id) => { self.ctx.check_arity(id, args.len())?; Ok(Expr::Call { func: id, args }) }
FuncName::Udf(id) => Ok(Expr::Call { func: FuncId::Udf(id), args }),
FuncName::Unknown(s) => Ok(Expr::Call { func: FuncId::Unknown(s), args }), // → #NAME? при вычислении, но формула сохраняется
}
}
}Особенности:
- Локализация.
ArgSepберётся из локали; при вводе=SUM(A1,B1)в ru-локали парсер даёт ошибку «ожидалась;» с предложением автоисправления (как Excel). Имена функций ищутся в локальной таблице, затем в английской (пользователь может писать по-английски в любой локали). - Ссылки R1C1 разбираются относительно ячейки формулы
(
ParseCtx.base); хранение — всегда R1C1-подобное (относительные смещения), отображение — по режиму книги. - 3D-ссылки
Лист1:Лист3!A1разворачиваются вSheetRange::Span, порядок листов берётся из книги на момент вычисления (перемещение листов меняет состав — как в Excel). - Структурированные ссылки резолвятся в
StructRef { table, item: Data|Headers|Totals|All|ThisRow, cols: Option<(ColId, ColId)> }по идентификаторам столбцов, поэтому переименование столбца не ломает формулы (при отображении — текущее имя). - Внешние ссылки
[Книга.xlsx]Лист!A1→SheetRange::External(link, sheet); значения берутся из кэша связи. - Отображение (
Expr → String) — обратный проход с минимальной расстановкой скобок по тем же приоритетам, локализацией имён, разделителей и десятичной точки; форматирование «красиво» (переносы и отступы по вложенности) — опция редактора.
5.3 Компиляция в байткод
compile(expr, base):
walk(expr):
Num/Str/Bool/Err → PushConst(idx)
Ref r → refs.push(normalize_relative(r, base)); PushRef(idx) // относительные → смещения
Name n → PushName(id) (имя → при выполнении: Range/Const/Formula; LET-переменные → LetLoad(slot))
Binary(op,a,b) → walk(a); walk(b); Op
Unary → walk(x); Neg/Pos/Percent
Call{IF,[c,t,e]} → walk(c); JumpIfFalse(L1); walk(t); Jump(L2); L1: walk(e); L2: // ленивость
Call{AND/OR/IFS/SWITCH/IFERROR/IFNA/CHOOSE} → аналогично с короткими замыканиями
Call{f,args} → for a in args: walk(a) (Missing → PushConst(Missing)); Call{f, argc}
Let → for (n,v): walk(v); LetBind(slot) ; walk(body); LetPop(count)
Lambda → MakeLambda(id) (тело компилируется отдельно; захват LET-слотов по значению)
Spill(x) → walk(x); SpillOp Implicit(x) → walk(x); ImplicitOp
flags.volatile = любой вызов из VOLATILE = {NOW, TODAY, RAND, RANDBETWEEN, RANDARRAY, INFO (всегда),
CELL с аргументами среды ("filename", "address" без ссылки, "protect", "width")}
flags.has_dynamic_refs = INDIRECT | OFFSET | INDEX с диапазоном-результатом | ADDRESS→INDIRECT
Общие формулы: src_hash = hash(code, consts, refs) — одинаковые относительные формулы соседних ячеек делят CompiledFormula.
Аргументы функций имеют декларированные типы в каталоге
(ArgKind: Number | Text | Bool | Any | Range | RangeOrArray | Lambda | Criteria);
при Call интерпретатор приводит операнды по правилам Excel
(§5.8) и выполняет лифтинг по массивам: если функция
скалярная, а операнд — массив/диапазон в контексте динамических
массивов, функция применяется поэлементно (с расширением по правилам
broadcasting: 1×N против M×1 → M×N; несовпадающие размеры →
#N/A в лишних позициях).
5.4 Граф зависимостей
pub struct DependencyGraph {
/// прямые ссылки на ячейки: ячейка → формулы, которые её читают / cell → dependent formulas
cell_deps: HashMap<(SheetId, CellRef), SmallVec<[FormulaCell; 2]>>,
/// диапазонные слушатели: R-дерево по листу (прямоугольник → формулы) / range listeners per sheet (R-tree)
range_deps: HashMap<SheetId, RTree<RangeListener>>,
/// крупные диапазоны (≥ 1 чанка или целые строки/столбцы): по чанкам, чтобы не раздувать R-дерево
chunk_deps: HashMap<(SheetId, ChunkKey), SmallVec<[FormulaCell; 4]>>,
col_deps: HashMap<(SheetId, u32), Vec<FormulaCell>>, row_deps: HashMap<(SheetId, u32), Vec<FormulaCell>>,
/// имена и таблицы: имя → формулы, использующие имя / name → formulas
name_deps: HashMap<NameId, Vec<FormulaCell>>, table_deps: HashMap<TableId, Vec<FormulaCell>>,
/// динамические зависимости (INDIRECT/OFFSET): формула → фактически прочитанные диапазоны в прошлом расчёте
dynamic: HashMap<FormulaCell, Vec<ReadRange>>,
volatile: HashSet<FormulaCell>,
/// порядок последнего успешного расчёта (calc chain) — начальное приближение для топологии
calc_chain: Vec<FormulaCell>,
}Регистрация формулы: для каждого RefExpr после
разрешения относительно base: одиночная ячейка →
cell_deps; прямоугольник < 1 чанка →
range_deps (R-дерево rstar с
AABB<[u32;2]>); иначе → chunk_deps по
всем покрываемым чанкам (целые столбцы/строки →
col_deps/row_deps, чтобы SUM(A:A)
не регистрировалось в 4096 чанках). Запрос «кто зависит от (r,c)»:
объединение cell_deps[(r,c)], точечный запрос в R-дерево,
chunk_deps[chunk(r,c)], col_deps[c],
row_deps[r].
INDIRECT/OFFSET — в Excel волатильны
(пересчитываются всегда). У нас: формула с has_dynamic_refs
после каждого вычисления записывает фактически прочитанные диапазоны в
dynamic и регистрирует их как обычные слушатели (с пометкой
«динамический»); при следующем изменении этих ячеек она становится
грязной. Результат идентичен Excel (функции детерминированы), но без
пересчёта на каждое нажатие. Опция совместимости «считать
INDIRECT/OFFSET волатильными» — для книг, где это важно (например,
OFFSET над изменяющимся через скрипт диапазоном без
ссылок). Истинно волатильные (NOW, RAND, …) —
в volatile, грязны при каждом пересчёте.
5.5 Пересчёт
recalc(trigger: Changed{cells} | Full | FullRebuild):
1. dirty := ∅
для каждой изменённой ячейки c: dirty ∪= dependents(c) // BFS по графу, посещённое не повторяем
dirty ∪= volatile; при Full — все формулы; при FullRebuild — перестроить граф из всех формул
2. Разбиение на компоненты: union-find по рёбрам между грязными формулами → компоненты K1..Kn
(рёбра: f → g, если g читает ячейку, которую пишет f; для spill — весь spill-диапазон)
3. Для каждой компоненты параллельно (rayon, пул = ядра − 1; компоненты > 50k формул делятся уровнями):
order := топологическая сортировка (Kahn) по подграфу компоненты, начальное приближение — calc_chain
если найден цикл (остались вершины с in-degree > 0):
scc := Tarjan(остаток)
если iterative_calc выключен: все ячейки scc ← 0 + флаг Circular; сообщить в строку состояния
иначе: повторять вычисление scc в порядке calc_chain до max_iter или |Δ| < max_change для всех
для f in order: результат ← eval(f) → буфер результатов (не в сетку: другие потоки читают старые значения
только своих компонент; компоненты независимы по построению)
4. commit: буферы → сетка (одним проходом, с обновлением spill-диапазонов и типов NumKind), calc_chain := конкатенация порядков
5. уведомления: рендер (изменённые прямоугольники), условное форматирование, диаграммы, сводные (если «автообновление»),
скрипты (onRecalc)
Детали:
- Вычисление по требованию внутри компоненты.
eval(f)при чтении операнда, который грязен и ещё не вычислен (возможно при неполной топологии из-за динамических ссылок), вычисляет его рекурсивно (явный стек, глубина не ограничена рекурсией Rust); повторное попадание в стек = цикл. - Инкрементальность. Пересчитываются только грязные;
формула, чьё значение не изменилось (по
Value::eqс учётом точности), не помечает своих зависимых («отсечение по равенству»), что сильно уменьшает каскады. - Отмена. Длинный пересчёт идёт в фоне порциями, UI показывает «Вычисление: N %» и кнопку остановки; при остановке грязные остаются грязными, строка состояния показывает «Вычислить» (как Excel в ручном режиме).
- Профайлер. В режиме профилирования каждая формула
получает
elapsedиcalls; панель SH-16 показывает топ-50. - Вставка/удаление строк и столбцов (сдвиг ссылок).
shift_refs(sheet, axis, at, delta): через R-дерево/chunk_depsнаходятся все формулы со ссылками на лист, пересекающие или лежащие ниже/правееat; для каждой ссылки:- полностью ниже/правее → смещение координат (абсолютные и
относительные — одинаково; относительные смещения формул, чьи
baseтоже сдвинулись, не меняются); - пересекает диапазон вставки → расширяется (вставка внутри диапазона)
или остаётся (вставка на границе — как Excel: вставка строки под
последней строкой
SUM(A1:A5)не расширяет; опция «расширять диапазоны при вставке на границе» — P1, выключена по умолчанию для совместимости); - полностью внутри удаляемого →
#REF!(ссылка помечаетсяdeleted, формула показывает#REF!на месте ссылки); - частично пересекает удаляемое → сжимается. То же — для имён, таблиц,
сводных, диаграмм, УФ, проверок, объединений, спарклайнов, гиперссылок,
обсуждений, разрывов. Общие формулы: сдвигаются один раз на
CompiledFormula, если все ячейки-владельцы сдвинулись одинаково; иначе — расщепление.
- полностью ниже/правее → смещение координат (абсолютные и
относительные — одинаково; относительные смещения формул, чьи
- Перемещение (вырезать-вставить) диапазона: ссылки на перемещённые ячейки следуют за ними (как Excel), ссылки внутри перемещённых формул не меняются.
5.6 Динамические массивы и spill
after eval(f) at anchor A:
если результат — Array(rows×cols) и формула не legacy-CSE:
target := Rect(A, rows, cols); clip по границам листа (выход за край → #SPILL! «выходит за пределы листа»)
blockers := ячейки в target \ {A}, у которых непустое значение или формула, или которые входят в объединение,
или принадлежат структурированной таблице (в таблицах spill запрещён — как в Excel)
если blockers ≠ ∅: A ← Error(Spill, info{blockers}); очистить прошлый spill-диапазон A (если был)
иначе: записать значения в target; ячейки target \ {A} получают Extra::SpillChild{anchor: A} (без формулы,
не редактируются «как есть»: ввод в такую ячейку делает её блокером → A становится #SPILL!, при очистке — восстанавливается)
зарегистрировать f как «пишущую» в target → зависимые от target становятся грязными
если результат скаляр, а раньше был spill → очистить старый диапазон
A1#→Expr::Spill: ссылка на текущий spill-диапазон якоря (зависимость от размера — ячейка-якорь); еслиA1не якорь —#REF!.@expr(неявное пересечение): массив/диапазон → значение на пересечении со строкой/столбцом формулы, иначе#VALUE!. При импорте формул из старых xlsx (безcmметаданных) все ссылки, где старый Excel применял неявное пересечение, обёртываются в@— так же, как делает Excel 365; при экспорте@пишется как есть, а формулы с динамическими результатами — сt="array"+cm="1"(метаданныеXLDAPR).- Legacy CSE
{=...}: вычисляется в массивном контексте, результат пишется только в заданный диапазон (обрезка/#N/Aпри нехватке), отображается в фигурных скобках, редактируется только целиком. - Размер spill меняется динамически; при пересчёте, если новая область
пересекает другие spill-области, порядок разрешения — по порядку
вычисления; взаимные блокировки дают
#SPILL!обоим, что совпадает с Excel.
5.7 Ошибки
| Ошибка | ERROR.TYPE |
Когда | Причина в ErrorInfo (панель/подсказка) |
|---|---|---|---|
#NULL! |
1 | пересечение непересекающихся диапазонов
A1:A3 B1:B3 |
«Диапазоны не пересекаются» |
#DIV/0! |
2 | деление на 0/пустую; MOD(x;0); AVERAGE
пустых |
делитель и его адрес |
#VALUE! |
3 | текст вместо числа, несовместимые типы, массив там, где скаляр, несовместимые валюты | аргумент, его значение и ожидаемый тип |
#REF! |
4 | удалённая ссылка, выход OFFSET/INDEX за
лист, ссылка на удалённый лист, A1# не на якоре |
«Удалена строка 12 (операция в 14:03)» — из журнала |
#NAME? |
5 | неизвестная функция/имя, незакрытая кавычка, неверный разделитель | ближайшее похожее имя (расстояние Левенштейна ≤ 2) |
#NUM! |
6 | переполнение, SQRT(-1), несходимость
IRR/RATE, недопустимые параметры
распределений |
«Итерации не сошлись за 20 шагов» |
#N/A |
7 | NA(), не найдено в
VLOOKUP/MATCH/XLOOKUP,
недостающие элементы массива |
искомое значение |
#GETTING_DATA |
8 | ожидание асинхронной UDF/внешних данных | источник |
#SPILL! |
9 | §5.6 | список блокеров, «Сдвинуть» |
#CONNECT! |
10 | нет подключения к источнику богатых типов/веб-функций | адрес |
#BLOCKED! |
11 | функция заблокирована политикой (WEBSERVICE, UDF без разрешения) | политика |
#UNKNOWN! |
12 | неизвестный богатый тип из файла | — |
#FIELD! |
13 | поле богатого типа отсутствует | имя поля |
#CALC! |
14 | пустой массив (FILTER без совпадений и без
if_empty), вложенные массивы, рекурсия LAMBDA > 1024
уровней, LAMBDA без вызова |
подтип |
Распространение: ошибка в операнде → ошибка результата; при
нескольких — первая по порядку вычисления аргументов (слева направо),
как в Excel. Функции IFERROR, IFNA,
ISERROR, ISERR, ISNA,
ERROR.TYPE, AGGREGATE (опция игнорирования),
SUMIF-семейство (пропуск ошибок в диапазоне суммирования не
выполняется — как Excel) — единственные, кто «видит» ошибку как
значение. Ошибки в массиве — поэлементны.
5.8 Числовая точность и приведение типов
- Представление. IEEE 754
f64. Отображение — не более 15 значащих цифр (Общий), вычисления — полная точность. Сохранение в xlsx — через кратчайшее round-trip представление (алгоритм Ryū), не 15-значное, чтобы не терять биты. - Сравнение. Операторы
=,<>,<,<=,>,>=и функции поиска сравнивают числа черезapprox_eq(как LibreOfficertl::math::approxEqual): равны, если|a−b| ≤ 2^-48·max(|a|,|b|)(≈ 15,6 десятичных цифр). Это даёт=0.1+0.2=0.3 → TRUE. Опция книги «строгое сравнение IEEE» выключает. Excel поведение — ровное сравнение битов, но с «косметической» коррекцией (п. 3); на корпусе тестов различия с Excel зафиксированы как допустимые. - Косметическая коррекция вычитания (как Excel): если
последняя операция формулы —
+/-и|result| < 2^-50·max(|a|,|b|), результат → 0. Применяется только к финальной операции (=(0.1+0.2-0.3)в скобках Excel не корректирует; мы — тоже). - Округление.
ROUNDработает через десятичное представление (строка 17 значащих → округление → парсинг), чтобыROUND(2.675;2) = 2.68, как в Excel, а не 2.67 как в наивномf64. - Приведение типов (в скалярных операциях): текст →
число по правилам ввода локали книги (
"1 000,5","12%","03.10.2026"→ сериал,"1e3"), иначе#VALUE!;Bool→ 0/1;Empty→ 0 (""в текстовом контексте); число → текст по форматуОбщийпри&; вSUM-семействе текстовые и логические в диапазонах игнорируются, в аргументах-литералах — приводятся (как Excel:SUM("3")=3,SUM(A1)с"3"= 0). NumKindпереносится:дата + число → дата,дата − дата → длительность(отображается как число дней, формат по умолчанию «Общий» — как Excel, но типDurationпозволяет «Формат: длительность» в один клик),валюта × число → валюта,валюта(RUB) + валюта(USD) → #VALUE!при включённой проверке валют,процентсохраняется только для×/÷на скаляр. Правило: результат формулы получаетNumKindпо таблице операций, числовой формат ячейки при первом вводе формулы подбирается автоматически (как Excel:=A1+1над датой форматируется датой).
5.9 Даты и время
- Сериал: 1 = 1900-01-01 (система 1900), дробь — доля суток
(
0.5 = 12:00). Время хранится какf64, точность ~ 1 мкс для дат XXI века. - Ошибка 1900 года воспроизводится: сериал 60 =
«1900-02-29» (несуществующий день); для сериалов ≤ 60 преобразования
сериал ↔︎ дата используют календарь с високосным 1900 годом;
WEEKDAYдля дат до 1900-03-01 — как в Excel (т.е. неверно на 1 день, для совместимости). Сериал 0 = «1900-01-00» (отображение времени без даты). - Система 1904 (флаг книги, Mac Excel ≤ 2011): 0 = 1904-01-01, без ошибки високосного года; при переключении системы значения ячеек не меняются (даты «сдвигаются» на 1462 дня — как в Excel, с предупреждением).
- Диапазон дат: 1900-01-01 … 9999-12-31 (отрицательные сериалы →
#####; опция «отрицательные даты» — P2). - Разбор ввода: порядок полей по локали (
dd.mm.yyyyru,m/d/yyyyen-US), двузначный год (00–29 → 20xx, 30–99 → 19xx, настраивается), названия месяцев (полные/сокращённые, локаль + английский), ISO 8601 всегда,T-разделитель, времяh:mm,h:mm:ss,AM/PM,[h]> 24. Опция «не распознавать даты при вводе/импорте» (частая боль CSV). - Функции календаря: пролептический григорианский;
NETWORKDAYS.INTLс масками выходных"0000011";DATEDIF— по алгоритму Excel с его известной особенностью"MD"(может давать отрицательные/неверные значения для некоторых пар — воспроизводим документированно, с предупреждением в справке). - Локальное время:
NOW()/TODAY()— локальный часовой пояс системы; часовые пояса в значениях не хранятся (как Excel);MS.NOWUTC()— расширение.
5.10 Числовые форматы
5.10.1 Грамматика кода формата
format := section (';' section){0,3} // положительные ; отрицательные ; ноль ; текст
section := condition? color? locale? body
condition := '[' ('<' | '<=' | '>' | '>=' | '=' | '<>') number ']'
color := '[' ('Black'|'Blue'|'Cyan'|'Green'|'Magenta'|'Red'|'White'|'Yellow'|'Color' 1..56) ']' // локализованные имена тоже
locale := '[$' currency_symbol? ('-' LCID_hex)? ']' // [$₽-419], [$-F800] (системная длинная дата), [$-x-sysdate]
body := token*
token := '0' | '#' | '?' | '.' | ',' | '%' | 'E+' | 'E-' | '/' | '"text"' | '\' char | '_' char | '*' char
| 'General' | '@' | date_token | elapsed_token | literal_safe_char (' ', '-', '+', '(', ')', ':', '$', …)
date_token := 'yy' | 'yyyy' | 'm' | 'mm' | 'mmm' | 'mmmm' | 'mmmmm' | 'd' | 'dd' | 'ddd' | 'dddd' | 'h' | 'hh' | 's' | 'ss'
| 'AM/PM' | 'A/P' | 'am/pm' | '.0' | '.00' | '.000' (доли секунды) | 'e' (эра) | 'g','gg','ggg' (яп. эра) | 'b' (буддийский год)
elapsed_token := '[h]' | '[hh]' | '[m]' | '[mm]' | '[s]' | '[ss]'
Компиляция (meridian-numfmt): код →
CompiledFormat { sections: [Section; ≤4], kind_hint: NumKind },
где секция — список инструкций (Digit{zero,space,hash},
Group, Decimal, Percent,
Exponent, Fraction{den_digits|fixed},
Literal, Fill(char), Pad(char),
Text, DateTime(part),
Elapsed(unit), General) + условие + цвет +
локаль. Неоднозначность m (месяц/минута) разрешается по
контексту: после h/hh или перед
s/ss — минуты. Кэш скомпилированных форматов
по коду.
5.10.2 Выбор секции и рендер
render(value, fmt, locale):
если value — Text: секция 4 (если есть) с '@', иначе текст как есть
если Bool/Error: как в General
секция := по условиям [..] если заданы; иначе: 1 секция — все числа; 2 — (≥0, <0 с автознаком '-' отбрасываемым);
3 — (>0, <0, =0); 4 — (+, −, 0, текст)
x := |value| если секция отрицательных и условие не задано явно (знак рисует сама секция: "(#,##0)" или "-")
если секция дата/время: разложить сериал (с ошибкой 1900 г., AM/PM, elapsed [h] = floor(x·24) и т.д.), подставить части,
локализованные названия месяцев/дней по [$-LCID] или локали книги, доли секунды с округлением
иначе числовая секция:
масштаб: ×100 за каждый '%', ÷1000 за каждую запятую в конце целой части (`#,##0,` → тысячи), E+/E- → мантисса/экспонента
дробь '# ?/?': подобрать знаменатель (цепные дроби, ограничение по числу '?' или фиксированный знаменатель)
округлить до числа десятичных плейсхолдеров (0/#/?), переполнение разрядов — расширить целую часть
собрать строку: целая часть с группировкой по ',' (разделители из locale), литералы, '_x' → пробел ширины x,
'*x' → заполнитель (расширяется до ширины ячейки при рендере), '?' → пробел-цифра
результат: DisplayString { text, color: Option<Color>, fill: Option<(pos, char)>, align_hint }
5.10.3 Локализация
Разделители группы/десятичной — из локали книги (не ОС),
переопределяются [$-LCID] и настройками Параметров;
названия месяцев/дней — ICU4X; валюты из [$€-407]
отображаются как записано. При экспорте в xlsx коды пишутся без
изменений; встроенные форматы (numFmtId 0–49 и локальные
164+) отображаются на таблицу Excel.
5.10.4 Формат «Общий»
Подбор представления под ширину столбца (как Excel): целое → как
есть; если не влезает — десятичные обрезаются до влезающих; если целая
часть не влезает (или число ≥ 1e11 / < 1e-? при 11 символах) →
экспоненциальный #.##E+## с уменьшением мантиссы; минимум
1E+10-подобное; если и это не влезает → #####.
Текст не обрезается (переливается в соседние пустые ячейки вправо, если
выравнивание общее/левое). Отрицательные числа при ##### —
тоже решётки.
5.11 Автозаполнение
5.11.1 Распознавание серии (маркер заполнения, Прогрессия → «автозаполнение»)
detect_series(cells: &[CellSlot]) -> Series:
если все формулы → Series::CopyFormulas (относительные ссылки сдвигаются по позиции)
если все числа (NumKind::Plain):
1 ячейка → Copy (с Ctrl → Linear{step: 1})
≥2 → Linear{step: by least squares} если точки лежат на прямой (|resid| < 1e-9·scale), иначе
LinearTrend (МНК по всем точкам — как Excel «Прогрессия с трендом»)
если все даты/время:
шаг := разность сериалов; если разности равны целым дням → Date{Day, step}
если даты — одинаковое число месяца и шаг в месяцах равен → Date{Month, step}; то же для Year
если одинаковый день/месяц, шаг в годах → Date{Year, step}; время → Linear по сериалу
если все тексты:
t = разобрать как <prefix><число><suffix> (число — последняя группа цифр, ведущие нули сохраняются по ширине):
одинаковые prefix/suffix и числа образуют арифметическую прогрессию → TextNumber{step, width}
иначе искать в списках: дни недели (полные/короткие, локаль + en), месяцы, кварталы ("Кв1", "Q1", "1-й квартал"),
пользовательские списки (SH-25): элемент найден → ListSeries{list, start_index, step по позициям, циклично}
иначе → Copy
смешанные типы → Copy (с чередованием блока: Excel повторяет блок как шаблон — воспроизводим: Pattern{block})
apply: для i-й новой ячейки — значение по серии; стиль копируется циклически из исходного блока («только форматы» / «без форматов» — опции);
при заполнении вверх/влево — шаг отрицателен
Двойной клик по маркеру: заполнение вниз до последней непустой строки соседних столбцов (слева, затем справа), с остановкой на первой пустой в целевом столбце (как Excel).
5.11.2 Прогрессия (диалог SH-51)
Арифметическая (a + i·step), геометрическая
(a · step^i), даты (единицы день/рабочий день/месяц/год — с
нормализацией конца месяца: 31.01 + 1 мес = 28.02), «автозаполнение» (п.
5.11.1), «тренд» (МНК линейный/экспоненциальный по выделенным, заменяет
значения), предел.
5.11.3 Заполнение по образцу (Flash Fill, P1)
Синтез программ в DSL над строками (упрощённый FlashFill/PROSE):
примеры — пары (входная строка соседних столбцов, выход). DSL:
Concat(atom…), где
atom ∈ { Const(s), SubStr(col, pos1, pos2), Case(atom, Upper|Lower|Proper), NumFmt(atom, fmt), DatePart(col, part) },
pos ∈ { Abs(k), RegexMatch(token_regex, occurrence, start|end) }
с токенами (цифры, буквы, пробелы, знаки, заглавные). Алгоритм: для
каждого примера строится граф всех способов получить каждую подстроку
выхода из входа (DAG путей); программы — пересечение DAG-ов по примерам;
ранжирование: меньше констант, больше регулярных токенов, короче.
Применяется к остальным строкам; результат показывается серым
(предпросмотр), подтверждается Enter; расхождения с новыми примерами
переобучают. Ограничение: ≤ 10 входных столбцов, строки ≤ 256 символов,
время синтеза ≤ 200 мс (иначе — «не удалось распознать образец»).
5.12 Сортировка
sort(range, keys: [SortKey{col|row, by: Value|CellColor|FontColor|Icon, order: Asc|Desc|CustomList(id)|ColorOnTop(c)}],
opts: {case_sensitive, orientation, has_header, locale})
1. извлечь строки (или столбцы) как массив индексов perm = [0..n)
2. компаратор по ключам (последовательно, первый ненулевой):
Value: ранг типа Number < Text < Bool < Error < Empty (Empty всегда последним при любом порядке — как Excel);
числа — по значению (approx_eq → 0); текст — ICU Collator(locale, strength = Secondary или Tertiary при case_sensitive,
numeric=on при опции «естественная сортировка 'a2' < 'a10'» — P1); Bool: FALSE < TRUE; ошибки — порядок ERROR.TYPE
CellColor/FontColor/Icon: заданный цвет/значок наверх (или вниз); остальные — сохраняют порядок
CustomList: позиция в списке (нет в списке → после, по значению)
3. стабильная сортировка perm (merge sort / `slice::sort_by` — стабильный в Rust); 1M строк ≈ 0,3–0,6 с (ключи
предвычислены в массив `SortKeyVec` — числа как f64, текст как ICU sort key `Vec<u8>` для memcmp)
4. применить Op::SortRows{permutation}: перестановка слотов построчно во всех столбцах диапазона (значения, стили, extra),
объединения внутри диапазона → ошибка «нельзя сортировать объединённые ячейки разного размера» (как Excel);
формулы: относительные ссылки *внутри* строки сохраняются (строка переезжает целиком), ссылки на другие строки диапазона —
не корректируются (как Excel; предупреждение в справке)
5. строки, скрытые вручную, сортируются вместе с остальными; строки, скрытые автофильтром, в сортировке не участвуют и остаются
на месте (поведение Excel)
Сортировка по столбцам (orientation = Columns) — симметрично. Undo — обратная перестановка.
5.13 Автофильтр и расширенный фильтр
apply_autofilter(af):
hidden := RoaringBitmap::new()
для каждой строки r в области (кроме заголовка), для каждого столбца с критерием:
v := значение (r, col) → если !matches(criteria, v): hidden.insert(r); break
criteria:
Values(set) — множество выбранных значений: числа и текст сравниваются по значению, даты — по дереву год/месяц/день
(DateTree{years→months→days} раскрывается в предикат по сериалу), как в Excel
Custom(op1, v1, and|or, op2, v2) — операторы Eq/Ne/Gt/Ge/Lt/Le/BeginsWith/EndsWith/Contains/NotContains с `* ? ~`
Top{n, percent, bottom} — предвычислить порог по столбцу
Color(cell|font, color) / Icon(set, id) — по вычисленному стилю (с учётом УФ)
Dynamic(period) — Today/Yesterday/ThisWeek/LastMonth/NextQuarter/YearToDate/… относительно NOW()
AboveAverage / BelowAverage
rows.hidden_by_filter := hidden; перерисовать; строка состояния «Найдено N из M»
Расширенный фильтр: диапазон условий → для каждой строки условий (И
между столбцами) → ИЛИ между строками; ячейка условия — значение с
оператором (>100, <>Москва,
М*) или формула-критерий (заголовок пустой/не совпадает с
полем; формула вычисляется относительно первой строки данных,
относительные ссылки сдвигаются по строкам — как Excel). Копирование в
другое место — Op::SetCells, «только уникальные» — хэш по
кортежу строки.
5.14 Сводные таблицы
build_cache(source):
для каждого столбца: dict := уникальные значения (HashMap<Value, u32>, отсортированный после сбора), records[f][i] = dict idx
тип поля: все числа → Number; все даты → Date (создать иерархию Годы/Кварталы/Месяцы как групповые поля); иначе Text/Mixed
(1M записей × 10 полей ≈ 40 МБ индексов; dict хранит Value)
refresh_pivot(p):
// 1. фильтры страницы и срезы → bitmap допустимых записей
// 2. группировка: ключ строки = кортеж индексов полей строк (с учётом группировок дат/чисел: idx → group idx),
// ключ столбца — аналогично
agg: HashMap<(RowKey, ColKey), Vec<Accumulator>> // по одному аккумулятору на поле значений
для i in records (параллельно по блокам, с последующим слиянием): agg[(rk(i), ck(i))].update(values…)
Accumulator: Sum{f64}, Count{u64}, Avg{sum,n}, Max, Min, Product, CountNums, StdDev/Var{Welford: n, mean, m2}, DistinctCount{HashSet<u32>}
// 3. деревья заголовков: RowTree из всех встреченных ключей, сортировка по уровням (по подписи ICU / по значению поля данных /
// ручная; «показывать элементы без данных» — из dict), фильтры по подписям/значениям/первые N на каждом уровне
// 4. промежуточные итоги: для каждого узла дерева — агрегат по поддереву (Sum/Count — сложение детей; Avg/StdDev/Distinct —
// пересчёт по записям узла: храним для узла bitmap записей или пересчитываем вторым проходом)
// 5. «показать как»: второй проход по сетке: % от итогов (деление на итог строки/столбца/общий/родителя), отличие (минус базовый
// элемент), нарастающий итог (префиксная сумма по базовому полю), ранг (сортировка в группе), индекс ((v·total)/(row_total·col_total))
// 6. вычисляемые поля: формула над агрегатами (Sum(Выручка) - Sum(Себестоимость)) — вычисляется на каждом узле после агрегации
// (семантика Excel: не по записям); вычисляемые элементы — формула над элементами одного поля, применяется на уровне ключей
// 7. render: PivotRender { grid: Vec<Vec<Value>>, styles (элементы стиля: header/subtotal/grandtotal/rowstripe), outline buttons,
// layout (compact: один столбец с отступами; outline/tabular: по столбцу на уровень), пустые строки, повтор подписей }
// → записывается в лист как значения (не формулы) с меткой PivotArea для контекстных команд и GETPIVOTDATA
Детализация (drill-through): bitmap записей узла → новый лист с
исходными строками. Срез: список dict поля с состоянием;
изменение → bitmap → refresh всех сводных на кэше. Временная шкала —
срез по датам с уровнем (годы/кварталы/месяцы/дни). Сводная диаграмма
берёт PivotRender как источник рядов (категории = строки,
ряды = столбцы).
5.15 Подбор параметра
Метод секущих (как в Excel) с защитой бисекцией:
goal_seek(target_cell, goal, var_cell, max_iter = 100, tol = 1e-3 /* «Относительная погрешность» из параметров книги */):
f(x) := { var_cell ← x; recalc(partial, только компонента target_cell); target_cell − goal }
x0 := текущее значение (или 0), x1 := x0 + (x0 == 0 ? 0.01 : x0·0.01)
f0, f1 := f(x0), f(x1)
for it in 0..max_iter:
if |f1| ≤ tol: return Ok(x1)
if f1 == f0: x1 += perturb; f1 = f(x1); continue // плато
x2 := x1 − f1·(x1 − x0)/(f1 − f0) // секущая
if !finite(x2) || |x2 − x1| > 1e6·max(1,|x1|): x2 := bisect_fallback(bracket) // при наличии скобки знака
x0, f0, x1, f1 := x1, f1, x2, f(x2)
report_progress(it, x1, f1 + goal) // окно SH-37: «Шаг/Пауза»
return Err(NoConvergence { best: x1 }) // «Решение не найдено», значение оставляется, пользователь выбирает OK/Отмена
Все пробные значения выполняются внутри одной транзакции undo; «Отмена» возвращает исходное.
5.16 Поиск решения (Solver)
Модель:
SolverModel { objective: CellRef, goal: Max|Min|Value(f64), vars: Vec<Rect>, constraints: Vec<Constraint{lhs: Rect, op: Le|Eq|Ge|Int|Bin|Dif, rhs: Expr}>, nonneg: bool, method, options }.
Сохраняется в скрытых именах листа (solver_*, совместимо с
Excel Solver) и в settings.xml.
| Метод | Когда | Алгоритм |
|---|---|---|
| Симплекс LP | целевая и ограничения линейны по переменным (проверка: численная
линейность — вторые разности ≈ 0 на случайных точках, плюс анализ AST:
только + − ×const SUMPRODUCT SUM) |
извлечение матрицы A, c, b конечными разностями (точно для
линейных); двухфазный симплекс на разреженных матрицах с правилом Блэнда
против зацикливания, масштабирование; целочисленные/бинарные — ветви и
границы (глубина сначала, отсечение по текущему рекорду, лимит по
времени), dif (все разные) — через бинарные переменные
назначения. Отчёты: результаты, устойчивость (двойственные цены,
приведённая стоимость, диапазоны), пределы |
| ОПГ-нелинейный (GRG) | гладкие нелинейные | обобщённый приведённый градиент: разбиение переменных на
базисные/небазисные по активным ограничениям, градиент конечными
разностями (прямыми или центральными — опция), квазиньютоновское
направление (BFGS) или сопряжённые градиенты, линейный поиск с
возвратом; сходимость: относительное изменение <
convergence (1e-4) за 5 итераций; мультистарт (случайные
точки в границах переменных, кластеризация) для глобальности |
| Эволюционный | негладкие (IF, LOOKUP, ABS,
ROUND в модели) |
дифференциальная эволюция (DE/rand/1/bin, популяция 100, F=0.8,
CR=0.9) + локальное уточнение лучших GRG; штрафы за нарушение
ограничений; критерий остановки: без улучшения >
mutation_rate-независимого числа поколений, лимит времени,
точность |
Каждая оценка целевой = частичный пересчёт компонент графа,
затронутых переменными (кэшируется подграф); для LP оценка нужна только
для извлечения матрицы. Параллелизм: популяции DE и мультистарт GRG — на
копиях книги (Workbook::fork_calc_state), по потокам.
Ограничение времени по умолчанию 100 с, итераций 100 (LP — не
ограничивается), «Показывать результаты итераций» — пошаговый режим.
5.17 Виртуализация отрисовки сетки
5.17.1 Единицы и размеры
Ширина столбца хранится в «символах» (как Excel: число символов
0 шрифта по умолчанию + 5 px поля) и переводится в пиксели:
px = trunc((chars·max_digit_width + 5) / max_digit_width · 256) / 256 · max_digit_width
(формула OOXML); высота строки — в пунктах. Для рендера всё переводится
в DIP с учётом масштаба и HiDPI.
5.17.2 Алгоритм
GridView::paint(viewport):
// 1. Видимые строки/столбцы: по префиксным суммам размеров (Fenwick в AxisProps, скрытые = 0) → бинарный поиск
first_row := axis_rows.index_at(scroll_y), last_row := axis_rows.index_at(scroll_y + viewport.h)
// Автовысота: строки с переносом измеряются лениво — только попавшие в окно ± 2 экрана; остальные — по оценке (строки
// по умолчанию); при измерении префиксные суммы корректируются (ползунок немного «дышит» — допустимо, как в браузерах)
// 2. Тайлы: сетка делится на тайлы 512×512 px (в DIP·scale); для видимых тайлов — Skia Picture (список отображения) в кэше
// по ключу (tile, zoom, generation листа); инвалидация по изменённым прямоугольникам (dirty rects от Op), а не всего тайла
// 3. Ячейка рисуется: заливка (стиль/УФ/таблица) → границы (с разрешением конфликтов соседних границ: толще побеждает,
// затем правая/нижняя) → содержимое: число/дата по формату (DisplayString кэш по (value, numfmt, width)) с выравниванием
// и `#####` при нехватке; текст — shaped layout из кэша (ключ: текст, шрифт, ширина при переносе) через meridian text-layout;
// переливание текста в пустые соседи; гистограммы/значки/шкалы УФ; спарклайны; индикаторы (примечание, ошибка, список)
// 4. Объединения: рисуются как одна ячейка (рект из MergeIndex), накрывают сетку
// 5. Поверх: линии сетки (цвет темы, скрываются под заливкой — как Excel), закреплённые области (отдельные тайловые слои),
// выделение (полупрозрачная заливка цвета приложения + рамка), рамки ссылок, бегущая рамка (анимация 120 мс шаг), объекты
// 6. Заголовки строк/столбцов — отдельные слои; при 2^31 строк — ширина заголовка по максимальному видимому номеру
// Прокрутка: смещение слоя текстуры без перерисовки (субпиксельная, плавная на колесе/трекпаде); при пересечении границы тайла —
// отрисовка новых тайлов в фоновом потоке (упреждающе ±1 экран); цель — 120 FPS на 4K при прокрутке 1M строк
Полоса прокрутки: диапазон = используемая область + 1 экран (как
Excel), в большом режиме — логарифмическая «грубая» прокрутка с точным
позиционированием по полю имени. Масштаб: Ctrl+колесо с
центром в курсоре; размеры шрифтов масштабируются, толщины линий — ≥ 1
px.
5.18 Выделение и навигация
- Модель выделения:
Selection { ranges: Vec<Rect>, active: CellRef, anchor: CellRef, mode: Cells|Rows|Cols|All }; Shift — расширение от якоря, Ctrl — добавление диапазона, Shift+F8 — режим добавления; выделение целых строк/столбцов — специальныеRectсAll. - Ctrl+стрелки (переход по блокам), пример для
«вниз»:
Shift+Ctrl+стрелка — расширяет выделение по тому же алгоритму. End-режим (jump_down(r, c): cur := nonempty(r,c); next := nonempty(r+1,c) если cur && next: идти вниз, пока nonempty(r+1,c) — «конец блока» (пропуск по словам битовых карт чанков: находим первый нулевой бит в столбце c начиная с r+1 — O(число слов), пустые чанки пропускаются по col_nonempty) иначе: идти вниз до первой непустой (первый единичный бит в столбце c после r+1); если нет — последняя строка листа скрытые строки пропускаются; объединения считаются непустыми целикомEnd, затем стрелка) — то же. Ctrl+Home— A1 (или левый верхний угол незакреплённой области),Ctrl+End— последняя ячейка используемой области,Ctrl+A— текущая область (прямоугольник непустых вокруг курсора, расширяется до данных; второе нажатие — весь лист),Ctrl+Shift+*— текущая область,Ctrl+/— текущий массив,Ctrl+[/]— влияющие/зависимые,Ctrl+Пробел/Shift+Пробел— столбец/строка,Ctrl+.— углы выделения,Tabв выделении — обход по ячейкам выделения.- Клавиатурный ввод адреса в поле имени,
F5— переход; «Перейти назад» (F5, Enter — Excel-поведение),Ctrl+G. - Клик по заголовку с Shift/Ctrl, двойной клик по границе ячейки — переход к краю блока (как Excel).
5.19 Копирование и вставка
- Внутренний буфер:
ClipboardPayload { source: Rect (на листе SheetId книги), cells: Vec<CellSlotFull> (значения, формулы в R1C1-относительном виде, стили, extra), merges, col_widths, row_heights, mode: Copy|Cut, origin_doc: Uuid }+ системные форматы:text/plain(TSV, значения как отображены),text/html(таблица с инлайн-стилями иdata-sheets-*-подобными атрибутами для round-trip формул),text/csv,application/vnd.meridian.sheets+cbor(полный payload),image/png(при «копировать как рисунок»),text/rtf(Windows). - Вставка (
Ctrl+V) из своего payload: относительные ссылки формул пересчитываются от новой базы (R1C1-смещения не меняются — это и есть «адаптация»); абсолютные — не меняются; ссылки на другие листы — сохраняются; при вставке в другую книгу ссылки на листы источника превращаются во внешние ссылки[Книга]Лист!A1(как Excel) или в значения — по опции. Вырезание:Op::MoveRange— ссылки, указывающие на вырезанный диапазон, перенаправляются на новое место; объединения переносятся. - Многократная вставка: один диапазон в большее выделение — тайлинг, если выделение кратно; одна ячейка — во все.
- Специальная вставка (SH-59): фильтр атрибутов payload + операция
(
target = target op sourceдля чисел, формулы →=(старая)+(новая)как Excel), транспонирование (ссылки в формулах транспонируются вместе — Excel не умеет корректно, мы — с предупреждением), «вставить связь» → формулы=Лист!A1, «ширины столбцов», «проверка», «все условные форматы объединить». - Из внешних источников: HTML-таблица → разбор с сохранением жирности/цвета/объединений; текст → мастер «текст по столбцам» с запоминанием разделителя; изображения → объект.
- Параметры вставки (SH-59b) — кнопка после вставки с переключением
вариантов без повторного
Ctrl+V.
5.20 Поиск
find(query, opts{scope: Sheet|Workbook, in: Formulas|Values|Notes|Threads, by: Rows|Cols, case, whole, mode: Plain|Wildcard|Regex, format: Option<StyleMatch>}):
matcher := скомпилировать: Plain → memchr/Boyer-Moore по NFC-нормализованной строке с опциональным case-folding (ICU);
Wildcard → перевести `* ? ~*` в regex; Regex → `regex` crate (Unicode, таймаут на бэктрекинг не нужен — RE2-семантика)
обход: по чанкам в порядке by (строки — чанки слева направо по ряду чанков, внутри — построчно; столбцы — по столбцам чанков),
начиная с ячейки после активной, циклически; в Values — сопоставляется DisplayString (как видит пользователь), в Formulas —
текст формулы в локали пользователя (или значение для констант); пропуск скрытых — опция
«Найти всё» → список (лист, адрес, значение, формула) в SH-15 с живой подсветкой; замена → Op::SetCells пакетом в одной транзакции,
для формул замена в тексте формулы с перепарсингом (ошибка парсинга → пропустить и сообщить)
производительность: 1M непустых ячеек — < 150 мс на Plain (без аллокаций, параллельно по чанкам с сохранением порядка результатов)
5.21 Импорт CSV
import_csv(path, opts: Option<CsvOptions>) -> (Sheet | Flow):
1. Чтение первых 64 КБ → кодировка: BOM (UTF-8/16LE/16BE/32) → иначе валидный UTF-8 → иначе статистика байтов:
cp1251 vs koi8-r vs cp866 vs iso-8859-1/-5 vs windows-1252 — по частотам букв и биграмм (таблицы для ru/en/de/fr);
неуверенность < 0.8 → показать мастер SH-40 с выбором (предпросмотр с кандидатами)
2. Разделитель: кандидаты `, ; \t |` и ' ' (только при фиксированной ширине нет); для каждого — число вхождений по строкам
(учитывая кавычки); побеждает тот, у которого дисперсия числа полей минимальна и число полей > 1; ограничитель — `"` (если
в строках есть `"`), иначе `'`; десятичный разделитель — по доле `\d,\d` против `\d\.\d` в полях (при `;`-разделителе чаще `,`)
3. Типы столбцов: выборка 1000 строк (равномерно по файлу): для каждого столбца — доля значений, разбираемых как число
(с учётом тысячных пробелов/NBSP, валют, %, скобок отрицательных), дата (по локали и ISO), bool, пусто; порог 95 % → тип;
ведущие нули (`007`, телефоны, ИНН) → текст (опция «сохранить ведущие нули» по умолчанию включена); длинные цифровые
строки > 15 цифр → текст (предупреждение); столбцы «общий» — автоопределение по ячейке
4. Разбор: потоковый парсер по RFC 4180 с расширениями (CR/LF/CRLF, многострочные поля в кавычках, `""`), SIMD-поиск разделителей
(`memchr`), порциями 8 МБ в пул потоков: разбор → ChunkBuilder (плотные чанки) → сборка листа; прогресс и отмена; строки > 2^20
→ предложение «большой режим» или потока данных
5. Результат: лист с заголовком (если обнаружен: первая строка — текст, остальные — типизированы), автоширина столбцов по выборке,
типы ячеек по столбцу; журнал импорта (сколько строк, ошибок разбора, пример проблемных строк)
Производительность: 1 ГБ CSV (10M строк × 10 столбцов) — < 20 с на 8 ядрах в большом режиме, память ≈ 1,5× данных
Экспорт CSV: кодировка (UTF-8 с BOM по умолчанию для Excel-совместимости; опция без BOM), разделитель, ограничитель всегда при необходимости, формат чисел «как отображено» или «сырые», даты ISO или по формату, только видимые строки — опция.
5.22 Экспорт
| Цель | Что | Как |
|---|---|---|
| лист/книга/выделение по параметрам печати | Meridian Print: список отображения тех же тайлов (вектор, текст как текст), теги PDF/UA (таблица, заголовки строк из «сквозных»), PDF/A-2b, закладки по листам | |
| HTML | таблица с инлайн-CSS, объединения, форматы, диаграммы как SVG; опция «интерактивная» (фиксированная шапка, сортировка JS) | шаблон, стили MDS не нужны |
| JSON | таблица/диапазон → массив объектов по заголовкам; типы (число/строка/bool/null/ISO-дата) | потоковый писатель |
| Parquet/Arrow | столбцы с типами по NumKind (Date32/Timestamp/Decimal для валют/Utf8/Boolean) | arrow-rs, сжатие zstd |
| Изображение | диаграмма/диапазон как PNG/SVG (с масштабом, прозрачностью) | Meridian Render |
| Markdown | таблица GFM (выравнивание по типу), text/markdown в
буфере |
— |
5.23 Каталог функций
Каталог —
meridian-calc/src/functions/<category>.rs,
регистрация через макрос func! с сигнатурой, типами
аргументов, флагами (волатильность, массивный результат, ленивые
аргументы) и локализованными именами из locale/*.toml.
Сводка: P0 — 317 функций (+ 38 алиасов совместимости),
P1 — 147, P2 — 16; всего 518 имён.
Русские имена даны для самых употребимых; полная таблица — в
locale/ru.toml.
Математические и тригонометрические (81)
| Фаза | Функции |
|---|---|
| P0 (57) | ABS (ABS), ACOS, ACOSH, ASIN, ASINH, ATAN, ATAN2, ATANH, AGGREGATE (АГРЕГАТ), CEILING (ОКРВВЕРХ), CEILING.MATH (ОКРВВЕРХ.МАТ), CEILING.PRECISE, COMBIN (ЧИСЛКОМБ), COS, COSH, DEGREES (ГРАДУСЫ), EVEN (ЧЁТН), EXP, FACT (ФАКТР), FLOOR (ОКРВНИЗ), FLOOR.MATH (ОКРВНИЗ.МАТ), FLOOR.PRECISE, GCD (НОД), INT (ЦЕЛОЕ), LCM (НОК), LN, LOG, LOG10, MMULT (МУМНОЖ), MOD (ОСТАТ), MROUND (ОКРУГЛТ), ODD (НЕЧЁТ), PI (ПИ), POWER (СТЕПЕНЬ), PRODUCT (ПРОИЗВЕД), QUOTIENT (ЧАСТНОЕ), RADIANS (РАДИАНЫ), RAND (СЛЧИС), RANDARRAY (СЛУЧМАССИВ), RANDBETWEEN (СЛУЧМЕЖДУ), ROUND (ОКРУГЛ), ROUNDDOWN (ОКРУГЛВНИЗ), ROUNDUP (ОКРУГЛВВЕРХ), SEQUENCE (ПОСЛЕД), SIGN (ЗНАК), SIN, SINH, SQRT (КОРЕНЬ), SUBTOTAL (ПРОМЕЖУТОЧНЫЕ.ИТОГИ), SUM (СУММ), SUMIF (СУММЕСЛИ), SUMIFS (СУММЕСЛИМН), SUMPRODUCT (СУММПРОИЗВ), SUMSQ (СУММКВ), TAN, TANH, TRUNC (ОТБР) |
| P1 (24) | ACOT, ACOTH, ARABIC, BASE (ОСНОВАНИЕ), COMBINA, COT, COTH, CSC, CSCH, DECIMAL (ДЕС), FACTDOUBLE, ISO.CEILING, MDETERM (МОПРЕД), MINVERSE (МОБР), MULTINOMIAL, MUNIT, ROMAN (РИМСКОЕ), SEC, SECH, SERIESSUM, SQRTPI, SUMX2MY2, SUMX2PY2, SUMXMY2 |
Статистические (111)
| Фаза | Функции |
|---|---|
| P0 (63) | AVERAGE (СРЗНАЧ), AVERAGEA, AVERAGEIF (СРЗНАЧЕСЛИ), AVERAGEIFS, AVEDEV, CORREL (КОРРЕЛ), COUNT (СЧЁТ), COUNTA (СЧЁТЗ), COUNTBLANK (СЧИТАТЬПУСТОТЫ), COUNTIF (СЧЁТЕСЛИ), COUNTIFS (СЧЁТЕСЛИМН), COVARIANCE.P, COVARIANCE.S, DEVSQ (КВАДРОТКЛ), FORECAST (ПРЕДСКАЗ), FORECAST.LINEAR, FREQUENCY (ЧАСТОТА), GEOMEAN (СРГЕОМ), GROWTH (РОСТ), HARMEAN, INTERCEPT (ОТРЕЗОК), KURT (ЭКСЦЕСС), LARGE (НАИБОЛЬШИЙ), LINEST (ЛИНЕЙН), LOGEST (ЛГРФПРИБЛ), MAX (МАКС), MAXA, MAXIFS (МАКСЕСЛИ), MEDIAN (МЕДИАНА), MIN (МИН), MINA, MINIFS (МИНЕСЛИ), MODE.MULT, MODE.SNGL (МОДА.ОДН), NORM.DIST (НОРМ.РАСП), NORM.INV, NORM.S.DIST, NORM.S.INV, PEARSON, PERCENTILE.EXC, PERCENTILE.INC (ПРОЦЕНТИЛЬ.ВКЛ), PERCENTRANK.EXC, PERCENTRANK.INC, QUARTILE.EXC, QUARTILE.INC (КВАРТИЛЬ.ВКЛ), RANK.AVG, RANK.EQ (РАНГ.РВ), RSQ (КВПИРСОН), SKEW (СКОС), SLOPE (НАКЛОН), SMALL (НАИМЕНЬШИЙ), STANDARDIZE (НОРМАЛИЗАЦИЯ), STDEV.P (СТАНДОТКЛОН.Г), STDEV.S (СТАНДОТКЛОН.В), STDEVA, STDEVPA, STEYX, TREND (ТЕНДЕНЦИЯ), TRIMMEAN (УРЕЗСРЕДНЕЕ), VAR.P (ДИСП.Г), VAR.S (ДИСП.В), VARA, VARPA |
| P1 (48) | BETA.DIST, BETA.INV, BINOM.DIST (БИНОМ.РАСП), BINOM.DIST.RANGE, BINOM.INV, CHISQ.DIST, CHISQ.DIST.RT, CHISQ.INV, CHISQ.INV.RT, CHISQ.TEST (ХИ2.ТЕСТ), CONFIDENCE.NORM (ДОВЕРИТ.НОРМ), CONFIDENCE.T, EXPON.DIST, F.DIST, F.DIST.RT, F.INV, F.INV.RT, F.TEST, FISHER, FISHERINV, FORECAST.ETS (ПРЕДСКАЗ.ETS), FORECAST.ETS.CONFINT, FORECAST.ETS.SEASONALITY, FORECAST.ETS.STAT, GAMMA, GAMMA.DIST, GAMMA.INV, GAMMALN, GAMMALN.PRECISE, GAUSS, HYPGEOM.DIST, LOGNORM.DIST, LOGNORM.INV, NEGBINOM.DIST, PERMUT (ПЕРЕСТ), PERMUTATIONA, PHI, POISSON.DIST (ПУАССОН.РАСП), PROB (ВЕРОЯТНОСТЬ), SKEW.P, T.DIST (СТЬЮДЕНТ.РАСП), T.DIST.2T, T.DIST.RT, T.INV, T.INV.2T, T.TEST (СТЬЮДЕНТ.ТЕСТ), WEIBULL.DIST, Z.TEST |
Совместимость (38 алиасов; фаза = фаза целевой функции)
| Алиас → цель | |
|---|---|
| BETADIST → BETA.DIST, BETAINV → BETA.INV, BINOMDIST → BINOM.DIST, CHIDIST → CHISQ.DIST.RT, CHIINV → CHISQ.INV.RT, CHITEST → CHISQ.TEST, CONFIDENCE → CONFIDENCE.NORM, COVAR → COVARIANCE.P, CRITBINOM → BINOM.INV, EXPONDIST → EXPON.DIST, FDIST → F.DIST.RT, FINV → F.INV.RT, FTEST → F.TEST, GAMMADIST → GAMMA.DIST, GAMMAINV → GAMMA.INV, HYPGEOMDIST → HYPGEOM.DIST, LOGINV → LOGNORM.INV, LOGNORMDIST → LOGNORM.DIST, MODE → MODE.SNGL, NEGBINOMDIST → NEGBINOM.DIST, NORMDIST → NORM.DIST, NORMINV → NORM.INV, NORMSDIST → NORM.S.DIST, NORMSINV → NORM.S.INV, PERCENTILE → PERCENTILE.INC, PERCENTRANK → PERCENTRANK.INC, POISSON → POISSON.DIST, QUARTILE → QUARTILE.INC, RANK → RANK.EQ, STDEV → STDEV.S, STDEVP → STDEV.P, TDIST → T.DIST.2T/RT (по аргументу tails), TINV → T.INV.2T, TTEST → T.TEST, VAR → VAR.S, VARP → VAR.P, WEIBULL → WEIBULL.DIST, ZTEST → Z.TEST | Алиасы сохраняют своё имя при сохранении (не переписываем формулы пользователя); отличия семантики (CEILING/FLOOR с отрицательными, HYPGEOMDIST без cumulative) учтены в адаптерах |
Текстовые (50)
| Фаза | Функции |
|---|---|
| P0 (33) | ARRAYTOTEXT (МАССИВВТЕКСТ), CHAR (СИМВОЛ), CLEAN (ПЕЧСИМВ), CODE (КОДСИМВ), CONCAT (СЦЕП), CONCATENATE (СЦЕПИТЬ), DOLLAR (РУБЛЬ), EXACT (СОВПАД), FIND (НАЙТИ), FIXED (ФИКСИРОВАННЫЙ), LEFT (ЛЕВСИМВ), LEN (ДЛСТР), LOWER (СТРОЧН), MID (ПСТР), NUMBERVALUE (ЧЗНАЧ), PROPER (ПРОПНАЧ), REPLACE (ЗАМЕНИТЬ), REPT (ПОВТОР), RIGHT (ПРАВСИМВ), SEARCH (ПОИСК), SUBSTITUTE (ПОДСТАВИТЬ), T, TEXT (ТЕКСТ), TEXTAFTER (ТЕКСТПОСЛЕ), TEXTBEFORE (ТЕКСТДО), TEXTJOIN (ОБЪЕДИНИТЬ), TEXTSPLIT (ТЕКСТРАЗД), TRIM (СЖПРОБЕЛЫ), UNICHAR (ЮНИСИМВ), UNICODE (ЮНИКОД), UPPER (ПРОПИСН), VALUE (ЗНАЧЕН), VALUETOTEXT (ЗНАЧЕНИЕВТЕКСТ) |
| P1 (15) | ASC, BAHTTEXT, DBCS, FINDB, JIS, LEFTB, LENB, MIDB, PHONETIC, REPLACEB, RIGHTB, SEARCHB, REGEXEXTRACT, REGEXREPLACE, REGEXTEST |
| P2 (2) | TRANSLATE, DETECTLANGUAGE (через Assist; без Assist —
#BLOCKED!) |
Логические (11) и массивы/LAMBDA (9)
| Фаза | Функции |
|---|---|
| P0 (11) | AND (И), FALSE (ЛОЖЬ), IF (ЕСЛИ), IFERROR (ЕСЛИОШИБКА), IFNA (ЕСНД), IFS (ЕСЛИМН), NOT (НЕ), OR (ИЛИ), SWITCH (ПЕРЕКЛЮЧ), TRUE (ИСТИНА), XOR (ИСКЛИЛИ) |
| P0 (9) | LET, LAMBDA, MAP, REDUCE, SCAN, BYROW, BYCOL, MAKEARRAY, ISOMITTED (имена не локализуются — как в Excel) |
Дата и время (25)
| Фаза | Функции |
|---|---|
| P0 (25) | DATE (ДАТА), DATEDIF (РАЗНДАТ), DATEVALUE (ДАТАЗНАЧ), DAY (ДЕНЬ), DAYS (ДНИ), DAYS360, EDATE (ДАТАМЕС), EOMONTH (КОНМЕСЯЦА), HOUR (ЧАС), ISOWEEKNUM, MINUTE (МИНУТЫ), MONTH (МЕСЯЦ), NETWORKDAYS (ЧИСТРАБДНИ), NETWORKDAYS.INTL, NOW (ТДАТА), SECOND (СЕКУНДЫ), TIME (ВРЕМЯ), TIMEVALUE (ВРЕМЗНАЧ), TODAY (СЕГОДНЯ), WEEKDAY (ДЕНЬНЕД), WEEKNUM (НОМНЕДЕЛИ), WORKDAY (РАБДЕНЬ), WORKDAY.INTL, YEAR (ГОД), YEARFRAC (ДОЛЯГОДА) |
Ссылки и поиск (41)
| Фаза | Функции |
|---|---|
| P0 (35) | ADDRESS (АДРЕС), AREAS (ОБЛАСТИ), CHOOSE (ВЫБОР), CHOOSECOLS, CHOOSEROWS, COLUMN (СТОЛБЕЦ), COLUMNS (ЧИСЛСТОЛБ), DROP, EXPAND, FILTER (ФИЛЬТР), FORMULATEXT (Ф.ТЕКСТ), GETPIVOTDATA (ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ), HLOOKUP (ГПР), HSTACK, HYPERLINK (ГИПЕРССЫЛКА), INDEX (ИНДЕКС), INDIRECT (ДВССЫЛ), LOOKUP (ПРОСМОТР), MATCH (ПОИСКПОЗ), OFFSET (СМЕЩ), ROW (СТРОКА), ROWS (ЧСТРОК), SORT (СОРТ), SORTBY (СОРТПО), TAKE, TOCOL, TOROW, TRANSPOSE (ТРАНСП), UNIQUE (УНИК), VLOOKUP (ВПР), VSTACK, WRAPCOLS, WRAPROWS, XLOOKUP (ПРОСМОТРX), XMATCH (ПОИСКПОЗX) |
| P1 (5) | IMAGE, GROUPBY, PIVOTBY, PERCENTOF, TRIMRANGE (и обрезающие ссылки
A.:.B) |
| P2 (1) | RTD (только через провайдер-скрипт) |
Финансовые (55)
| Фаза | Функции |
|---|---|
| P0 (27) | CUMIPMT (ОБЩПЛАТ), CUMPRINC (ОБЩДОХОД), DB (ФУО), DDB (ДДОБ), DOLLARDE, DOLLARFR, EFFECT (ЭФФЕКТ), FV (БС), FVSCHEDULE (БЗРАСПИС), IPMT (ПРПЛТ), IRR (ВСД), ISPMT, MIRR (МВСД), NOMINAL (НОМИНАЛ), NPER (КПЕР), NPV (ЧПС), PDURATION, PMT (ПЛТ), PPMT (ОСПЛТ), PV (ПС), RATE (СТАВКА), RRI, SLN (АПЛ), SYD (АСЧ), VDB (ПУО), XIRR (ЧИСТВНДОХ), XNPV (ЧИСТНЗ) |
| P1 (24) | ACCRINT, ACCRINTM, AMORDEGRC, AMORLINC, COUPDAYBS, COUPDAYS, COUPDAYSNC, COUPNCD, COUPNUM, COUPPCD, DISC, DURATION, INTRATE, MDURATION, PRICE, PRICEDISC, PRICEMAT, RECEIVED, TBILLEQ, TBILLPRICE, TBILLYIELD, YIELD, YIELDDISC, YIELDMAT |
| P2 (4) | ODDFPRICE, ODDFYIELD, ODDLPRICE, ODDLYIELD |
Инженерные (54)
| Фаза | Функции |
|---|---|
| P0 (24) | BIN2DEC, BIN2HEX, BIN2OCT, BITAND, BITLSHIFT, BITOR, BITRSHIFT, BITXOR, CONVERT (ПРЕОБР), DEC2BIN, DEC2HEX, DEC2OCT, DELTA, ERF, ERF.PRECISE, ERFC, ERFC.PRECISE, GESTEP, HEX2BIN, HEX2DEC, HEX2OCT, OCT2BIN, OCT2DEC, OCT2HEX |
| P1 (30) | BESSELI, BESSELJ, BESSELK, BESSELY, COMPLEX, IMABS, IMAGINARY, IMARGUMENT, IMCONJUGATE, IMCOS, IMCOSH, IMCOT, IMCSC, IMCSCH, IMDIV, IMEXP, IMLN, IMLOG10, IMLOG2, IMPOWER, IMPRODUCT, IMREAL, IMSEC, IMSECH, IMSIN, IMSINH, IMSQRT, IMSUB, IMSUM, IMTAN |
Информационные (20), базы данных (12), куб (7), веб (3), прочее
| Категория | Фаза | Функции |
|---|---|---|
| Информационные | P0 (20) | CELL (ЯЧЕЙКА), ERROR.TYPE (ТИП.ОШИБКИ), INFO (ИНФОРМ), ISBLANK (ЕПУСТО), ISERR (ЕОШ), ISERROR (ЕОШИБКА), ISEVEN (ЕЧЁТН), ISFORMULA (ЕФОРМУЛА), ISLOGICAL (ЕЛОГИЧ), ISNA (ЕНД), ISNONTEXT (ЕНЕТЕКСТ), ISNUMBER (ЕЧИСЛО), ISODD (ЕНЕЧЁТ), ISREF (ЕССЫЛКА), ISTEXT (ЕТЕКСТ), N (Ч), NA (НД), SHEET (ЛИСТ), SHEETS (ЛИСТЫ), TYPE (ТИП) |
| Базы данных | P0 (12) | DAVERAGE (ДСРЗНАЧ), DCOUNT (БСЧЁТ), DCOUNTA (БСЧЁТА), DGET (БИЗВЛЕЧЬ), DMAX (ДМАКС), DMIN (ДМИН), DPRODUCT (БДПРОИЗВЕД), DSTDEV (ДСТАНДОТКЛ), DSTDEVP, DSUM (БДСУММ), DVAR (БДДИСП), DVARP — общий движок критериев с расширенным фильтром (§5.13) |
| Куб | P2 (7) | CUBEKPIMEMBER, CUBEMEMBER, CUBEMEMBERPROPERTY, CUBERANKEDMEMBER, CUBESET, CUBESETCOUNT, CUBEVALUE — при чтении xlsx значения берутся из кэша, формулы сохраняются |
| Веб | P0 (1) / P1 (1) / P2 (1) | ENCODEURL (КОДИР.URL) — P0; FILTERXML (ФИЛЬТР.XML, XPath 1.0) — P1;
WEBSERVICE (ВЕБСЛУЖБА) — P2, по политике, с разрешением на домен,
#BLOCKED! по умолчанию |
| Надстройки | P2 / нет | EUROCONVERT — P2; CALL, REGISTER.ID, SQL.REQUEST — не поддерживаются
(значение из кэша файла, #NAME? при пересчёте с
пояснением) |
Расширения MS. |
P1 | MS.REGEX (алиас REGEXEXTRACT), MS.NOWUTC, MS.UUID, MS.HASH
(sha256/md5/crc32), MS.JSONVALUE (JSONPath по тексту), MS.CELLCOLOR,
MS.SHEETNAME, MS.FORMULAS (список формул диапазона), MS.DISPLAYTEXT
(отображаемый текст ячейки); при экспорте в xlsx —
_xludf.MS.* + кэш значений |
Семантика критериев (COUNTIF-семейство,
SUMIFS, AVERAGEIFS, MINIFS,
MAXIFS, D-функции): строка критерия разбирается как
[оператор][значение] (">=10",
"<>", "=*а?", "10",
"текст"), подстановочные * ? ~, число в строке
сравнивается как число (с разбором по локали), текст — без учёта
регистра; пустой критерий "" соответствует пустым и пустым
строкам, "<>" — непустым; критерий-число
соответствует и числам-текстам в ячейках (Excel-совместимость).
5.24 Особенности реализации сложных функций
| Функция | Реализация |
|---|---|
SUMPRODUCT |
аргументы вычисляются в массивном контексте (без неявного
пересечения), размеры должны совпадать (иначе #VALUE!);
текст/логические в диапазонах → 0, в выражениях
(A1:A9="x")*B1:B9 булевы приводятся к 0/1;
SIMD-умножение-сложение по столбцам чанков;
SUMPRODUCT(--(...)) оптимизируется в один проход |
SUBTOTAL / AGGREGATE |
коды 1–11 (с скрытыми) / 101–111 (без скрытых вручную);
AGGREGATE — коды 1–19 (включая
LARGE/SMALL/PERCENTILE/QUARTILE/MEDIAN/MODE) и опции 0–7 (игнорировать
вложенные SUBTOTAL/AGGREGATE, скрытые, ошибки); оба никогда не считают
строки, скрытые автофильтром; ссылочная форма (ссылки) и массивная форма
(массив + k) различаются по типу аргумента |
TEXT |
формат компилируется парсером §5.10 (кэш), результат — DisplayString
без ширины (без ###); локаль — книги;
TEXT(x;"0") для ошибок возвращает ошибку |
DATEDIF |
единицы Y, M, D,
MD, YM, YD по алгоритму Excel:
Y — полные годы; M — полные месяцы;
MD — день конца минус день начала с переносом месяца
(воспроизводится известная ошибка Excel для MD при переходе
через конец месяца — в справке помечена); дата начала > конца →
#NUM! |
VLOOKUP/HLOOKUP/MATCH/LOOKUP
(approx) |
бинарный поиск по отсортированному диапазону с семантикой Excel
(последнее значение ≤ искомого; смешанные типы: числа < текст <
bool, значения другого типа пропускаются); точный VLOOKUP —
линейный с хэш-ускорением: при повторных вызовах на одном диапазоне в
одном пересчёте строится индекс
HashMap<ValueKey, row> (кэш на пересчёт), что даёт
O(1) на вызов и решает классическую проблему «10 000 ВПР медленно» |
XLOOKUP/XMATCH |
режимы сравнения (0 точный, −1 следующий меньший, 1 следующий
больший, 2 подстановочные) и поиска (1 с начала, −1 с конца, 2 бинарный
по возрастанию, −2 по убыванию); if_not_found; возвращает
диапазон (можно XLOOKUP(...):XLOOKUP(...)); хэш-кэш как у
VLOOKUP для режима 0/1 |
FILTER/SORT/SORTBY/UNIQUE/SEQUENCE/TAKE/DROP/CHOOSEROWS/HSTACK… |
работают над Val::Array без копирования исходных
данных, где возможно (срезы по индексам); UNIQUE — хэш с
сохранением порядка, by_col, exactly_once;
SORT — тот же компаратор §5.12; результат — spill |
TEXTSPLIT/TEXTBEFORE/TEXTAFTER/TEXTJOIN |
несколько разделителей (массив), ignore_empty,
match_mode (регистр), pad_with; поиск — по
графемным кластерам (Unicode), а не по байтам |
LAMBDA/LET/MAP/REDUCE/SCAN/BYROW/BYCOL/MAKEARRAY |
замыкания над LET-слотами и именами; рекурсия через именованную
LAMBDA (глубина ≤ 1024 → #CALC!); хелперы вызывают тело по
элементам/строкам/столбцам; результат хелпера — массив (вложенные
массивы запрещены → #CALC!); REDUCE с
аккумулятором-массивом допустим |
LINEST/LOGEST/TREND/GROWTH |
решение МНК через QR-разложение (Householder), а не нормальные
уравнения — точность при коллинеарности; статистики
(stats=TRUE): se коэффициентов, R², se_y, F, df, ss_reg,
ss_resid; коллинеарные столбцы — нулевой коэффициент (как Excel);
многомерные X (матрица), const=FALSE |
FORECAST.ETS* |
модель AAA (аддитивный тренд и сезонность, Holt-Winters) с
оптимизацией α, β, γ по MSE (Nelder–Mead); сезонность: автоопределение
по автокорреляции (пик до 8760 периодов), data_completion
(интерполяция пропусков), aggregation (дубликаты меток
времени); доверительный интервал по симуляции остатков;
детерминированность — фиксированный seed |
IRR/XIRR/RATE/YIELD
и прочие итерационные |
метод Ньютона от guess (0.1) с аналитической
производной, 20 итераций до 1e-7 (как Excel: #NUM! при
несходимости), затем резерв: бисекция/Брент на интервале (−0.99…10) с
поиском смены знака — находит решение там, где Excel даёт
#NUM! (результаты расходятся с Excel только в этих случаях;
корпус тестов помечает их); XIRR — дни/365;
RATE — type и fv |
NPV/XNPV/PMT-семейство |
формулы в замкнутом виде; PMT при rate=0 —
линейный случай; точность — компенсированное суммирование (Kahan) для
NPV длинных потоков |
PRICE/YIELD/DURATION/COUP*/ACCRINT |
базисы дней 0–4 (US 30/360, Actual/Actual, Actual/360, Actual/365, European 30/360) по формулам стандарта OOXML §18.17.4; купонные даты — от даты погашения назад с нормализацией конца месяца |
Распределения (NORM.*, T.*,
CHISQ.*, F.*, GAMMA.*,
BETA.*) |
специальные функции: lgamma (Lanczos), регуляризованные
неполные гамма/бета (непрерывные дроби Лентца, DiDonato–Morris),
erf/erfc (Cody); обратные:
NORM.S.INV — алгоритм Acklam с шагом Ньютона-Халли
(точность 1e-15), остальные — бисекция+Ньютон по CDF до 1e-12; проверка
параметров → #NUM! |
PERCENTILE.INC/EXC, QUARTILE.*,
MEDIAN, LARGE/SMALL |
частичная сортировка (select_nth_unstable) O(n);
интерполяция по определению Excel (INC: ранг
p·(n−1)+1; EXC: p·(n+1), вне
диапазона → #NUM!) |
MODE.MULT, FREQUENCY |
хэш-подсчёт; FREQUENCY — бинарный поиск по
отсортированным bins, результат на 1 длиннее bins (spill) |
RANK.EQ/AVG, PERCENTRANK |
сортированная копия с кэшем на пересчёт (повторные вызовы по одному
диапазону); approx_eq при сравнении |
STDEV.*/VAR.*/COVARIANCE.*/CORREL |
алгоритм Уэлфорда (одним проходом, численно устойчив), параллельное слияние по чанкам |
ROUND, MROUND, CEILING.*,
FLOOR.* |
десятичное округление (§5.8);
CEILING/FLOOR старые — ошибка при разных
знаках (#NUM!), .MATH — с mode
для отрицательных |
CONVERT |
таблица единиц OOXML (вес, расстояние, время, давление, сила,
энергия, мощность, магнетизм, температура, объём, площадь, информация,
скорость) с префиксами СИ и двоичными (ki,
Mi…); температура — аффинные преобразования |
CELL/INFO |
CELL("format") — коды форматов Excel (G,
F2, ,2, C2, P2,
D1…D9, S2), "width",
"protect", "address", "filename",
"color", "prefix", "type";
INFO: "directory", "numfile",
"osversion", "recalc", "release"
(строка версии Meridian), "system"
(pcdos/mac/linux) |
INDIRECT |
разбор текста ссылки парсером §5.2 в локали пользователя (как Excel
— зависит от локали! опция «английский A1 всегда»), R1C1 при
a1=FALSE; динамические зависимости (§5.4) |
OFFSET |
возвращает ссылку (не значения) → работает как операнд
: и для SUM;
height/width 0 → #REF! |
HYPERLINK |
значение — текст/число, ссылка хранится в результате
(Extra::Hyperlink динамический), клик открывает |
GETPIVOTDATA |
поиск по PivotRender по подписям полей/элементов
(локализованные имена итогов учитываются) |
IMAGE |
загрузка по URL с разрешением политики, кэш в media/;
размещение в ячейке |
RAND/RANDBETWEEN/RANDARRAY |
ChaCha20 PRNG на пересчёт, равномерность; воспроизводимость — опция «seed пересчёта» (тесты) |
ROMAN/ARABIC/BAHTTEXT/DOLLAR/FIXED |
таблицы форм (ROMAN 0–4), локализованные денежные форматы |
FORMULATEXT |
формула в локали отображения пользователя (как Excel) |
WEBSERVICE/FILTERXML/ENCODEURL |
HTTP через оболочку с политикой и кэшем (TTL),
#BLOCKED! без разрешения; XPath 1.0
(sxd-xpath) |
6. MeridianScript API для Sheets
Общие положения MeridianScript (песочница QuickJS-ng, транспиляция
TypeScript, разрешения, подписи, магазин) — в
docs/03-architecture.md (раздел MeridianScript). Здесь —
объектная модель Sheets. Декларации: meridian-sheets.d.ts
(генерируются из Rust-атрибутов #[script_api]). Все вызовы
синхронны по отношению к модели (выполняются в потоке документа между
пересчётами), сетевые и файловые — Promise. Пакетирование:
изменения внутри одного обработчика объединяются в одну транзакцию undo
и один пересчёт.
6.1 Объектная модель
Application (глобальный объект `sheets`)
├─ workbooks: WorkbookCollection → Workbook (active, open(), create(), byName())
├─ ui: UI (ribbon, sidebar, dialogs, toast, prompt, commandPalette)
├─ clipboard, locale, version, edition (Features), user (Meridian ID, если вошёл)
└─ schedule: Scheduler (every/at/cron — только для скриптов книги с разрешением)
Workbook
├─ sheets: SheetCollection → Sheet (add, byName, byIndex, active, move, delete)
├─ names: NamedItemCollection → NamedItem
├─ tables, pivots, charts (сквозь листы), connections, flows, styles, theme, properties, protection
├─ calculation: { mode, recalc(), recalcAll(), iterative }
├─ history (undo/redo, beginGroup/endGroup), save(), saveAs(), export(), close()
└─ events: onOpen, onBeforeSave, onBeforeClose, onSheetActivate, onEdit, onRecalc, onSelectionChange, onCommentAdded, onProtectionChange
Sheet
├─ range(a1 | row,col[,rows,cols]) → Range; cell(row,col) → Range 1×1; usedRange; selection; activeCell
├─ rows/columns: AxisCollection (height/width/hidden/group); freeze(rows, cols); split; zoom; view
├─ tables → Table; pivots → PivotTable; charts → Chart; images, shapes, controls; comments (threads), notes; hyperlinks
├─ conditionalFormats, validations, autoFilter, sparklines, protection, pageSetup, printAreas
├─ name, tabColor, visibility (visible|hidden|veryHidden), index, id
└─ events: onEdit, onSelectionChange, onActivate, onDoubleClick, onRightClick (отменяемые)
Range (прямоугольник или объединение прямоугольников — `areas`)
├─ address, sheet, row/column/rowCount/columnCount, cells(), rows(), columns(), offset(), resize(), intersect(), union()
├─ values: CellValue[][] (get/set), formulas (локаль-независимые, английские; formulasLocal), formulasR1C1, text (отображаемый)
├─ numberFormat, style/font/fill/borders/alignment/protection (get/set, пакетно), merge()/unmerge(), clear(what)
├─ sort(), autoFilter(), find()/replace(), removeDuplicates(), textToColumns(), fill(direction|series), copyTo()/moveTo()/paste()
├─ insert(shift), delete(shift), hyperlink, note, comments, validation, conditionalFormats, spillParent, spillRange, hasSpill
├─ dependents(), precedents(), calculate(), errorInfo
└─ табличные: table (если внутри), pivot (если внутри)
Table → columns (TableColumn: name, range, dataBodyRange, totalsFormula, calculatedFormula), rows, headerRange, totalsRange, style, resize(), convertToRange(), addRow(), filters
PivotTable → cache, fields (rows/columns/filters/values), layout, refresh(), addDataField(), group(), slicers, render → Range
Chart → type, series (SeriesCollection), axes, title, legend, dataLabels, trendlines, style, position (anchor), export(png|svg|pdf), pivotSource
NamedItem → name, scope, type (range|constant|formula|lambda), value, formula, comment, delete()
DataConnection / DataFlow → source, steps, refresh(), schedule, lastRefresh
Типы значений:
CellValue = number | string | boolean | null | ErrorValue | RichValue;
ErrorValue = { error: '#N/A' | ..., message?: string };
даты — number сериал плюс помощники
sheets.dates.fromDate(Date), toDate(serial);
Range.typedValues возвращает
{ kind: 'date', value: Date } и т.д.
6.2 Декларации
(фрагмент meridian-sheets.d.ts)
declare namespace sheets {
const workbooks: WorkbookCollection;
const ui: UI;
const schedule: Scheduler;
function customFunction(opts?: CustomFunctionOptions): MethodDecorator; // @sheets.customFunction()
interface Workbook {
readonly name: string; readonly path: string | null; readonly sheets: SheetCollection;
readonly names: NamedItemCollection; readonly tables: Table[]; readonly pivots: PivotTable[]; readonly charts: Chart[];
calculation: { mode: 'auto' | 'autoExceptTables' | 'manual'; iterative: { enabled: boolean; maxIterations: number; maxChange: number } };
recalc(full?: boolean): void;
save(): Promise<void>; saveAs(path: string, format?: FileFormat): Promise<void>;
export(format: 'pdf' | 'csv' | 'html' | 'json' | 'parquet', opts?: ExportOptions): Promise<Uint8Array>;
batch<T>(fn: () => T): T; // одна транзакция undo + один пересчёт + одна перерисовка
on(event: 'edit', handler: (e: EditEvent) => void): Disposable;
on(event: 'recalc', handler: (e: RecalcEvent) => void): Disposable;
on(event: 'open' | 'beforeSave' | 'beforeClose', handler: (e: WorkbookEvent) => void): Disposable;
on(event: 'selectionChange', handler: (e: SelectionEvent) => void): Disposable;
}
interface Sheet {
name: string; tabColor: string | null; visibility: 'visible' | 'hidden' | 'veryHidden'; readonly index: number;
range(address: string): Range; range(row: number, col: number, rows?: number, cols?: number): Range;
readonly usedRange: Range; readonly selection: Range; activeCell: Range;
readonly rows: Axis; readonly columns: Axis;
freeze(rows: number, cols: number): void;
readonly tables: TableCollection; readonly pivots: PivotCollection; readonly charts: ChartCollection;
readonly conditionalFormats: ConditionalFormatCollection; readonly validations: ValidationCollection;
protect(opts?: SheetProtectionOptions): void; unprotect(password?: string): void;
on(event: 'edit', handler: (e: EditEvent) => void): Disposable;
}
interface Range {
readonly address: string; readonly sheet: Sheet;
readonly row: number; readonly column: number; readonly rowCount: number; readonly columnCount: number; // 1-based, как в Excel
values: CellValue[][]; formulas: (string | null)[][]; formulasLocal: (string | null)[][]; readonly text: string[][];
numberFormat: string | string[][]; style: string | null; font: Font; fill: Fill; borders: Borders; alignment: Alignment;
offset(rows: number, cols: number): Range; resize(rows?: number, cols?: number): Range; cell(row: number, col: number): Range;
getRow(i: number): Range; getColumn(i: number): Range; intersect(other: Range): Range | null;
clear(what?: 'all' | 'contents' | 'formats' | 'notes' | 'hyperlinks'): void;
insert(shift: 'down' | 'right'): void; delete(shift: 'up' | 'left'): void;
merge(across?: boolean): void; unmerge(): void;
sort(keys: SortKey[], opts?: SortOptions): void; removeDuplicates(columns?: number[], hasHeader?: boolean): number;
find(what: string | RegExp, opts?: FindOptions): Range | null; findAll(what: string | RegExp, opts?: FindOptions): Range[];
replace(what: string | RegExp, by: string, opts?: FindOptions): number;
fill(direction: 'down' | 'right' | 'up' | 'left', mode?: 'copy' | 'series' | 'formats'): void;
copyTo(target: Range, what?: PasteWhat, opts?: PasteOptions): void; moveTo(target: Range): void;
readonly spillParent: Range | null; readonly spillRange: Range | null; readonly errorInfo: ErrorInfo | null;
dependents(direct?: boolean): Range[]; precedents(direct?: boolean): Range[];
readonly table: Table | null; readonly pivot: PivotTable | null;
validation: Validation | null; hyperlink: Hyperlink | null; note: string | null; readonly comments: CommentThread[];
}
interface EditEvent { range: Range; oldValues: CellValue[][]; newValues: CellValue[][]; source: 'user' | 'script' | 'paste' | 'fill' | 'import'; cancel(): void }
interface RecalcEvent { changed: Range[]; durationMs: number; circular: Range[] }
interface UI {
ribbon: { addButton(opts: { tab: string; group: string; label: string; icon?: string; onClick: () => void; key?: string }): Disposable };
sidebar: { open(opts: { title: string; html: string; onMessage?: (m: unknown) => void }): SidebarHandle };
toast(text: string, kind?: 'info' | 'success' | 'warning' | 'error'): void;
prompt(opts: PromptOptions): Promise<string | null>; confirm(text: string): Promise<boolean>;
pickRange(prompt: string): Promise<Range | null>;
}
}6.3 События
| Событие | Уровень | Когда | Особенности |
|---|---|---|---|
onOpen |
книга | после загрузки и первого пересчёта | скрипты книги требуют разрешения «выполнять при открытии» (политика; по умолчанию — вопрос пользователю) |
onEdit |
книга, лист | после изменения значений/формул пользователем, вставкой, заполнением, импортом; не при пересчёте | e.cancel() до коммита — откат (валидация); обработчик
не должен работать > 200 мс (предупреждение), иначе — асинхронно |
onRecalc |
книга | после каждого пересчёта | изменённые диапазоны, длительность, циклы; запрещено менять ячейки синхронно (ставится в очередь — защита от бесконечного цикла, лимит 3 вложения) |
onSelectionChange |
книга, лист | выделение | для панелей и подсказок |
onBeforeSave / onBeforeClose |
книга | перед сохранением/закрытием | отменяемые; могут обновить потоки, снять фильтры |
onSheetActivate / onActivate |
книга / лист | переключение листа | |
onDoubleClick / onRightClick |
лист | по ячейке | отменяемые (кастомные действия) |
onCommentAdded |
книга | обсуждение | фаза 6 — уведомления |
onDataRefreshed |
книга | завершение потока/подключения | |
onButtonClick |
элемент управления | кнопка/флажок/список на листе | привязка в свойствах объекта |
| расписание | книга | sheets.schedule.every('1h'), at('09:00'),
cron('0 9 * * 1-5') |
работает, пока книга открыта; «фоновые задачи Hub» — P2 |
6.4 Примеры
1. Обработка диапазона — нормализация телефонов в выделении (пакетно, одна транзакция):
// Нормализуем телефоны к виду +7XXXXXXXXXX / Normalize phone numbers to +7XXXXXXXXXX
const wb = sheets.workbooks.active;
const rng = wb.sheets.active.selection;
wb.batch(() => {
const v = rng.values; // одно чтение / single read
for (const row of v) for (let c = 0; c < row.length; c++) {
const s = String(row[c] ?? '').replace(/\D/g, '');
if (s.length === 11 && (s[0] === '7' || s[0] === '8')) row[c] = '+7' + s.slice(1);
else if (s.length === 10) row[c] = '+7' + s;
}
rng.values = v; // одна запись / single write
rng.numberFormat = '@';
});
sheets.ui.toast(`Обработано ${rng.rowCount * rng.columnCount} ячеек`, 'success');2. Пользовательская функция (UDF) с типами и массивным результатом:
/** Налог по прогрессивной шкале / Progressive tax. Доступна в формулах как =ПРОГРНАЛОГ(доход) или =SCRIPT.PROGTAX(income) */
@sheets.customFunction({ name: 'PROGTAX', localName: { ru: 'ПРОГРНАЛОГ' }, description: 'Налог по прогрессивной шкале',
args: [{ name: 'income', type: 'number', description: 'Доход за год' }], volatile: false })
export function progTax(income: number): number {
const brackets: [number, number][] = [[2_400_000, 0.13], [5_000_000, 0.15], [20_000_000, 0.18], [50_000_000, 0.20], [Infinity, 0.22]];
let tax = 0, prev = 0;
for (const [limit, rate] of brackets) { if (income <= prev) break; tax += (Math.min(income, limit) - prev) * rate; prev = limit; }
return Math.round(tax * 100) / 100;
}
/** Возвращает массив → разливается (spill) / returns array → spills */
@sheets.customFunction({ name: 'FIBS', args: [{ name: 'n', type: 'number' }] })
export function fibs(n: number): number[][] {
const out: number[][] = [[0], [1]];
for (let i = 2; i < n; i++) out.push([out[i - 1][0] + out[i - 2][0]]);
return out.slice(0, n);
}3. Импорт JSON по расписанию в структурированную таблицу:
// Каждый час тянем курсы валют и обновляем таблицу «Курсы» / Hourly FX rates into table "Курсы"
sheets.schedule.every('1h', async () => {
const resp = await sheets.net.fetch('https://api.example.com/rates?base=RUB'); // требует разрешения «сеть: api.example.com»
const data = await resp.json() as { date: string; rates: Record<string, number> };
const wb = sheets.workbooks.active;
const table = wb.sheets.byName('Данные').tables.byName('Курсы');
wb.batch(() => {
table.dataBodyRange?.clear('contents');
const rows = Object.entries(data.rates).map(([code, rate]) => [code, rate, sheets.dates.fromDate(new Date(data.date))]);
table.addRows(rows); // таблица расширяется, форматы и вычисляемые столбцы применяются
table.columns.byName('Дата').dataBodyRange.numberFormat = 'dd.mm.yyyy';
});
wb.pivots.filter(p => p.cache.source === table).forEach(p => p.refresh());
});4. Построение отчёта: сводная + диаграмма + форматирование на новом листе:
const wb = sheets.workbooks.active;
const src = wb.sheets.byName('Продажи').tables.byName('Продажи');
const rep = wb.sheets.add('Отчёт');
const pv = rep.pivots.add({ source: src, at: rep.range('A3'), name: 'СводПродажи' });
pv.rows.add('Регион'); pv.columns.add('Квартал'); pv.values.add('Сумма', { aggregate: 'sum', numberFormat: '#,##0 ₽' });
pv.values.add('Сумма', { aggregate: 'sum', showAs: { kind: 'percentOfRowTotal' }, name: 'Доля' });
pv.layout = { form: 'tabular', subtotals: 'none', grandTotals: 'both' };
pv.refresh();
const ch = rep.charts.add({ type: 'columnClustered', pivot: pv, anchor: rep.range('H3:P20') });
ch.title.text = 'Продажи по регионам'; ch.legend.position = 'bottom'; ch.style = 7;
rep.range('A1').values = [['Отчёт по продажам']]; rep.range('A1').style = 'Заголовок 1';
rep.range('A2').formulas = [['="Сформирован "&TEXT(NOW();"dd.mm.yyyy hh:mm")']];
rep.columns.autoFit(); rep.freeze(3, 0);
await wb.export('pdf', { sheets: ['Отчёт'], fitTo: { width: 1, height: 0 } }).then(bytes => sheets.files.save('Отчёт.pdf', bytes));5. Кнопка в ленте и панель:
// Кнопка «Закрыть месяц» на вкладке Скрипты / Ribbon button "Close month"
sheets.ui.ribbon.addButton({ tab: 'Скрипты', group: 'Финансы', label: 'Закрыть месяц', icon: 'lock', key: 'Ctrl+Shift+M',
onClick: async () => {
if (!(await sheets.ui.confirm('Закрыть текущий месяц? Ячейки ввода будут защищены.'))) return;
const sh = sheets.workbooks.active.sheets.byName('Ввод');
sheets.workbooks.active.batch(() => {
sh.range('B2:M200').protection = { locked: true };
sh.protect({ allowFilter: true, allowSort: false });
sh.range('A1').note = `Закрыт ${new Date().toLocaleDateString('ru-RU')} пользователем ${sheets.user?.name ?? 'локальный'}`;
});
sheets.ui.toast('Месяц закрыт', 'success');
}});6. Проверка ввода через onEdit с отменой и
журналом:
const wb = sheets.workbooks.active;
const log = wb.sheets.byName('Журнал') ?? wb.sheets.add('Журнал', { visibility: 'hidden' });
wb.sheets.byName('Заказы').on('edit', e => {
const col = e.range.table?.columns.byName('Количество');
if (col && e.range.intersect(col.dataBodyRange)) {
for (const row of e.newValues) for (const v of row)
if (typeof v !== 'number' || v <= 0 || !Number.isInteger(v)) { sheets.ui.toast('Количество — целое число > 0', 'error'); e.cancel(); return; }
}
log.usedRange.offset(log.usedRange.rowCount, 0).resize(1, 4).values =
[[sheets.dates.fromDate(new Date()), sheets.user?.email ?? 'local', e.range.address, JSON.stringify(e.newValues)]];
});7. Запись действий — что получает пользователь после «Записать» (сортировка, УФ, итог):
// Записано 2026-10-03 12:41 / Recorded by MeridianScript recorder
export function main(wb = sheets.workbooks.active) {
const sh = wb.sheets.byName('Данные');
sh.range('A1:D500').sort([{ column: 3, order: 'desc' }], { hasHeader: true });
sh.range('C2:C500').conditionalFormats.add({ kind: 'dataBar', color: '#1E8E5A', min: { kind: 'auto' }, max: { kind: 'auto' } });
sh.range('C501').formulas = [['=SUBTOTAL(109;C2:C500)']];
sh.range('A501:C501').style = 'Итог';
}8. Поток данных из Base по запросу и обновление сводных:
const flow = sheets.workbooks.active.flows.add({
name: 'Заказы из CRM',
source: { kind: 'base', connection: 'crm-postgres', query: 'SELECT id, customer, total, created_at FROM orders WHERE created_at > now() - interval \'90 days\'' },
steps: [
{ kind: 'setTypes', columns: { total: 'currency:RUB', created_at: 'date' } },
{ kind: 'filterRows', formula: '[total] > 0' },
{ kind: 'addColumn', name: 'Месяц', formula: 'EOMONTH([created_at];0)' },
],
target: { kind: 'table', sheet: 'Данные', name: 'Заказы' },
});
await flow.refresh();6.5 Соответствие объектам VBA Excel (для транслятора и миграции)
| VBA | MeridianScript | Примечание |
|---|---|---|
Application |
sheets |
Application.ScreenUpdating = False →
wb.batch(() => …); Application.Calculation
→ wb.calculation.mode |
Workbooks, ActiveWorkbook,
ThisWorkbook |
sheets.workbooks, .active,
sheets.workbooks.current |
Workbooks.Open(path) →
await sheets.workbooks.open(path) |
Worksheets, Sheets,
ActiveSheet |
wb.sheets, wb.sheets.active |
Worksheets("X") → wb.sheets.byName('X');
Sheets(1) → byIndex(1) |
Range("A1"), Cells(r,c),
[A1] |
sh.range('A1'), sh.cell(r,c) |
Range("A1:B2").Value → .values (всегда
2D) |
.Value, .Value2, .Formula,
.FormulaR1C1, .Text |
.values, .values, .formulas,
.formulasR1C1, .text |
.FormulaLocal → .formulasLocal |
.NumberFormat, .Font,
.Interior, .Borders,
.HorizontalAlignment |
.numberFormat, .font, .fill,
.borders, .alignment.horizontal |
константы xlCenter → 'center' |
.Offset, .Resize,
.End(xlDown), .CurrentRegion,
.EntireRow |
.offset, .resize,
.end('down'), .currentRegion,
.entireRow |
|
.Copy Destination:=, .PasteSpecial,
.Cut |
.copyTo(target, what), .moveTo |
без системного буфера |
.Sort, .AutoFilter,
.RemoveDuplicates, .TextToColumns |
.sort, .autoFilter,
.removeDuplicates, .textToColumns |
|
.Find, .Replace |
.find, .replace |
возвращает `Range |
ListObjects, ListObject,
ListColumns, DataBodyRange |
sh.tables, Table, .columns,
.dataBodyRange |
|
PivotTables, PivotFields,
PivotCache.Refresh |
sh.pivots, .fields,
.refresh() |
|
ChartObjects, Chart.SeriesCollection |
sh.charts, .series |
|
Names, Names.Add |
wb.names, .add(name, formula) |
|
Comments, .AddComment |
.note (примечание) / .comments.add
(обсуждение) |
|
Hyperlinks.Add |
.hyperlink = {…} |
|
Workbook_Open, Worksheet_Change,
Worksheet_SelectionChange,
Workbook_BeforeSave |
wb.on('open'), sh.on('edit'),
sh.on('selectionChange'),
wb.on('beforeSave') |
Target → e.range;
Application.EnableEvents=False →
e.source === 'script' фильтр |
MsgBox, InputBox,
Application.InputBox(Type:=8) |
sheets.ui.confirm/toast, sheets.ui.prompt,
sheets.ui.pickRange |
асинхронные |
UserForm |
sheets.ui.sidebar.open({ html }) / диалог из
HTML-шаблона |
формы VBA не транслируются автоматически |
Dir, Open … For Input,
FileSystemObject |
sheets.files.* (с разрешением) |
|
CreateObject("MSXML2.XMLHTTP"),
WinHttp |
sheets.net.fetch |
разрешение на домен |
Application.OnTime |
sheets.schedule.at |
|
Application.WorksheetFunction.X(...) |
sheets.fn.X(...) (все функции каталога из скрипта) |
ошибки — исключения CalcError |
Err, On Error Resume Next |
try/catch |
транслятор оборачивает блоки |
Транслятор VBA (P1) покрывает: Sub/Function, переменные
и типы (Dim … As),
If/Select Case/For/For Each/Do While/With, операции над
Range/Cells/Worksheets/Workbooks,
WorksheetFunction, MsgBox/InputBox, события
листа/книги. Непереводимые конструкции (Declare,
COM-объекты, UserForm, GoTo) сохраняются
комментариями // TODO VBA: с исходным текстом.
7. Форматы
Подробно — docs/04-file-formats.md (структура пакетов,
таблицы соответствия элементов, корпус тестов). Здесь — особенности
Sheets.
| Формат | Режим | Фаза | Особенности |
|---|---|---|---|
.msx / .mst / .msxf |
R/W | P0 | MOP: content.xml (книга: листы ссылками на
sheets/sheetN.xml), styles.xml,
names.xml, pivots/, charts/
(общая схема Meridian Chart), flows/,
scripts/, history/, meta.xml,
settings.xml, thumbnail.png. Ячейки —
построчно
<r n="3"><c c="2" t="n" s="7" f="SUM(R[-2]C:R[-1]C)" sf="12">660000</c></r>
(формулы в каноническом R1C1-относительном виде с индексом общей формулы
sf, значения — Ryū); большие листы — потоковая
запись/чтение (quick-xml), опционально столбцовые бинарные
части sheets/sheetN.cells (CBOR, zstd) для > 1M ячеек
(флаг в манифесте); плоский .msxf — всё в одном XML (без
бинарных частей) |
.xlsx / .xltx |
R/W | P0 | SpreadsheetML: sharedStrings (чтение — напрямую в
StringPool), styles (xf →
StyleTable), формулы (shared,
array, dataTable),
cm/xda метаданные динамических массивов,
_xlfn.-префиксы снимаются, темы, УФ (включая
x14: расширения), проверка, таблицы, автофильтр, сводные
(pivotCacheDefinition/Records, pivotTable),
срезы, диаграммы (chart + chartEx для
современных типов), спарклайны (x14:sparklineGroups),
обсуждения (threadedComments + persons),
примечания (comments + VML), изображения в ячейках
(richData), защита, печать, представления, внешние ссылки,
свойства; запись — полный round-trip неизвестных частей (сохраняем
нераспознанные part'ы как есть) |
.xlsm |
R/W | P0 | как xlsx + vbaProject.bin сохраняется байт-в-байт (не
исполняется); при наличии скриптов MeridianScript они пишутся в
customXml/meridian/scripts/ (Excel
игнорирует); предупреждение «макросы VBA не будут выполняться» |
.xlsb |
R | P1 | бинарные записи BIFF12 (BrtCellReal,
BrtFmlaNum…), парсер формул из PTG-токенов (тот же, что для
xls); запись — P2 |
.xls (BIFF8) |
R/W | P0 (R), P1 (W) | BIFF8 + OLE2: SST, XF,
FORMULA с PTG, SHRFMLA, ARRAY,
диаграммы BIFF (чтение базовых типов), сводные (SXVIEW,
чтение), макросы Excel 4.0 — только сохранение; BIFF5/BIFF2–4 — чтение;
запись — 65 536 строк × 256 столбцов с предупреждением об обрезке |
.ods / .ots |
R/W | P0 | ODF 1.3: формулы OpenFormula of:=SUM([.A1:.B2]) ↔︎ канон
(конвертер синтаксиса ссылок и имён функций, ;
разделитель), стили автоматические/именованные, УФ
(calcext:), проверка, фильтры, сводные
(table:data-pilot-table), диаграммы (ODF chart),
примечания; «что теряется»: обсуждения → примечания, спарклайны → нет
(значения остаются), структурированные ссылки → обычные |
.csv / .tsv |
R/W | P0 | §5.21 |
.numbers |
R | P1 | IWA (protobuf + snappy) через numbers-parser-подобный
модуль: таблицы, формулы (частично — по известным opcode), стили
базовые |
.gnumeric, .dif, .sylk,
.dbf |
R | P1 | простые таблицы и формулы |
.parquet, .json, .xml |
R (+ W для parquet/json) | P1 | §2.8, §5.22 |
.pdf, .html, .png
диаграмм |
W | P0 | §5.22 |
Что теряется при сохранении .msx →
.xlsx (диалог «Проверка совместимости» перечисляет
по месту):
| Возможность Meridian | В xlsx |
|---|---|
| Скрипты MeridianScript и события | сохраняются в meridian/scripts/*.ts (custom part) —
Excel не видит; при открытии в Meridian восстанавливаются; кнопки ленты
— нет |
UDF SCRIPT.* / MS.* |
формула _xludf.ИМЯ(...) + кэшированное значение (Excel
показывает #NAME? после пересчёта) |
Типы NumKind (валюта с кодом, длительность, тип
даты) |
только числовой формат ячейки; код валюты — в формате
[$₽-419]; тип теряется (восстанавливается эвристикой по
формату) |
Причины ошибок (ErrorInfo), профили пересчёта |
теряются |
| Проверка данных «регулярное выражение» | custom с формулой REGEXTEST(...) (Excel
365 понимает) или без проверки для старых версий |
| Потоки данных | meridian/flows/*.json; в Excel данные остаются
статическими |
| История CRDT, версии | не сохраняется (только последнее состояние) |
| Большой режим > 2^20 строк | обрезка с предупреждением; предложение Parquet/CSV |
| Богатые типы собственных схем | richData с типом «запись» (Excel 365 показывает
карточку базово), иначе текст |
| Обсуждения с @упоминаниями Meridian ID | threadedComments с persons (почта как
идентификатор) |
| Диаграммы: карта, свои стили | chartEx (365) или растр для старых версий — по
выбору |
| Градиентные заливки ячеек, свои наборы значков УФ | градиент — есть в xlsx; свои значки → ближайший стандартный набор |
| Цвета ярлычков/темы сверх 12 цветов темы | RGB — без потерь |
Обратно (xlsx → msx) потерь нет: все неизвестные части
сохраняются как «посторонние» элементы пакета для round-trip.
8. Горячие клавиши
Схема Excel; macOS — ⌘ вместо Ctrl там, где
указано, иначе Ctrl (⌃). Схемы
LibreOffice/Google Sheets/своя — в Параметрах.
| Действие | Windows / Linux | macOS |
|---|---|---|
| Создать / Открыть / Сохранить / Сохранить как / Печать | Ctrl+N / Ctrl+O / Ctrl+S /
F12 |
⌘N / ⌘O / ⌘S /
⌘⇧S / ⌘P |
| Закрыть книгу / Выход | Ctrl+W / Alt+F4 |
⌘W / ⌘Q |
| Отменить / Повторить | Ctrl+Z / Ctrl+Y |
⌘Z / ⌘⇧Z |
| Вырезать / Копировать / Вставить / Специальная вставка | Ctrl+X / Ctrl+C / Ctrl+V /
Ctrl+Alt+V |
⌘X / ⌘C / ⌘V /
⌘⌃V |
| Командная палитра | Ctrl+K |
⌘K |
| Найти / Заменить / Перейти | Ctrl+F / Ctrl+H / F5,
Ctrl+G |
⌘F / ⌃H / F5,
⌃G |
| Правка ячейки / Отмена ввода / Ввод и остаться | F2 / Esc / Ctrl+Enter |
⌃U или F2 / Esc /
⌘Return |
| Ввод вниз / вверх / вправо / влево | Enter / Shift+Enter / Tab /
Shift+Tab |
то же |
| Новая строка в ячейке | Alt+Enter |
⌥Return или ⌃⌥Return |
| Заполнить вниз / вправо / Мгновенное заполнение | Ctrl+D / Ctrl+R / Ctrl+E |
⌘D / ⌘R / ⌃E |
| Автосумма | Alt+= |
⌘⇧T |
| Вставить функцию / Мастер функций | Shift+F3 |
⇧F3 |
| Вставить имя / Диспетчер имён / Создать имена из выделенного | F3 / Ctrl+F3 /
Ctrl+Shift+F3 |
F3 / ⌃F3 / ⌃⇧F3 |
| Переключить тип ссылки ($) | F4 |
⌘T или F4 |
| Вычислить фрагмент формулы / Повторить действие | F9 в строке формул / F4,
Ctrl+Y |
F9 / ⌘Y |
| Пересчитать книгу / лист / всё / перестроить | F9 / Shift+F9 / Ctrl+Alt+F9 /
Ctrl+Shift+Alt+F9 |
F9 / ⇧F9 / ⌘⌥F9 /
⌘⇧⌥F9 |
| Показать формулы | Ctrl+` |
⌃` |
| Формат ячеек | Ctrl+1 |
⌘1 |
| Полужирный / Курсив / Подчёркнутый / Зачёркнутый | Ctrl+B / Ctrl+I / Ctrl+U /
Ctrl+5 |
⌘B / ⌘I / ⌘U /
⌘⇧X |
| Формат: Общий / Числовой / Денежный / Процентный / Экспоненциальный / Дата / Время | Ctrl+Shift+~ / ! / $ /
% / ^ / # / @ |
⌃⇧~ / ! / $ / %
/ ^ / # / @ |
| Внешние границы / Убрать границы | Ctrl+Shift+& / Ctrl+Shift+_ |
⌘⌥0 / ⌘⌥- |
| Вставить дату / время | Ctrl+; / Ctrl+Shift+; |
⌃; / ⌘; |
| Вставить гиперссылку / Создать таблицу / Создать диаграмму (объект) / на новом листе | Ctrl+K (в ячейке) / Ctrl+T /
Alt+F1 / F11 |
⌘K / ⌘T / fn⌥F1 /
fn F11 |
| Вставить / удалить ячейки, строки, столбцы | Ctrl+Shift+= / Ctrl+- |
⌘⇧= / ⌘- |
| Скрыть строки / столбцы; показать | Ctrl+9 / Ctrl+0; Ctrl+Shift+9
/ Ctrl+Shift+0 |
⌃9 / ⌃0; ⌃⇧9 /
⌃⇧0 |
| Группировать / Разгруппировать | Alt+Shift+→ / Alt+Shift+← |
⌘⇧K / ⌘⇧J |
| Автофильтр вкл/выкл / Повторить фильтр / Меню фильтра | Ctrl+Shift+L / Ctrl+Alt+L /
Alt+↓ |
⌘⇧F / ⌘⌥L / ⌥↓ |
| Выделить столбец / строку / всё (область → лист) / текущую область / массив | Ctrl+Пробел / Shift+Пробел /
Ctrl+A / Ctrl+Shift+* /
Ctrl+/ |
⌃Space / ⇧Space / ⌘A /
⌃⇧* / ⌃/ |
| Влияющие / Зависимые ячейки | Ctrl+[ / Ctrl+] |
⌃[ / ⌃] |
| Переход к краю блока / с выделением | Ctrl+стрелки / Ctrl+Shift+стрелки |
⌘стрелки / ⌘⇧стрелки |
| Начало строки / A1 / последняя ячейка | Home / Ctrl+Home /
Ctrl+End |
fn← / ⌃Home, fn⌃← /
⌃End, fn⌃→ |
| Страница вниз/вверх; экран вправо/влево | PgDn / PgUp; Alt+PgDn /
Alt+PgUp |
fn↓ / fn↑; ⌥fn↓ /
⌥fn↑ |
| Следующий / предыдущий лист | Ctrl+PgDn / Ctrl+PgUp |
⌥→ / ⌥← (или ⌃fn↓ /
⌃fn↑) |
| Новый лист | Shift+F11 |
⇧F11 или fn⇧F11 |
| Следующее окно / книга | Ctrl+F6, Ctrl+Tab |
⌘~ |
| Расширить выделение (режим) / Добавить к выделению | F8 / Shift+F8 |
F8 / ⇧F8 |
| Выделить до начала / конца строки | Shift+Home / Shift+End (End-режим) |
⇧fn← / ⇧fn→ |
| Удалить содержимое / Удалить до конца строки в ячейке | Delete / Ctrl+Delete |
fn⌫ / ⌃fn⌫ |
| Добавить обсуждение / Примечание / Показать все примечания | Ctrl+Shift+M / Shift+F2 / — |
⌘⇧M / ⇧F2 / — |
| Орфография / Тезаурус | F7 / Shift+F7 |
F7 / ⇧F7 |
| Макрос: записать / остановить / выполнить / редактор скриптов | Alt+Shift+R / Alt+Shift+S /
Alt+F8 / Alt+F11 |
⌥⇧R / ⌥⇧S / ⌥F8 /
⌥F11 |
| Сводная таблица (создать) / Обновить сводную | Alt+N, V (подсказки клавиш) / Alt+F5 |
— / ⌘⌥R |
| Закрепить области / Разделить | Alt+W, F, F / Alt+W, S |
⌘⌥F (своя) / — |
| Масштаб: увеличить / уменьшить / 100 % | Ctrl+колесо, Ctrl+Alt+= /
Ctrl+Alt+- / Ctrl+Alt+0 |
⌘колесо, ⌘⌥= / ⌘⌥- /
⌘⌥0 |
| Свернуть ленту / Компактный режим / Фокус | Ctrl+F1 / Ctrl+Shift+F1 /
Ctrl+Shift+F (своя) |
⌘⌥R / ⌘⇧⌥R / ⌘⌃F |
| Переключение листов по номеру | Ctrl+Shift+1…9 (своя, отключаема) |
⌘⌃1…9 |
| Строка формул: развернуть / форматировать формулу | Ctrl+Shift+U / Ctrl+Shift+F (в режиме
правки) |
⌃⇧U / ⌃⇧F |
| Автодополнение: принять / следующий аргумент / имена аргументов | Tab / Ctrl+Shift+A /
Ctrl+Shift+A |
Tab / ⌃⇧A |
| Выбрать из раскрывающегося списка (автозавершение по столбцу) | Alt+↓ |
⌥↓ |
| Подсказки клавиш ленты | Alt или F10 |
⌃⌥ (своя) |
| Справка / Что нужно сделать | F1 / Ctrl+K |
⌘/, F1 |
Контекстное меню — Shift+F10 / ⌃Return;
мини-панель — при выделении с правой кнопкой. Все сочетания
переопределяются в «Параметры → Клавиатура» (экспорт/импорт схемы JSON);
конфликты с системными (⌘H, ⌘Space) обходятся
по умолчанию.
9. Критерии готовности 1.0
Измеряются автоматически в CI (bench/sheets/), на
эталонной машине (8 ядер, 16 ГБ, NVMe) по трём ОС; «корпус» —
corpus/sheets/ (10 000+ реальных xlsx/xls/ods из открытых
источников + синтетические).
| # | Критерий | Метрика / порог |
|---|---|---|
| 1 | Совместимость формул | корпус формул corpus/formulas/ (≥ 25 000 формул с
эталонными значениями из Excel 365): совпадение ≥ 99,5 % (с допуском
1e-9 относительной погрешности для чисел; известные расхождения — в
белом списке с обоснованием, ≤ 0,2 %) |
| 2 | Каталог функций | все 317 P0 реализованы, покрыты unit-тестами (≥ 5 случаев на функцию, включая ошибки и граничные), локализованы для ru/en |
| 3 | Пересчёт | 1 000 000 ячеек с формулами (=A1*2+B1, цепочки глубиной
100, SUMIFS по 100k) — полный пересчёт < 1
с; инкрементальный после одного изменения с 10 000 зависимых —
< 50 мс; 8 потоков — ускорение ≥ 4× против 1 потока |
| 4 | Открытие файлов | xlsx 100 МБ (1M строк × 20 столбцов, 10 листов) — < 5 с до интерактивности (ленивая загрузка остальных листов допустима, полная — < 15 с); xlsx 10 МБ — < 1 с; сохранение 100 МБ — < 10 с |
| 5 | Отрисовка | прокрутка листа 1M строк — ≥ 60 FPS на 4K, 100 % масштаб, с УФ и переносом; первый кадр после открытия — < 100 мс после загрузки модели |
| 6 | Сортировка / фильтр | 1M строк × 10 столбцов, ключ текст+число — сортировка < 1 с, автофильтр по значению < 300 мс, удаление дубликатов < 1 с |
| 7 | Сводные | кэш 1M записей × 10 полей — построение < 3 с, обновление сводной с 3 уровнями строк — < 500 мс |
| 8 | Импорт CSV | 1 ГБ (10M × 10) — < 20 с в большом режиме; 100 МБ — < 3 с; детект кодировки/разделителя — ≥ 98 % верно на корпусе CSV (500 файлов) |
| 9 | Точность рендера | снимочные тесты 2 000 xlsx: отличие от Excel-рендера (PDF-печать) ≤ 1 % пикселей по страницам с текстом, 100 % совпадение набора видимых ячеек/значений |
| 10 | Round-trip | xlsx → msx → xlsx: структурная эквивалентность (нормализованный XML) для ≥ 98 % корпуса; неизвестные части сохранены в 100 % |
| 11 | Память | 1M заполненных числовых ячеек — ≤ 60 МБ RSS сверх базовой; текстовых (10 уникальных) — ≤ 80 МБ |
| 12 | Надёжность | фаззинг парсеров (xlsx, xls, ods, csv, формулы, коды форматов) ≥ 72 ч без падений; восстановление после сбоя — потеря ≤ 2 с правок |
| 13 | Скрипты | API §6 реализован; 8 примеров проходят как интеграционные тесты; запись действий воспроизводит 50 сценариев без правок |
| 14 | Доступность | VoiceOver/NVDA/Orca читают ячейки, заголовки, формулы; все команды доступны с клавиатуры; контраст AAA |
| 15 | Печать | 200 файлов корпуса: разбивка на страницы совпадает с Excel в ≥ 95 % случаев (число страниц и границы) |
| 16 | Локализация | ru/en интерфейс и имена функций; разделители по локали; 1 000 формул ru↔︎en без искажений |
| 17 | Лицензирование | Features::has для Assist/Base-коннекторов/скриптов из
магазина; Free работает без аккаунта |
10. Риски
| Риск | Вероятность / влияние | Снижение |
|---|---|---|
| Семантика Excel в мелочах (приведение типов, даты, критерии, округление, порядок ошибок) — бесконечный хвост расхождений | высокая / высокое | корпус формул с эталонами из реального Excel, автогенерация тестов из документации OOXML, белый список расхождений с обоснованием; ранний выпуск беты для сбора файлов |
Производительность пересчёта на «плохих» книгах (волатильные, тысячи
VLOOKUP по целым столбцам, УФ по всему листу) |
средняя / высокое | хэш-кэши поиска, динамические зависимости вместо волатильности, профайлер в UI, ограничение УФ видимой областью |
| Сводные таблицы: объём семантики (макеты, итоги, «показать как», совместимость кэшей xlsx) | высокая / среднее | поэтапно: P0 — основные макеты и агрегаты, кэш совместим с xlsx; тесты на корпусе сводных |
| Диаграммы: зависимость от Meridian Chart (общий модуль, параллельная разработка) | средняя / высокое | заморозить API Chart к середине фазы 2; базовые типы — первыми; сводные диаграммы — после |
Память Value 24 Б и Arc<str> при
10M+ ячеек |
средняя / среднее | бенчмарк на фазе 0/1; при необходимости ThinArc/SSO;
большой режим с ленивыми чанками |
| Многопоточный пересчёт: гонки, недетерминизм при циклах и волатильных | средняя / высокое | компоненты независимы по построению, запись через буферы, детерминированный порядок коммита, фаззинг графа |
| xls (BIFF8) и xlsb — бинарные форматы с плохой документацией | средняя / среднее | использовать опыт LibreOffice/calamine, фаззинг, запись
xls — P1 |
| Flash Fill и Assist — ожидания «магии» | средняя / низкое | честный предпросмотр, отмена, ограничения в документации |
| Solver: сходимость GRG на реальных моделях | средняя / среднее | мультистарт, эталонные задачи (Excel Solver samples), эволюционный как запасной |
| Локализация имён функций: конфликты и неоднозначности (ru-имена Excel меняются между версиями) | низкая / среднее | таблица из Excel 365 как источник, принимать оба варианта при вводе |
| CRDT для таблиц (фаза 6): вставки строк и одновременные сортировки | высокая / высокое (позже) | стабильные RowId, запрет сортировки при наличии
несинхронизированных правок, прототип в фазе 2 на модели операций |
Совместная работа ядра с Write/Slides (общий
grid-model) — конфликт требований |
низкая / среднее | ранний интерфейс «встроенная таблица» с подмножеством возможностей |
11. Вопросы к библии
- Скрипты в редакции Free. В §9.1 Free — «без скриптов из магазина», Home — «+ скрипты». Нужно решение: доступны ли в Free запись действий, запуск скриптов книги и UDF (предлагаем: да, без магазина и расписаний; иначе Free-пользователь не откроет книгу коллеги с UDF корректно).
- Имена крейтов. Библия задаёт
kernel/crates/meridian-*по примеруmeridian-license; здесь использованыmeridian-grid,meridian-calc,meridian-pivot,meridian-numfmt,meridian-solver— просьба закрепить в03-architecture.md. - Документы ядра. Ссылки на
docs/algorithms/chart-engine.md,docs/03-architecture.md(раздел MeridianScript),docs/03-architecture.md(раздел о совместной работе) — таких файлов в плане библии нет (есть03-architecture.md,04-file-formats.md,08-roadmap.md,10-licensing-accounts.md); предлагаем добавить папкуdocs/kernel/для общих модулей. - Бинарные части в MOP. §3.1 описывает
XML-содержимое; для листов > 1M ячеек предложены столбцовые бинарные
части (
sheets/sheetN.cells, CBOR+zstd) с флагом в манифесте — требуется подтверждение, что это допустимо для «простого и строгого» MOP, либо ограничиться XML с потоковой записью (медленнее в ~3–4 раза на больших книгах). - Большой режим (2^31 строк) меняет ограничение «как Excel» — подтвердить как P1 и зафиксировать в §1.1 (сегмент «большие таблицы»).
- Горячие клавиши. В библии
Ctrl+K— командная палитра; в ExcelCtrl+K— гиперссылка. Здесь: палитра всегда, гиперссылка поCtrl+Kтолько внутри правки ячейки / через ленту — подтвердить общее правило для всех приложений. - Поведение INDIRECT/OFFSET (динамические зависимости вместо волатильности) — отличие от Excel с тем же результатом; просьба зафиксировать как принцип «точнее Excel при равных результатах допустимо» в разделе принципов продукта.