opencrbasic 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. opencrbasic-1.0.0/.gitignore +15 -0
  2. opencrbasic-1.0.0/LICENSE +21 -0
  3. opencrbasic-1.0.0/PKG-INFO +223 -0
  4. opencrbasic-1.0.0/README.md +206 -0
  5. opencrbasic-1.0.0/pyproject.toml +84 -0
  6. opencrbasic-1.0.0/src/opencrbasic/__init__.py +3 -0
  7. opencrbasic-1.0.0/src/opencrbasic/__main__.py +5 -0
  8. opencrbasic-1.0.0/src/opencrbasic/analysis.py +184 -0
  9. opencrbasic-1.0.0/src/opencrbasic/bindings.py +1034 -0
  10. opencrbasic-1.0.0/src/opencrbasic/catalog.py +173 -0
  11. opencrbasic-1.0.0/src/opencrbasic/checkconfig.py +144 -0
  12. opencrbasic-1.0.0/src/opencrbasic/checker/__init__.py +977 -0
  13. opencrbasic-1.0.0/src/opencrbasic/checker/cr3xx.py +3118 -0
  14. opencrbasic-1.0.0/src/opencrbasic/checker/ess.py +81 -0
  15. opencrbasic-1.0.0/src/opencrbasic/checker/expressions.py +3581 -0
  16. opencrbasic-1.0.0/src/opencrbasic/checker/instr_data.py +1802 -0
  17. opencrbasic-1.0.0/src/opencrbasic/checker/instr_measure.py +1547 -0
  18. opencrbasic-1.0.0/src/opencrbasic/checker/instr_rules.py +3540 -0
  19. opencrbasic-1.0.0/src/opencrbasic/checker/instructions.py +17 -0
  20. opencrbasic-1.0.0/src/opencrbasic/checker/intervals.py +678 -0
  21. opencrbasic-1.0.0/src/opencrbasic/checker/longconv.py +133 -0
  22. opencrbasic-1.0.0/src/opencrbasic/checker/scan_time.py +580 -0
  23. opencrbasic-1.0.0/src/opencrbasic/checker/storage.py +456 -0
  24. opencrbasic-1.0.0/src/opencrbasic/checker/structs.py +240 -0
  25. opencrbasic-1.0.0/src/opencrbasic/checker/structure.py +6213 -0
  26. opencrbasic-1.0.0/src/opencrbasic/checker/symbols.py +609 -0
  27. opencrbasic-1.0.0/src/opencrbasic/checker/terminals.py +249 -0
  28. opencrbasic-1.0.0/src/opencrbasic/checker/unused.py +186 -0
  29. opencrbasic-1.0.0/src/opencrbasic/checker/var_types.py +564 -0
  30. opencrbasic-1.0.0/src/opencrbasic/checker/warnings.py +661 -0
  31. opencrbasic-1.0.0/src/opencrbasic/checks.py +491 -0
  32. opencrbasic-1.0.0/src/opencrbasic/cli.py +391 -0
  33. opencrbasic-1.0.0/src/opencrbasic/commands.py +128 -0
  34. opencrbasic-1.0.0/src/opencrbasic/compiler.py +320 -0
  35. opencrbasic-1.0.0/src/opencrbasic/consteval.py +327 -0
  36. opencrbasic-1.0.0/src/opencrbasic/data/background_calibration.json +4 -0
  37. opencrbasic-1.0.0/src/opencrbasic/data/before_beginprog.json +99 -0
  38. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/background_calibration.json +4 -0
  39. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/before_beginprog.json +138 -0
  40. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/ess_variables.json +1087 -0
  41. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/fnotch.json +29 -0
  42. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/functions.json +1337 -0
  43. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/instructions.json +4836 -0
  44. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/measure.json +121 -0
  45. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/mode.json +111 -0
  46. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/pipeline_tasks.json +88 -0
  47. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/settings_fields.json +321 -0
  48. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/status_members.json +318 -0
  49. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/symbols.json +872 -0
  50. opencrbasic-1.0.0/src/opencrbasic/data/cr1000x/terminals.json +225 -0
  51. opencrbasic-1.0.0/src/opencrbasic/data/cr300/before_beginprog.json +42 -0
  52. opencrbasic-1.0.0/src/opencrbasic/data/cr300/ess_variables.json +1087 -0
  53. opencrbasic-1.0.0/src/opencrbasic/data/cr300/fnotch.json +22 -0
  54. opencrbasic-1.0.0/src/opencrbasic/data/cr300/functions.json +1261 -0
  55. opencrbasic-1.0.0/src/opencrbasic/data/cr300/instructions.json +2837 -0
  56. opencrbasic-1.0.0/src/opencrbasic/data/cr300/measure.json +33 -0
  57. opencrbasic-1.0.0/src/opencrbasic/data/cr300/mode.json +121 -0
  58. opencrbasic-1.0.0/src/opencrbasic/data/cr300/settings_fields.json +259 -0
  59. opencrbasic-1.0.0/src/opencrbasic/data/cr300/status_members.json +303 -0
  60. opencrbasic-1.0.0/src/opencrbasic/data/cr300/symbols.json +584 -0
  61. opencrbasic-1.0.0/src/opencrbasic/data/cr300/terminals.json +193 -0
  62. opencrbasic-1.0.0/src/opencrbasic/data/cr350/before_beginprog.json +44 -0
  63. opencrbasic-1.0.0/src/opencrbasic/data/cr350/ess_variables.json +1087 -0
  64. opencrbasic-1.0.0/src/opencrbasic/data/cr350/fnotch.json +22 -0
  65. opencrbasic-1.0.0/src/opencrbasic/data/cr350/functions.json +1262 -0
  66. opencrbasic-1.0.0/src/opencrbasic/data/cr350/instructions.json +2848 -0
  67. opencrbasic-1.0.0/src/opencrbasic/data/cr350/measure.json +35 -0
  68. opencrbasic-1.0.0/src/opencrbasic/data/cr350/mode.json +121 -0
  69. opencrbasic-1.0.0/src/opencrbasic/data/cr350/settings_fields.json +292 -0
  70. opencrbasic-1.0.0/src/opencrbasic/data/cr350/status_members.json +303 -0
  71. opencrbasic-1.0.0/src/opencrbasic/data/cr350/symbols.json +572 -0
  72. opencrbasic-1.0.0/src/opencrbasic/data/cr350/terminals.json +190 -0
  73. opencrbasic-1.0.0/src/opencrbasic/data/ess_variables.json +1087 -0
  74. opencrbasic-1.0.0/src/opencrbasic/data/fnotch.json +34 -0
  75. opencrbasic-1.0.0/src/opencrbasic/data/functions.json +1337 -0
  76. opencrbasic-1.0.0/src/opencrbasic/data/instructions.json +4965 -0
  77. opencrbasic-1.0.0/src/opencrbasic/data/measure.json +123 -0
  78. opencrbasic-1.0.0/src/opencrbasic/data/mode.json +121 -0
  79. opencrbasic-1.0.0/src/opencrbasic/data/notices_cr6.json +890 -0
  80. opencrbasic-1.0.0/src/opencrbasic/data/pipeline_tasks.json +84 -0
  81. opencrbasic-1.0.0/src/opencrbasic/data/settings_fields.json +322 -0
  82. opencrbasic-1.0.0/src/opencrbasic/data/status_members.json +319 -0
  83. opencrbasic-1.0.0/src/opencrbasic/data/status_table.json +684 -0
  84. opencrbasic-1.0.0/src/opencrbasic/data/symbols.json +902 -0
  85. opencrbasic-1.0.0/src/opencrbasic/data/terminals.json +195 -0
  86. opencrbasic-1.0.0/src/opencrbasic/data/type_codes.json +249 -0
  87. opencrbasic-1.0.0/src/opencrbasic/diagnostics.py +196 -0
  88. opencrbasic-1.0.0/src/opencrbasic/editing.py +827 -0
  89. opencrbasic-1.0.0/src/opencrbasic/formatting.py +310 -0
  90. opencrbasic-1.0.0/src/opencrbasic/helpdocs.py +677 -0
  91. opencrbasic-1.0.0/src/opencrbasic/hover.py +667 -0
  92. opencrbasic-1.0.0/src/opencrbasic/inlay.py +169 -0
  93. opencrbasic-1.0.0/src/opencrbasic/lexer.py +183 -0
  94. opencrbasic-1.0.0/src/opencrbasic/lsp/__init__.py +5 -0
  95. opencrbasic-1.0.0/src/opencrbasic/lsp/__main__.py +7 -0
  96. opencrbasic-1.0.0/src/opencrbasic/lsp/config.py +202 -0
  97. opencrbasic-1.0.0/src/opencrbasic/lsp/features.py +602 -0
  98. opencrbasic-1.0.0/src/opencrbasic/lsp/server.py +741 -0
  99. opencrbasic-1.0.0/src/opencrbasic/lsp/session.py +1189 -0
  100. opencrbasic-1.0.0/src/opencrbasic/lsp/worker.py +128 -0
  101. opencrbasic-1.0.0/src/opencrbasic/mode.py +143 -0
  102. opencrbasic-1.0.0/src/opencrbasic/navigation.py +401 -0
  103. opencrbasic-1.0.0/src/opencrbasic/parser.py +3264 -0
  104. opencrbasic-1.0.0/src/opencrbasic/positions.py +274 -0
  105. opencrbasic-1.0.0/src/opencrbasic/preprocess.py +269 -0
  106. opencrbasic-1.0.0/src/opencrbasic/profiles.py +266 -0
  107. opencrbasic-1.0.0/src/opencrbasic/provenance.py +17 -0
  108. opencrbasic-1.0.0/src/opencrbasic/quickfix.py +331 -0
  109. opencrbasic-1.0.0/src/opencrbasic/rename.py +662 -0
  110. opencrbasic-1.0.0/src/opencrbasic/signature.py +33 -0
  111. opencrbasic-1.0.0/src/opencrbasic/source.py +600 -0
  112. opencrbasic-1.0.0/src/opencrbasic/sources.py +125 -0
  113. opencrbasic-1.0.0/src/opencrbasic/syntax.py +337 -0
  114. opencrbasic-1.0.0/src/opencrbasic/tables.py +2173 -0
  115. opencrbasic-1.0.0/src/opencrbasic/tables_hook.py +14 -0
  116. opencrbasic-1.0.0/src/opencrbasic/tdf.py +142 -0
  117. opencrbasic-1.0.0/src/opencrbasic/timing.py +565 -0
  118. opencrbasic-1.0.0/tests/corpus/README.md +21 -0
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ .venv/
3
+ dist/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .DS_Store
7
+ .oracle-cache/
8
+ scratch/
9
+ difftest-*.json
10
+ timingtest-*.json
11
+ # Campbell and third-party material, kept local (docs/legal.md)
12
+ compiler/
13
+ extracted_chm/
14
+ examples/
15
+ tests/corpus/chm/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Utrecht University
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,223 @@
1
+ Metadata-Version: 2.5
2
+ Name: opencrbasic
3
+ Version: 1.0.0
4
+ Summary: Open-source re-implementation of the Campbell Scientific CRBasic pre-compilers (CR6, CR1000X, CR300, CR350)
5
+ Project-URL: Repository, https://github.com/UtrechtUniversity/open-crbasic
6
+ Author: Utrecht University
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Classifier: Topic :: Software Development :: Compilers
13
+ Requires-Python: >=3.15
14
+ Provides-Extra: lsp
15
+ Requires-Dist: pygls<3,>=2.1.1; extra == 'lsp'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # opencrbasic
19
+
20
+ A native re-implementation of Campbell Scientific's CRBasic pre-compilers for the CR6, CR1000X,
21
+ CR300 and CR350 dataloggers (`CR6Comp.exe`, `cr1000xcomp.exe`, `cr300comp.exe`, `cr350comp.exe`),
22
+ with a language server. It runs on Linux, macOS and Windows with only Python 3.15+, no Wine and no
23
+ Windows binaries. It is MIT licensed and not affiliated with Campbell Scientific; provenance and
24
+ what this repository deliberately leaves out are in the [legal notes](docs/legal.md). Version 1.0:
25
+ [release record](docs/release-1.0.md).
26
+
27
+ ```bash
28
+ pip install opencrbasic # crbc, the compiler
29
+ pip install "opencrbasic[lsp]" # also crbc-lsp, the language server
30
+ ```
31
+
32
+ The VS Code extension lives in [open-crbasic-vsix](https://github.com/UtrechtUniversity/open-crbasic-vsix);
33
+ agent skills for writing CRBasic in [open-crbasic-skills](https://github.com/UtrechtUniversity/open-crbasic-skills).
34
+
35
+ Status, measured against the real compilers: `PROGRESS.md`. Design and the v1.0 milestones:
36
+ [`PLAN.md`](PLAN.md#v10-milestones).
37
+
38
+ ## Scope
39
+
40
+ - **Gated profiles: CR6 and CR1000X.** On these, v1.0 gates the reference verdict in the relevant
41
+ area (the FirnMelt/GEUS instruction set and language constructs), the execution mode, and the
42
+ first error for realistic mistakes on relevant rules, measured on the FirnMelt programs, the 507
43
+ CRBasic help examples, the 66 GEUS programs, the saved fuzz replay and fresh `--relevant` fuzz
44
+ seeds. The help examples and GEUS programs are not redistributed here; see
45
+ [tests/corpus/README.md](tests/corpus/README.md) for running against local copies. One relevant-area false accept is known: a `Sub` name that is also another routine's
46
+ parameter, used as a value. CR300 and CR350 are best effort: measured and kept, not gated (human
47
+ decision 2026-10-24).
48
+ - **Outputs.** stdout, the message file, `-s`, `-u`, `-p`/`-i`, `-l`/`-c` and the CR6 TDF (`-z`) are
49
+ compared byte for byte by the [artifact matrix](docs/artifacts.md), which also lists what is best
50
+ effort per profile. `-t` covers the oracle-fitted instruction set (`docs/cli.md`).
51
+ - **Excluded:** the CDM binary image of `-b` ([PLAN](PLAN.md#explicit-exclusions-from-parity)).
52
+ - Known remaining cases (false accepts outside the relevant set, first-error decisions, matrix
53
+ differences) are listed in the release record.
54
+
55
+ ## Three channels
56
+
57
+ Every finding belongs to one channel ([PLAN](PLAN.md#three-diagnostic-channels)):
58
+
59
+ 1. **Reference diagnostics**: what the reference compiler reports. Default `crbc` stdout, the message
60
+ file and the exit status carry only these, so `crbc` stays a drop-in.
61
+ 2. **opencrbasic checks** (`OCB001`-`OCB007`): likely defects the reference accepts, each with
62
+ evidence, controls and a default severity; `crbc --strict` (stderr, exit 3 on a check error),
63
+ `--report`, `opencrbasic.toml`, and the editor (source `opencrbasic (check)`). See
64
+ `docs/checker.md` "opencrbasic checks OCB002-OCB007 (M3)".
65
+ 3. **Coverage notices**: CR6 reference rules opencrbasic is known not to model, from the
66
+ [evidence inventory](docs/inventory.md); `--report` and, when enabled, editor hints. Information,
67
+ never a verdict.
68
+
69
+ ## Accepted differences
70
+
71
+ In the differential tools, equal means equal exit status and equal stdout after whitespace
72
+ normalization (line endings, trailing spaces); the artifact matrix compares bytes. By the human decision of 2026-10-24, differences in the order,
73
+ wording or cascade of diagnostics after the first error are accepted and do not block a release;
74
+ differences in the first error are fixed when they come from a realistic mistake and otherwise
75
+ recorded in [`docs/first-error-review.md`](docs/first-error-review.md). Runner failures and
76
+ reference crashes are never counted as verdicts.
77
+
78
+ ## Versions
79
+
80
+ - The package version (`pyproject.toml`) and `opencrbasic.__version__` are one number
81
+ (`tests/test_versions.py`). Major: a change to the drop-in contract or a gated verdict policy;
82
+ minor: new checks, notices or features; patch: fixes. The VS Code extension, in its own repository, has its
83
+ own version.
84
+ - `analysis.API_VERSION` (now 2) versions the editor facade; the language server refuses to start
85
+ with another (`docs/lsp-api.md`). `checks.SCHEMA_VERSION` (now 2) versions the `--report` JSON.
86
+ Each is bumped only on an incompatible change.
87
+
88
+ ## Use
89
+
90
+ Python dependencies are managed with [uv](https://docs.astral.sh/uv/). The package itself has
91
+ no runtime dependencies; the `dev` group adds pytest, pefile (for the reverse-engineering tools),
92
+ pygls, pyrefly and pre-commit.
93
+
94
+ ```bash
95
+ uv sync # environment from uv.lock
96
+ uv run pre-commit install # ruff, pyrefly and file hygiene on every commit
97
+ uv run crbc Program.CR6 # compile
98
+ uv run pytest -q -n auto # tests
99
+ uv tool install . # optional: `crbc` on PATH outside the project
100
+ ```
101
+
102
+ Contributor and agent conventions: [AGENTS.md](AGENTS.md).
103
+
104
+ `crbc` is a drop-in for `CR6Comp.exe` (`docs/cli.md`):
105
+
106
+ ```
107
+ crbc [options] program [messagefile]
108
+ -s also print ProgSignature
109
+ -z FILE write the binary table definition file (TDF)
110
+ -l FILE write the text table list (also -c)
111
+ -v / -d print version (then compile / and exit)
112
+ --logger L cr6 | cr1000x | cr300 | cr350 (default: from the file extension
113
+ .CR6/.CR1X/.CR300/.CR350, else cr6)
114
+ --no-message-file do not write the message file (CR6cmple.xxx, CR1000Xcmple.xxx, ...)
115
+ --strict also run opencrbasic checks (likely defects the reference accepts) on stderr;
116
+ exit 3 when the program compiles but a check reports an error
117
+ (exit 4: invalid opencrbasic.toml)
118
+ --report FILE write a JSON report (reference verdict and diagnostics, check findings)
119
+ --config FILE use this opencrbasic.toml (default: nearest ancestor of the program)
120
+ --check CODE=SEVERITY set a check severity (error|warning|info|off); repeatable
121
+ ```
122
+
123
+ Check configuration and the report schema: `docs/checker.md` "Check configuration and report file".
124
+
125
+ Output follows the exe's format: `Program.CR6 -- Compiled in SequentialMode.` (CR300/CR350:
126
+ `-- Compiled OK.`), `Warning: ...` lines, or `-- Compile Failed!` followed by `line N: message`
127
+ lines; the same text goes to `<LOGGER>cmple.xxx` (for example `CR6cmple.xxx`) with CRLF line
128
+ endings; exit status 0 or 1.
129
+
130
+ As a library:
131
+
132
+ ```python
133
+ from pathlib import Path
134
+ from opencrbasic import profiles
135
+ from opencrbasic.compiler import compile_file
136
+
137
+ result = compile_file(Path("Main.CR6"), profiles.get("cr6"))
138
+ result.ok, result.mode, result.signature
139
+ [d.cr6comp() for d in result.diagnostics.items]
140
+ result.tables # list of tdf.TdfTable (Status, DataTables, DataTableInfo, ConstTable, Public)
141
+ ```
142
+
143
+ `parser.parse_file()` gives the syntax tree (`syntax.py`) for other tools.
144
+
145
+ [openloggernet](https://github.com/UtrechtUniversity/open-loggernet) uses opencrbasic when
146
+ `CR6COMP=opencrbasic` is set, and the FirnMelt station repository compiles every program with it in
147
+ CI.
148
+
149
+ ## Editor support
150
+
151
+ `crbc-lsp` (`python -m opencrbasic.lsp`) is a language server for CRBasic programs; the `logger`
152
+ setting selects the CR6 profile (default) or CR1000X, CR300 or CR350. It reports the compiler's
153
+ diagnostics and offers hover, completion, signature help, outline, folding, go to definition,
154
+ references, highlighting, rename, formatting and quick fixes. Its highlighting names are the CR6 ones.
155
+ The VS Code client is [open-crbasic-vsix](https://github.com/UtrechtUniversity/open-crbasic-vsix). Setup, settings, feature limits and the
156
+ host-tested status are in [`docs/lsp-user-guide.md`](docs/lsp-user-guide.md). It also shows the
157
+ opencrbasic checks and, with `opencrbasic.coverageNotices`, the coverage notices. The editor features
158
+ were host-tested in VS Code on macOS (T-01 to T-18, `LSP_TO_BE_TESTED.md`); the check and notice display
159
+ (M6) has not been host-tested yet, and Windows and Linux are not claimed.
160
+
161
+ ## How it was built
162
+
163
+ The exe is the logger's own compiler built for Windows. Its instruction, function and
164
+ predefined-symbol tables were extracted from the binary (`tools/extract_tables.py`), its
165
+ behaviour was studied with Ghidra and with thousands of runs of the real exe under Wine, and
166
+ everything is checked differentially:
167
+
168
+ | Tool | Checks |
169
+ |---|---|
170
+ | `tools/oracle.py` | runs the real exe under Wine (cached) |
171
+ | `tools/difftest.py --logger L` | normalized output vs the exe over repository programs and 507 CHM help examples |
172
+ | `tools/tdftest.py` | `-z` TDF and `-l` table list, byte for byte |
173
+ | `tools/fuzz.py`, `tools/fuzz_replay.py` | mutated programs, differential; reproducers in `tests/corpus/fuzz/` |
174
+ | `tools/artifact_matrix.py` | every output file per profile and option set, with a baseline gate |
175
+ | `tools/inventory.py` | the CR6 evidence inventory and the coverage-notice table |
176
+ | `uv run pytest -q` | unit tests, corpus parse tests, TDF fixtures |
177
+
178
+ The tools that run the real compilers need them in a local, git-ignored `compiler/` directory
179
+ (copy `CR6Comp.exe`, `cr1000xcomp.exe`, `cr300comp.exe` and `cr350comp.exe` from a CRBasic Editor
180
+ install) and Wine on macOS and Linux.
181
+
182
+ Differential and replay runs default to reporting differences without failing on known gaps. Use
183
+ the gates when a nonzero exit is required:
184
+
185
+ ```bash
186
+ uv run tools/difftest.py --logger cr6 --gate no-regressions
187
+ uv run tools/difftest.py --logger cr6 --gate exact
188
+ uv run tools/fuzz_replay.py --gate no-regressions --json replay.json
189
+ uv run tools/fuzz_replay.py --gate exact --json replay.json
190
+ ```
191
+
192
+ `no-regressions` fails for a false rejection, opencrbasic crash or hang (and both tools
193
+ fail for an oracle error). `exact` fails for any result that does not match the normalized
194
+ reference output. Replay's `--json FILE` writes summary counts and individual result rows for CI
195
+ and later triage. See [`PLAN.md`](PLAN.md#remaining-work-strategy) for how remaining differences
196
+ are measured and fixed.
197
+
198
+ Reverse-engineering notes: `docs/binary.md` (exe layout), `docs/cli.md`, `docs/type-codes.md`
199
+ (parameter kinds), `docs/frontend.md` (language quirks), `docs/checker.md` and
200
+ `docs/instructions.md` (diagnostic rules), `docs/modes.md` (Sequential/Pipeline),
201
+ `docs/tdf-rules.md` (table definitions), `docs/loggers.md` (per-logger differences),
202
+ `docs/fuzz-findings.md`. Provenance and licensing questions: `docs/legal.md`.
203
+
204
+ ## Layout
205
+
206
+ ```
207
+ src/opencrbasic/
208
+ lexer.py source.py consteval.py parser.py syntax.py front end
209
+ checker/ semantic checks and diagnostics
210
+ mode.py tables.py tdf.py signature.py outputs
211
+ profiles.py data/<logger>/*.json per-logger tables and rules
212
+ compiler.py cli.py driver and crbc
213
+ tools/ oracle, difftest, tdftest, fuzzing, table extraction, Ghidra script
214
+ tests/ unit tests, corpora, fixtures
215
+ docs/ behaviour of the original compilers, with evidence
216
+ ```
217
+
218
+ ## Releasing
219
+
220
+ Every push to `dev` that passes CI is published to TestPyPI as `<version>.dev<run number>`. A push
221
+ to `main` publishes to PyPI when `version` in `pyproject.toml` (and `opencrbasic.__version__`) has
222
+ not been released yet. Publishing uses PyPI trusted publishing from the `pypi` and `testpypi` GitHub
223
+ environments; no API token is stored in the repository.
@@ -0,0 +1,206 @@
1
+ # opencrbasic
2
+
3
+ A native re-implementation of Campbell Scientific's CRBasic pre-compilers for the CR6, CR1000X,
4
+ CR300 and CR350 dataloggers (`CR6Comp.exe`, `cr1000xcomp.exe`, `cr300comp.exe`, `cr350comp.exe`),
5
+ with a language server. It runs on Linux, macOS and Windows with only Python 3.15+, no Wine and no
6
+ Windows binaries. It is MIT licensed and not affiliated with Campbell Scientific; provenance and
7
+ what this repository deliberately leaves out are in the [legal notes](docs/legal.md). Version 1.0:
8
+ [release record](docs/release-1.0.md).
9
+
10
+ ```bash
11
+ pip install opencrbasic # crbc, the compiler
12
+ pip install "opencrbasic[lsp]" # also crbc-lsp, the language server
13
+ ```
14
+
15
+ The VS Code extension lives in [open-crbasic-vsix](https://github.com/UtrechtUniversity/open-crbasic-vsix);
16
+ agent skills for writing CRBasic in [open-crbasic-skills](https://github.com/UtrechtUniversity/open-crbasic-skills).
17
+
18
+ Status, measured against the real compilers: `PROGRESS.md`. Design and the v1.0 milestones:
19
+ [`PLAN.md`](PLAN.md#v10-milestones).
20
+
21
+ ## Scope
22
+
23
+ - **Gated profiles: CR6 and CR1000X.** On these, v1.0 gates the reference verdict in the relevant
24
+ area (the FirnMelt/GEUS instruction set and language constructs), the execution mode, and the
25
+ first error for realistic mistakes on relevant rules, measured on the FirnMelt programs, the 507
26
+ CRBasic help examples, the 66 GEUS programs, the saved fuzz replay and fresh `--relevant` fuzz
27
+ seeds. The help examples and GEUS programs are not redistributed here; see
28
+ [tests/corpus/README.md](tests/corpus/README.md) for running against local copies. One relevant-area false accept is known: a `Sub` name that is also another routine's
29
+ parameter, used as a value. CR300 and CR350 are best effort: measured and kept, not gated (human
30
+ decision 2026-10-24).
31
+ - **Outputs.** stdout, the message file, `-s`, `-u`, `-p`/`-i`, `-l`/`-c` and the CR6 TDF (`-z`) are
32
+ compared byte for byte by the [artifact matrix](docs/artifacts.md), which also lists what is best
33
+ effort per profile. `-t` covers the oracle-fitted instruction set (`docs/cli.md`).
34
+ - **Excluded:** the CDM binary image of `-b` ([PLAN](PLAN.md#explicit-exclusions-from-parity)).
35
+ - Known remaining cases (false accepts outside the relevant set, first-error decisions, matrix
36
+ differences) are listed in the release record.
37
+
38
+ ## Three channels
39
+
40
+ Every finding belongs to one channel ([PLAN](PLAN.md#three-diagnostic-channels)):
41
+
42
+ 1. **Reference diagnostics**: what the reference compiler reports. Default `crbc` stdout, the message
43
+ file and the exit status carry only these, so `crbc` stays a drop-in.
44
+ 2. **opencrbasic checks** (`OCB001`-`OCB007`): likely defects the reference accepts, each with
45
+ evidence, controls and a default severity; `crbc --strict` (stderr, exit 3 on a check error),
46
+ `--report`, `opencrbasic.toml`, and the editor (source `opencrbasic (check)`). See
47
+ `docs/checker.md` "opencrbasic checks OCB002-OCB007 (M3)".
48
+ 3. **Coverage notices**: CR6 reference rules opencrbasic is known not to model, from the
49
+ [evidence inventory](docs/inventory.md); `--report` and, when enabled, editor hints. Information,
50
+ never a verdict.
51
+
52
+ ## Accepted differences
53
+
54
+ In the differential tools, equal means equal exit status and equal stdout after whitespace
55
+ normalization (line endings, trailing spaces); the artifact matrix compares bytes. By the human decision of 2026-10-24, differences in the order,
56
+ wording or cascade of diagnostics after the first error are accepted and do not block a release;
57
+ differences in the first error are fixed when they come from a realistic mistake and otherwise
58
+ recorded in [`docs/first-error-review.md`](docs/first-error-review.md). Runner failures and
59
+ reference crashes are never counted as verdicts.
60
+
61
+ ## Versions
62
+
63
+ - The package version (`pyproject.toml`) and `opencrbasic.__version__` are one number
64
+ (`tests/test_versions.py`). Major: a change to the drop-in contract or a gated verdict policy;
65
+ minor: new checks, notices or features; patch: fixes. The VS Code extension, in its own repository, has its
66
+ own version.
67
+ - `analysis.API_VERSION` (now 2) versions the editor facade; the language server refuses to start
68
+ with another (`docs/lsp-api.md`). `checks.SCHEMA_VERSION` (now 2) versions the `--report` JSON.
69
+ Each is bumped only on an incompatible change.
70
+
71
+ ## Use
72
+
73
+ Python dependencies are managed with [uv](https://docs.astral.sh/uv/). The package itself has
74
+ no runtime dependencies; the `dev` group adds pytest, pefile (for the reverse-engineering tools),
75
+ pygls, pyrefly and pre-commit.
76
+
77
+ ```bash
78
+ uv sync # environment from uv.lock
79
+ uv run pre-commit install # ruff, pyrefly and file hygiene on every commit
80
+ uv run crbc Program.CR6 # compile
81
+ uv run pytest -q -n auto # tests
82
+ uv tool install . # optional: `crbc` on PATH outside the project
83
+ ```
84
+
85
+ Contributor and agent conventions: [AGENTS.md](AGENTS.md).
86
+
87
+ `crbc` is a drop-in for `CR6Comp.exe` (`docs/cli.md`):
88
+
89
+ ```
90
+ crbc [options] program [messagefile]
91
+ -s also print ProgSignature
92
+ -z FILE write the binary table definition file (TDF)
93
+ -l FILE write the text table list (also -c)
94
+ -v / -d print version (then compile / and exit)
95
+ --logger L cr6 | cr1000x | cr300 | cr350 (default: from the file extension
96
+ .CR6/.CR1X/.CR300/.CR350, else cr6)
97
+ --no-message-file do not write the message file (CR6cmple.xxx, CR1000Xcmple.xxx, ...)
98
+ --strict also run opencrbasic checks (likely defects the reference accepts) on stderr;
99
+ exit 3 when the program compiles but a check reports an error
100
+ (exit 4: invalid opencrbasic.toml)
101
+ --report FILE write a JSON report (reference verdict and diagnostics, check findings)
102
+ --config FILE use this opencrbasic.toml (default: nearest ancestor of the program)
103
+ --check CODE=SEVERITY set a check severity (error|warning|info|off); repeatable
104
+ ```
105
+
106
+ Check configuration and the report schema: `docs/checker.md` "Check configuration and report file".
107
+
108
+ Output follows the exe's format: `Program.CR6 -- Compiled in SequentialMode.` (CR300/CR350:
109
+ `-- Compiled OK.`), `Warning: ...` lines, or `-- Compile Failed!` followed by `line N: message`
110
+ lines; the same text goes to `<LOGGER>cmple.xxx` (for example `CR6cmple.xxx`) with CRLF line
111
+ endings; exit status 0 or 1.
112
+
113
+ As a library:
114
+
115
+ ```python
116
+ from pathlib import Path
117
+ from opencrbasic import profiles
118
+ from opencrbasic.compiler import compile_file
119
+
120
+ result = compile_file(Path("Main.CR6"), profiles.get("cr6"))
121
+ result.ok, result.mode, result.signature
122
+ [d.cr6comp() for d in result.diagnostics.items]
123
+ result.tables # list of tdf.TdfTable (Status, DataTables, DataTableInfo, ConstTable, Public)
124
+ ```
125
+
126
+ `parser.parse_file()` gives the syntax tree (`syntax.py`) for other tools.
127
+
128
+ [openloggernet](https://github.com/UtrechtUniversity/open-loggernet) uses opencrbasic when
129
+ `CR6COMP=opencrbasic` is set, and the FirnMelt station repository compiles every program with it in
130
+ CI.
131
+
132
+ ## Editor support
133
+
134
+ `crbc-lsp` (`python -m opencrbasic.lsp`) is a language server for CRBasic programs; the `logger`
135
+ setting selects the CR6 profile (default) or CR1000X, CR300 or CR350. It reports the compiler's
136
+ diagnostics and offers hover, completion, signature help, outline, folding, go to definition,
137
+ references, highlighting, rename, formatting and quick fixes. Its highlighting names are the CR6 ones.
138
+ The VS Code client is [open-crbasic-vsix](https://github.com/UtrechtUniversity/open-crbasic-vsix). Setup, settings, feature limits and the
139
+ host-tested status are in [`docs/lsp-user-guide.md`](docs/lsp-user-guide.md). It also shows the
140
+ opencrbasic checks and, with `opencrbasic.coverageNotices`, the coverage notices. The editor features
141
+ were host-tested in VS Code on macOS (T-01 to T-18, `LSP_TO_BE_TESTED.md`); the check and notice display
142
+ (M6) has not been host-tested yet, and Windows and Linux are not claimed.
143
+
144
+ ## How it was built
145
+
146
+ The exe is the logger's own compiler built for Windows. Its instruction, function and
147
+ predefined-symbol tables were extracted from the binary (`tools/extract_tables.py`), its
148
+ behaviour was studied with Ghidra and with thousands of runs of the real exe under Wine, and
149
+ everything is checked differentially:
150
+
151
+ | Tool | Checks |
152
+ |---|---|
153
+ | `tools/oracle.py` | runs the real exe under Wine (cached) |
154
+ | `tools/difftest.py --logger L` | normalized output vs the exe over repository programs and 507 CHM help examples |
155
+ | `tools/tdftest.py` | `-z` TDF and `-l` table list, byte for byte |
156
+ | `tools/fuzz.py`, `tools/fuzz_replay.py` | mutated programs, differential; reproducers in `tests/corpus/fuzz/` |
157
+ | `tools/artifact_matrix.py` | every output file per profile and option set, with a baseline gate |
158
+ | `tools/inventory.py` | the CR6 evidence inventory and the coverage-notice table |
159
+ | `uv run pytest -q` | unit tests, corpus parse tests, TDF fixtures |
160
+
161
+ The tools that run the real compilers need them in a local, git-ignored `compiler/` directory
162
+ (copy `CR6Comp.exe`, `cr1000xcomp.exe`, `cr300comp.exe` and `cr350comp.exe` from a CRBasic Editor
163
+ install) and Wine on macOS and Linux.
164
+
165
+ Differential and replay runs default to reporting differences without failing on known gaps. Use
166
+ the gates when a nonzero exit is required:
167
+
168
+ ```bash
169
+ uv run tools/difftest.py --logger cr6 --gate no-regressions
170
+ uv run tools/difftest.py --logger cr6 --gate exact
171
+ uv run tools/fuzz_replay.py --gate no-regressions --json replay.json
172
+ uv run tools/fuzz_replay.py --gate exact --json replay.json
173
+ ```
174
+
175
+ `no-regressions` fails for a false rejection, opencrbasic crash or hang (and both tools
176
+ fail for an oracle error). `exact` fails for any result that does not match the normalized
177
+ reference output. Replay's `--json FILE` writes summary counts and individual result rows for CI
178
+ and later triage. See [`PLAN.md`](PLAN.md#remaining-work-strategy) for how remaining differences
179
+ are measured and fixed.
180
+
181
+ Reverse-engineering notes: `docs/binary.md` (exe layout), `docs/cli.md`, `docs/type-codes.md`
182
+ (parameter kinds), `docs/frontend.md` (language quirks), `docs/checker.md` and
183
+ `docs/instructions.md` (diagnostic rules), `docs/modes.md` (Sequential/Pipeline),
184
+ `docs/tdf-rules.md` (table definitions), `docs/loggers.md` (per-logger differences),
185
+ `docs/fuzz-findings.md`. Provenance and licensing questions: `docs/legal.md`.
186
+
187
+ ## Layout
188
+
189
+ ```
190
+ src/opencrbasic/
191
+ lexer.py source.py consteval.py parser.py syntax.py front end
192
+ checker/ semantic checks and diagnostics
193
+ mode.py tables.py tdf.py signature.py outputs
194
+ profiles.py data/<logger>/*.json per-logger tables and rules
195
+ compiler.py cli.py driver and crbc
196
+ tools/ oracle, difftest, tdftest, fuzzing, table extraction, Ghidra script
197
+ tests/ unit tests, corpora, fixtures
198
+ docs/ behaviour of the original compilers, with evidence
199
+ ```
200
+
201
+ ## Releasing
202
+
203
+ Every push to `dev` that passes CI is published to TestPyPI as `<version>.dev<run number>`. A push
204
+ to `main` publishes to PyPI when `version` in `pyproject.toml` (and `opencrbasic.__version__`) has
205
+ not been released yet. Publishing uses PyPI trusted publishing from the `pypi` and `testpypi` GitHub
206
+ environments; no API token is stored in the repository.
@@ -0,0 +1,84 @@
1
+ [project]
2
+ name = "opencrbasic"
3
+ version = "1.0.0"
4
+ description = "Open-source re-implementation of the Campbell Scientific CRBasic pre-compilers (CR6, CR1000X, CR300, CR350)"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Utrecht University" }]
9
+ requires-python = ">=3.15"
10
+ dependencies = []
11
+ classifiers = [
12
+ "Programming Language :: Python :: 3",
13
+ "Operating System :: OS Independent",
14
+ "Topic :: Software Development :: Compilers",
15
+ "Topic :: Scientific/Engineering",
16
+ ]
17
+
18
+ [project.urls]
19
+ Repository = "https://github.com/UtrechtUniversity/open-crbasic"
20
+
21
+ [project.optional-dependencies]
22
+ # The language server (`crbc-lsp`, docs/lsp-api.md "Language server (LSP-01)"); the compiler
23
+ # itself has no runtime dependencies.
24
+ lsp = ["pygls>=2.1.1,<3"]
25
+
26
+ [project.scripts]
27
+ crbc = "opencrbasic.cli:main"
28
+ crbc-lsp = "opencrbasic.lsp.server:main"
29
+
30
+ [dependency-groups]
31
+ # `uv sync` installs `dev` by default: the test runner and the reverse-engineering tools
32
+ # (`tools/extract_tables.py`, `tools/message_catalog.py` read the exe with pefile), and pygls
33
+ # so the suite can test the language server; pytest-xdist for parallel test runs (`-n`); pyrefly
34
+ # checks the repository (`[tool.pyrefly]`); pre-commit runs ruff and pyrefly before each commit.
35
+ dev = [
36
+ "pytest>=8",
37
+ "pefile>=2024.8.26",
38
+ "pygls>=2.1.1,<3",
39
+ "pytest-xdist>=3.6",
40
+ "pyrefly==1.3.1",
41
+ "pre-commit>=4",
42
+ ]
43
+
44
+ [build-system]
45
+ requires = ["hatchling"]
46
+ build-backend = "hatchling.build"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/opencrbasic"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ include = ["src", "README.md", "LICENSE"]
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ pythonpath = ["src", "tools"]
57
+ addopts = "--strict-markers --dist worksteal"
58
+ markers = [
59
+ "slow: corpus sweeps and other tests above ~3 s; skip with -m 'not slow' for the fast tier",
60
+ "server: tests that start the language server as a subprocess; run alone with -m server",
61
+ ]
62
+
63
+ [tool.ruff]
64
+ line-length = 120
65
+ target-version = "py314"
66
+ extend-exclude = ["tests/corpus", "tests/fixtures"]
67
+
68
+ [tool.ruff.lint]
69
+ select = ["E4", "E7", "E9", "F", "I", "B"]
70
+ # B023: closures here are called within the loop iteration that defines them;
71
+ # B009/B010: getattr/setattr reach attributes the type checker cannot see (pefile, dataclass extras)
72
+ ignore = ["E702", "E731", "E741", "B009", "B010", "B023", "B905"]
73
+
74
+ [tool.ruff.lint.per-file-ignores]
75
+ # F811: pytest fixtures imported into a test module; E402: sys.path set up before an import
76
+ "tests/**" = ["F811", "E402", "B008", "B017"]
77
+ "tools/**" = ["E402"]
78
+
79
+ [tool.pyrefly]
80
+ preset = "default"
81
+ python-version = "3.15"
82
+ project-includes = ["src", "tools", "tests"]
83
+ search-path = ["src", "tools", "tests/lsp"]
84
+ project-excludes = ["tests/corpus", "tests/fixtures"]
@@ -0,0 +1,3 @@
1
+ """Open-source CRBasic (CR6) pre-compiler."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())