A fork of OneScript 2.2.0 that stops taking a # inside strings and comments of an inactive #Если branch for a preprocessor directive
Project page with the test and measurement charts: ivanbokhan84.github.io/OneScript.
OneScript is an independent cross-platform virtual machine that runs scripts written in the 1C:Enterprise language, without the 1C:Enterprise platform. Its oscript -check is also a handy syntax check for 1C modules. This fork keeps everything OneScript 2.2.0 does and fixes one preprocessor bug that shows up exactly there: code in an inactive #Если branch that contains a # in a string or a comment.
Maintained by Ivan Bokhan, on top of OneScript 2.2.0 by Andrei Ovsiankin (EvilBeaver) and the OneScript contributors — see Credits.
Upstream documentation: README-EN.md (English), README-RU.md (Russian), oscript.io.
One method changes: FindHashSign in src/OneScript.Language/SyntaxAnalysis/ConditionalDirectiveHandler.cs. Regression tests are added to src/Tests/OneScript.Language.Tests/PreprocessorTests.cs. Nothing else in the engine is touched.
#Если Клиент Тогда
А = "цвет #000000";
#КонецЕсли| Engine | oscript -check |
|---|---|
| OneScript 1.9.4 | No errors. |
| OneScript 2.2.0 | Ошибка в строке: 2,12 / Ожидается директива препроцессора |
| this fork | No errors. |
Клиент is not defined in OneScript, so the branch is inactive and its text should not be parsed at all. When an #Если condition is false, ConditionalDirectiveHandler skips the branch without lexing it and looks for the next #. FindHashSign checked SourceCodeIterator.OnNewLine, but that flag is updated only when a lexeme is read. After ReadToLineEnd it stayed true for the whole skipped branch, so any # — in a string, in a comment, after code — was taken for a directive.
The same root cause produced other symptoms:
| Text in an inactive branch | OneScript 2.2.0 | 1.9.4 and this fork |
|---|---|---|
А = "#КонецЕсли"; |
false error: the block is closed early, the rest of the line is parsed as code | no errors |
|#КонецЕсли in a multi-line string |
false error Неизвестный символ | |
no errors |
// #КонецЕсли and no real #КонецЕсли |
false success | Ожидается директива препроцессора #КонецЕсли |
А = 1; #КонецЕсли and no real #КонецЕсли |
false success | Ожидается директива препроцессора #КонецЕсли |
LF file: a #КонецОбласти line right before #КонецЕсли |
false error Недопустимое начало директивы препроцессора |
no errors |
The last case is a second defect in the same function. FindHashSign stepped over the newline under the iterator with MoveNext, so SkipSpaces never saw it and the private _onNewLine flag stayed false; the directive lexer then rejected the real #КонецЕсли. With CRLF the iterator stands on \r at that point, which is why Windows files were not affected.
A # is a directive only when it is the first non-whitespace character of a line — the same rule the lexer applies to active code. The start of a line is detected by a change of CurrentLine, and every whitespace character, including the one under the iterator on entry, goes through SkipSpaces, so the directive lexer sees the new line. The contract of SourceCodeIterator is unchanged, the text of an inactive branch is still not parsed, and errors in active code stay errors.
- Tests first. 28 new tests in
PreprocessorTests(14 scenarios, CRLF and LF): strings, doubled quotes, multi-line strings, comments, indentation, nested blocks,#ИначеЕсли/#Иначе, a missing#КонецЕсли, and active code. On the upstream code 23 of them fail. With the fix all 198 tests ofOneScript.Language.Testspass. - Scripted tests.
tests/preprocessor-skipped-branch.osruns under the upstreamtestrunner.os: 14 scenarios, each compiled from a string with CRLF and with LF line endings. This fork passes 14 of 14, OneScript 2.2.0 passes 4 (every "must compile" scenario and both false-success scenarios fail), OneScript 1.9.4 passes 13: it skips#000000at the start of a line in an inactive branch. - Control build. The same commit without the fix was built with the same command, to separate the fix from the build environment. It behaves exactly like the official 2.2.0 in every check below.
- Engine regression. The other unit test projects pass.
tests/testrunner.os -runallgives the same result for every upstream test as the control build (1,092 tests); with the new file it runs 1,106. - Real code. 11,275 modules of a 1C:Enterprise 8.3 configuration and its archived builds were checked with
oscript -checkby both engines. The official 2.2.0 reported a false preprocessor error in 90 of them (30 distinct modules): colours like#000000, code templates like"&&$##", query text placeholders like#Поле. With the fix these errors are gone, and on each of the 90 modules the result and the error line match OneScript 1.9.4. No module got worse. Check time per file did not change.
oscript -check stops at the first error, and on 1C modules that is usually Неизвестный символ — the 1C global context does not exist in OneScript. A false preprocessor error is visible only when it comes first, so 90 is a lower bound.
-check stops at the first error, and on 1C modules the first error is usually an unknown global name (Справочники, Документы, another common module) that OneScript does not have. The code generator checks after that point — wrong argument count for the module's own methods, a procedure used as a function, a duplicate method, labels — never run. -check also starts the engine once per file.
This fork adds -checkall:
oscript -encoding=utf-8 -checkall <file or folder> [...]
<file>: Ошибка в строке: N,M / message every error of the module
<file>: No errors.
<file>: unknown symbols: K (...) the 1C global context, not an error
Files: X, with errors: Y, not checked: Z
Exit code: 0 no errors, 1 errors, 2 a file could not be checked.
Folders are walked recursively for *.bsl and *.os. The engine starts once for all files, errors go to a collecting sink, and unknown symbols are listed instead of stopping the check. -check itself is unchanged.
Measured on 29 September 2026 (short runs on one loaded machine):
-checkall 2.2.0-vanteam.2 |
-check 2.2.0 |
-check 1.9.4 |
|
|---|---|---|---|
| 76 modules of a 1C configuration | 2.4 s, one process | 80 s | 50 s |
| one 155 KB module | 0.61 s | 0.69 s | 0.51 s |
| 116 real modules checked to the end | 116 | 10 | 10 |
| code generator errors after an unknown global, of 4 | 4 | 0 | 0 |
| syntax errors after an unknown global, of 10 | 10 | 10 | 0 |
The 2.2.0-vanteam.2 build is also published with ReadyToRun, which makes plain -check about a third faster than the official 2.2.0 (0.48 s against 0.69 s on the same module).
Requires the .NET 8 SDK or newer.
git clone https://github.com/ivanbokhan84/OneScript.git
cd OneScript
dotnet test src/Tests/OneScript.Language.Tests/OneScript.Language.Tests.csproj -c Release -p:Platform=AnyCPU
cd tests && ../dist/bin/oscript testrunner.os -run preprocessor-skipped-branch.os && cd ..
dotnet publish src/oscript/oscript.csproj -r win-x64 --self-contained -c Release -p:VersionPrefix=2.2.0 -p:VersionSuffix=vanteam.2 -p:PublishReadyToRun=true -o dist/binThis builds oscript only — enough for oscript -check. For a full distribution, including the C++ Native API component and the standard library packages, use the upstream Build.csproj targets.
- A
#that starts a line in an inactive branch is still a directive:#000000alone on a line is an error, as in 2.2.0. OneScript 1.9.4 skips such a line. The line-start rule is kept on purpose. - The fix has not been submitted upstream yet.
Mozilla Public License 2.0, the same as upstream. Modified files keep their license headers. The engine changes are two commits on top of v2.2.0: the preprocessor fix and -checkall; the other commits add tests and documentation.
- OneScript by Andrei Ovsiankin (EvilBeaver) and the OneScript contributors.
- The bug was found while building Vanteam BSL Check, which uses
oscript -checkas the quick level of its checks.
