Skip to content

Repository files navigation

open-bsl

open-bsl — интерпретатор встроенного языка 1С:Предприятия (BSL), написанный на Rust. Исходный текст проходит обычный для интерпретатора конвейер: разбор, семантический анализ, компиляцию в регистровый байт-код и исполнение в виртуальной машине.

Проект находится на ранней стадии разработки. Основные конструкции языка уже работают, однако совместимость с 1С пока неполная, а публичный API крейтов может меняться.

Быстрый старт

Для сборки нужен Rust с поддержкой редакции 2021.

cargo build --workspace
cargo run -p bsl-cli -- path/to/script.bsl

Без имени файла запускается интерактивная оболочка:

cargo run -p bsl-cli

Всё, что идёт после имени скрипта, доступно из кода массивом строк АргументыКоманднойСтроки (синоним — CommandLineArguments; скобки необязательны, как в OneScript):

cargo run -p bsl-cli -- path/to/script.bsl арг1 "арг 2"

Список параметров командной строки:

cargo run -p bsl-cli -- --help

Перед отправкой изменений рекомендуется выполнить все проверки:

cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace

Устройство проекта

Код разделён на несколько крейтов по этапам обработки программы:

крейт назначение
bsl-syntax лексер, парсер, AST и синтаксические диагностики
bsl-sema разрешение имён и семантическое представление программы
bsl-bytecode компилятор и формат байт-кода
bsl-vm виртуальная машина и экспериментальный JIT
bsl-rt значения BSL, коллекции и встроенные функции
bsl-number десятичная арифметика
bsl-format правила преобразования значений в строки
bsl-cli запуск файлов, REPL и conformance-тесты

Ядро интерпретатора почти не использует внешних библиотек. bsl-number зависит от num-bigint и num-traits, а rustyline нужен только командной строке.

REPL

Интерактивная оболочка сохраняет переменные между введёнными фрагментами. Доступны подсветка синтаксиса и дополнение по Tab. Набор подсказок зависит от контекста: после точки предлагаются методы, после Новый — типы, в остальных случаях — ключевые слова, встроенные функции и переменные текущей сессии.

Поиск не зависит от регистра: например, стрн дополняется до СтрНайти. Повторное нажатие Tab выводит все подходящие варианты.

Цвет можно отключить переменной окружения NO_COLOR=1. Если терминал не поддерживает сырой режим, REPL переходит к обычному построчному вводу.

Байт-код

Скомпилированную программу можно вывести в текстовом виде и затем исполнить:

cargo run -p bsl-cli -- --emit-bytecode script.bsl
cargo run -p bsl-cli -- --emit-bytecode script.bsl out.bslc
cargo run -p bsl-cli -- --run-bytecode out.bslc

Вывод --emit-bytecode — не отладочный отчёт, а входной формат для --run-bytecode. Его можно изучать и править вручную; всё после ; считается комментарием.

  .handlers 1
    0 3 11 12  ; Попытка 3..11 -> обработчик 12
  .code 14
    0000 LoadConst dst=1 k=0  ; = 5
    0002 NewStructure dst=0 shape=0 base=1 count=2  ; поля: цена, количество
    0004 GetProp dst=4 obj=5 name=0  ; .цена

Формат удобен при разборе работы компилятора: по нему видно короткое замыкание логических операторов, распределение регистров и выбранные варианты инструкций. Стабильность формата между версиями не гарантируется; номер версии проверяется при загрузке.

JIT

На Linux x86-64 программу можно запустить с экспериментальным JIT-компилятором:

cargo run -p bsl-cli --release -- --jit script.bsl

JIT переводит в машинный код арифметику, сравнения, присваивания и переходы. Остальные инструкции по-прежнему исполняет виртуальная машина. Поэтому режим помогает главным образом программам с вычислительно насыщенными циклами и почти не влияет на код, который большую часть времени работает со строками, коллекциями или файлами.

На других платформах параметр --jit принимается, но используется обычный интерпретатор. Корпус тестов отдельно проверяет, что результаты обоих режимов совпадают.

Встраивание

bsl-cli — один из клиентов интерпретатора, а не обязательная его часть. Крейты пока не опубликованы на crates.io, поэтому их следует подключать по пути или через git:

[dependencies]
bsl-syntax   = { path = "../open-bsl/crates/bsl-syntax" }
bsl-sema     = { path = "../open-bsl/crates/bsl-sema" }
bsl-bytecode = { path = "../open-bsl/crates/bsl-bytecode" }
bsl-vm       = { path = "../open-bsl/crates/bsl-vm" }
bsl-rt       = { path = "../open-bsl/crates/bsl-rt" }
bsl-format   = { path = "../open-bsl/crates/bsl-format" }
# Нужен, если приложение само создаёт числовые значения BSL:
bsl-number   = { path = "../open-bsl/crates/bsl-number" }

Минимальный запуск состоит из четырёх этапов. У каждого свой тип ошибки, так что приложение может сообщить пользователю, где именно возникла проблема.

use bsl_rt::BslValue;

