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 нужен только командной
строке.
Интерактивная оболочка сохраняет переменные между введёнными фрагментами.
Доступны подсветка синтаксиса и дополнение по 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 ; .цена
Формат удобен при разборе работы компилятора: по нему видно короткое замыкание логических операторов, распределение регистров и выбранные варианты инструкций. Стабильность формата между версиями не гарантируется; номер версии проверяется при загрузке.
На Linux x86-64 программу можно запустить с экспериментальным JIT-компилятором:
cargo run -p bsl-cli --release -- --jit script.bslJIT переводит в машинный код арифметику, сравнения, присваивания и переходы. Остальные инструкции по-прежнему исполняет виртуальная машина. Поэтому режим помогает главным образом программам с вычислительно насыщенными циклами и почти не влияет на код, который большую часть времени работает со строками, коллекциями или файлами.
На других платформах параметр --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С, а не OneScript. Там, где поведение языка нельзя надёжно вывести из документации, оно проверяется на реальной платформе.
Непроверенное предположение отмечается в трёх местах:
- комментарием
// НЕ ИЗМЕРЕНО(ОБЛАСТЬ.ВОПРОС)рядом с реализацией; - записью в
crates/bsl-rt/src/open_questions.rs; - примером в
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С».