fn run_script(src: &str) -> Result<BslValue, String> {
    let parsed = bsl_syntax::parse(src)
        .map_err(|e| format!("синтаксическая ошибка: {e:?}"))?;
    let resolved = bsl_sema::resolve_program(&parsed.items)
        .map_err(|e| format!("семантическая ошибка: {e:?}"))?;
    let program = bsl_bytecode::compile_program(&resolved)
        .map_err(|e| format!("ошибка компиляции: {e:?}"))?;
    bsl_vm::run_program(&program)
        .map_err(|e| format!("ошибка выполнения: {e}"))
}

run_program возвращает значение верхнеуровневого оператора Возврат, а при его отсутствии — Неопределено. Для представления значений используется bsl_rt::BslValue. Пользовательский вывод следует формировать через bsl_format::format_value: реализация Display предназначена для отладки и не воспроизводит форматирование 1С.

Для REPL-подобных сценариев существуют resolve_snippet_stmts, compile_snippet и run_repl_chunk. Они позволяют передать начальные значения локальных переменных и получить их новое состояние после исполнения. Пример такой сессии находится в crates/bsl-cli/src/repl.rs.

У встраивания сейчас есть несколько существенных ограничений:

  • BslValue и скомпилированная программа используют Rc и RefCell, поэтому не реализуют Send и Sync;
  • лимитов времени, памяти и числа инструкций нет;
  • Сообщить пишет в stdout, готового интерфейса для перенаправления вывода нет;
  • ЗаписьТекста может создавать файлы по указанному программой пути;
  • реестра пользовательских встроенных функций пока нет;
  • публичные интерфейсы не считаются стабильными.

Недоверенные программы лучше запускать в отдельном процессе с ограничениями по времени, памяти и доступу к файловой системе.

Совместимость с 1С

Целевая реализация для проекта — платформа 1С, а не OneScript. Там, где поведение языка нельзя надёжно вывести из документации, оно проверяется на реальной платформе.

Непроверенное предположение отмечается в трёх местах:

  1. комментарием // НЕ ИЗМЕРЕНО(ОБЛАСТЬ.ВОПРОС) рядом с реализацией;
  2. записью в crates/bsl-rt/src/open_questions.rs;
  3. примером в tests/conformance/measure/measure-all.bsl.

Согласованность этих списков проверяет тест open_questions_registry_matches_source_markers.

Замеры на установленной платформе запускаются так:

./tests/conformance/measure/1c/run-on-1c.sh
cargo run -p bsl-cli -- \
  --ingest-measurements tests/conformance/measure/platform.tsv

Первый скрипт создаёт временную файловую базу и запускает внешнюю обработку. Платформа может показать предупреждение о небезопасном действии и ждать подтверждения пользователя; особенности настройки описаны в комментариях к run-on-1c.sh.

Команда --ingest-measurements сохраняет результат и выводит найденные расхождения, но не меняет реализацию автоматически. Эталонные файлы также не создаются из вывода самого open-bsl.

Фикстуры conformance находятся в tests/conformance/fixtures/. Файл без соответствующего .expected считается ещё не измеренным и пропускается. Получить сводку можно командой:

cargo test -p bsl-cli -- --nocapture

Подтверждённые особенности чисел и форматирования

Эти результаты получены на платформе 1С и используются как эталоны:

1/3                     -> 0,333333333333333333333333333
2/3                     -> 0,666666666666666666666666667
10/3                    -> 3,333333333333333333333333333
1/268435456             -> 0,000000003725290298461914063
Sqrt(2)                 -> 1,4142135623731
1.10 * 1.00             -> 1,1
Pow(10, 30)             -> 1 000 000 000 000 000 000 000 000 000 000
СтрДлина(Строка(1/3))   -> 29
Строка(1000.5)          -> 1 000,5
КодСимвола(разделитель) -> 160
Строка(Истина)          -> Да
Строка(Новый Массив)    -> Массив

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

Открытые вопросы, включая поведение Sqrt на малых аргументах, перечислены в open_questions.rs.

Производительность

Сценарии находятся в каталоге benchmarks. Для большинства из них есть версии на BSL и Lua. Скрипт измеряет только время своей основной работы, не включая запуск процесса, и последней строкой печатает результат в миллисекундах.

./benchmarks/run.sh          # все сценарии, медиана пяти запусков
./benchmarks/run.sh "" 7     # все сценарии, семь запусков
./benchmarks/run.sh str_find 9

Результаты зависят от компьютера и версий исполнителей, поэтому приведённые в репозитории числа не стоит воспринимать как универсальный рейтинг. Кроме того, сравниваемые среды заметно различаются: в BSL используется точная десятичная арифметика и строки UTF-16, Lua обычно работает с double и байтовыми строками, а LuaJIT компилирует программу. Подробное описание сценариев и актуальные результаты находятся в benchmarks/README.md.

Лицензия

Код распространяется на условиях GNU General Public License версии 3 или любой более поздней версии. Полный текст приведён в файле COPYING.

1С:Предприятие — продукт фирмы «1С». Проект open-bsl не связан с фирмой «1С».

About

Интерпретатор BSL (встроенного языка «1С:Предприятия») на Rust с регистровой виртуальной машиной.

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages