opencrbasic 1.0.0__tar.gz → 1.1.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.
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/PKG-INFO +17 -1
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/README.md +16 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/pyproject.toml +1 -1
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/__init__.py +1 -1
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/analysis.py +1 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/cli.py +21 -4
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/compiler.py +7 -3
- opencrbasic-1.1.0/src/opencrbasic/macros.py +161 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/parser.py +2 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/source.py +26 -1
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/.gitignore +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/LICENSE +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/__main__.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/bindings.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/catalog.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checkconfig.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/__init__.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/cr3xx.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/ess.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/expressions.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/instr_data.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/instr_measure.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/instr_rules.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/instructions.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/intervals.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/longconv.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/scan_time.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/storage.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/structs.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/structure.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/symbols.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/terminals.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/unused.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/var_types.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checker/warnings.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/checks.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/commands.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/consteval.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/background_calibration.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/before_beginprog.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/background_calibration.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/before_beginprog.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/ess_variables.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/fnotch.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/functions.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/instructions.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/measure.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/mode.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/pipeline_tasks.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/settings_fields.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/status_members.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/symbols.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/terminals.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/before_beginprog.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/ess_variables.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/fnotch.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/functions.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/instructions.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/measure.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/mode.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/settings_fields.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/status_members.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/symbols.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr300/terminals.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/before_beginprog.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/ess_variables.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/fnotch.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/functions.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/instructions.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/measure.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/mode.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/settings_fields.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/status_members.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/symbols.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr350/terminals.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/ess_variables.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/fnotch.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/functions.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/instructions.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/measure.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/mode.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/notices_cr6.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/pipeline_tasks.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/settings_fields.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/status_members.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/status_table.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/symbols.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/terminals.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/type_codes.json +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/diagnostics.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/editing.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/formatting.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/helpdocs.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/hover.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/inlay.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/lexer.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/lsp/__init__.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/lsp/__main__.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/lsp/config.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/lsp/features.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/lsp/server.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/lsp/session.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/lsp/worker.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/mode.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/navigation.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/positions.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/preprocess.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/profiles.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/provenance.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/quickfix.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/rename.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/signature.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/sources.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/syntax.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/tables.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/tables_hook.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/tdf.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/timing.py +0 -0
- {opencrbasic-1.0.0 → opencrbasic-1.1.0}/tests/corpus/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opencrbasic
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Open-source re-implementation of the Campbell Scientific CRBasic pre-compilers (CR6, CR1000X, CR300, CR350)
|
|
5
5
|
Project-URL: Repository, https://github.com/UtrechtUniversity/open-crbasic
|
|
6
6
|
Author: Utrecht University
|
|
@@ -118,6 +118,22 @@ crbc [options] program [messagefile]
|
|
|
118
118
|
--report FILE write a JSON report (reference verdict and diagnostics, check findings)
|
|
119
119
|
--config FILE use this opencrbasic.toml (default: nearest ancestor of the program)
|
|
120
120
|
--check CODE=SEVERITY set a check severity (error|warning|info|off); repeatable
|
|
121
|
+
--expand FILE expand #Define/#Macro macros into FILE, then compile FILE instead
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`#Define` and `#Macro` are an opencrbasic extension, not CRBasic: the reference compilers reject
|
|
125
|
+
them. `--expand` writes plain CRBasic (definitions commented out, uses expanded inline) that
|
|
126
|
+
`CR6Comp.exe` accepts and that is the file to send to the logger; write it next to the source so
|
|
127
|
+
its `Include`s resolve. The language server expands them the same way, so the editor reports
|
|
128
|
+
the expansion's diagnostics on the line of each use. Syntax and rules:
|
|
129
|
+
[`macros.py`](src/opencrbasic/macros.py).
|
|
130
|
+
|
|
131
|
+
```crbasic
|
|
132
|
+
#Define LOGD(msg) DebugMsg = msg ' release build: #Define LOGD(msg)
|
|
133
|
+
#Macro LOGV(name, value)
|
|
134
|
+
DebugMsg = name & "=" & value
|
|
135
|
+
CallTable Debug
|
|
136
|
+
#EndMacro
|
|
121
137
|
```
|
|
122
138
|
|
|
123
139
|
Check configuration and the report schema: `docs/checker.md` "Check configuration and report file".
|
|
@@ -101,6 +101,22 @@ crbc [options] program [messagefile]
|
|
|
101
101
|
--report FILE write a JSON report (reference verdict and diagnostics, check findings)
|
|
102
102
|
--config FILE use this opencrbasic.toml (default: nearest ancestor of the program)
|
|
103
103
|
--check CODE=SEVERITY set a check severity (error|warning|info|off); repeatable
|
|
104
|
+
--expand FILE expand #Define/#Macro macros into FILE, then compile FILE instead
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`#Define` and `#Macro` are an opencrbasic extension, not CRBasic: the reference compilers reject
|
|
108
|
+
them. `--expand` writes plain CRBasic (definitions commented out, uses expanded inline) that
|
|
109
|
+
`CR6Comp.exe` accepts and that is the file to send to the logger; write it next to the source so
|
|
110
|
+
its `Include`s resolve. The language server expands them the same way, so the editor reports
|
|
111
|
+
the expansion's diagnostics on the line of each use. Syntax and rules:
|
|
112
|
+
[`macros.py`](src/opencrbasic/macros.py).
|
|
113
|
+
|
|
114
|
+
```crbasic
|
|
115
|
+
#Define LOGD(msg) DebugMsg = msg ' release build: #Define LOGD(msg)
|
|
116
|
+
#Macro LOGV(name, value)
|
|
117
|
+
DebugMsg = name & "=" & value
|
|
118
|
+
CallTable Debug
|
|
119
|
+
#EndMacro
|
|
104
120
|
```
|
|
105
121
|
|
|
106
122
|
Check configuration and the report schema: `docs/checker.md` "Check configuration and report file".
|
|
@@ -120,6 +120,7 @@ def analyze(request: AnalysisRequest) -> AnalysisResult:
|
|
|
120
120
|
warn_unused=request.warn_unused,
|
|
121
121
|
source=recorder,
|
|
122
122
|
signature=request.signature,
|
|
123
|
+
macros=True,
|
|
123
124
|
)
|
|
124
125
|
includes = getattr(result.program_ast, "include_paths", None) or {}
|
|
125
126
|
lowered = {name.lower(): path for name, path in includes.items()}
|
|
@@ -11,7 +11,8 @@ accepted (matching the exe's own option set) but has no implemented effect: orac
|
|
|
11
11
|
have none observable from the CLI either. Extra long options: --logger
|
|
12
12
|
{cr6,cr1000x,cr300,cr350} (default: from the file extension, else cr6) and --no-message-file (do
|
|
13
13
|
not write the message file), --strict (opencrbasic checks, `checks.py`, on stderr only), --report FILE
|
|
14
|
-
(JSON report, `docs/checker.md` "Check configuration and report file"), --
|
|
14
|
+
(JSON report, `docs/checker.md` "Check configuration and report file"), --expand FILE (expand
|
|
15
|
+
`#Define`/`#Macro` macros into FILE and compile FILE instead, `macros.py`), --config FILE and
|
|
15
16
|
--check CODE=SEVERITY (check configuration; both only act together with --strict or --report).
|
|
16
17
|
Output goes to stdout and to the message file (`<LOGGER>cmple.xxx` by default, e.g. CR6cmple.xxx;
|
|
17
18
|
CRLF line endings);
|
|
@@ -47,6 +48,7 @@ class _Options(TypedDict):
|
|
|
47
48
|
strict: bool
|
|
48
49
|
report: str | None
|
|
49
50
|
config: str | None
|
|
51
|
+
expand: str | None
|
|
50
52
|
check: list[str]
|
|
51
53
|
|
|
52
54
|
|
|
@@ -68,6 +70,7 @@ def _parse(argv: list[str]) -> tuple[_Options, list[str]]:
|
|
|
68
70
|
"strict": False,
|
|
69
71
|
"report": None,
|
|
70
72
|
"config": None,
|
|
73
|
+
"expand": None,
|
|
71
74
|
"check": [],
|
|
72
75
|
}
|
|
73
76
|
positional: list[str] = []
|
|
@@ -75,10 +78,10 @@ def _parse(argv: list[str]) -> tuple[_Options, list[str]]:
|
|
|
75
78
|
while i < len(argv):
|
|
76
79
|
arg = argv[i]
|
|
77
80
|
if arg.startswith("--"):
|
|
78
|
-
if arg in ("--logger", "--report", "--config", "--check"):
|
|
81
|
+
if arg in ("--logger", "--report", "--config", "--expand", "--check"):
|
|
79
82
|
value = argv[i + 1] if i + 1 < len(argv) else ""
|
|
80
83
|
i += 2
|
|
81
|
-
elif arg.split("=", 1)[0] in ("--logger", "--report", "--config", "--check"):
|
|
84
|
+
elif arg.split("=", 1)[0] in ("--logger", "--report", "--config", "--expand", "--check"):
|
|
82
85
|
arg, value = arg.split("=", 1)
|
|
83
86
|
i += 1
|
|
84
87
|
else:
|
|
@@ -87,7 +90,7 @@ def _parse(argv: list[str]) -> tuple[_Options, list[str]]:
|
|
|
87
90
|
if arg == "--check":
|
|
88
91
|
opts["check"].append(value)
|
|
89
92
|
else:
|
|
90
|
-
opts[cast(Literal["logger", "report", "config"], arg[2:])] = value # noqa: F821 -- ruff reads the Literal strings as names
|
|
93
|
+
opts[cast(Literal["logger", "report", "config", "expand"], arg[2:])] = value # noqa: F821 -- ruff reads the Literal strings as names
|
|
91
94
|
continue
|
|
92
95
|
if arg == "--no-message-file":
|
|
93
96
|
opts["message_file"] = False
|
|
@@ -254,6 +257,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
254
257
|
_write_report(opts["report"], checks.missing_program_report(program.name, profile.name, opts["strict"]))
|
|
255
258
|
return 1
|
|
256
259
|
|
|
260
|
+
if opts["expand"]:
|
|
261
|
+
from . import macros
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
expanded = macros.expand(program.read_bytes().decode("latin-1"))
|
|
265
|
+
except macros.MacroError as e:
|
|
266
|
+
lines = [f"{positional[0]} -- Compile Failed!", "", f"line {e.line}: {e}"]
|
|
267
|
+
print("\n".join(lines))
|
|
268
|
+
if opts["message_file"]:
|
|
269
|
+
_write_message_file(message_path, lines)
|
|
270
|
+
return 1
|
|
271
|
+
program = Path(opts["expand"])
|
|
272
|
+
program.write_bytes(expanded.encode("latin-1", "replace"))
|
|
273
|
+
|
|
257
274
|
result = compile_file(program, profile, display_name=program.name, warn_unused=opts["u"], provenance=False)
|
|
258
275
|
if opts["p"]:
|
|
259
276
|
from . import preprocess
|
|
@@ -200,9 +200,12 @@ def compile_file(
|
|
|
200
200
|
source: SourceProvider | None = None,
|
|
201
201
|
signature: bool = True,
|
|
202
202
|
provenance: bool = True,
|
|
203
|
+
macros: bool = False,
|
|
203
204
|
) -> CompileResult:
|
|
204
205
|
"""`provenance=False` (the CLI and the tools) skips the position data only the language server
|
|
205
206
|
reads (`LogicalLine.pieces`, `Program.line_map`, `Stmt.origin`); the compile output is unchanged.
|
|
207
|
+
`macros=True` (the language server) expands the entry file's `#Define`/`#Macro` macros
|
|
208
|
+
(`macros.py`), which the reference rejects.
|
|
206
209
|
|
|
207
210
|
Never lets an unexpected exception escape (`docs/checker.md` "Robustness"): the real
|
|
208
211
|
compiler never crashes on a syntactically-loadable file, so an internal bug in this project
|
|
@@ -215,7 +218,7 @@ def compile_file(
|
|
|
215
218
|
"""
|
|
216
219
|
profile = profile or profiles.for_file(path)
|
|
217
220
|
name = display_name or path.name
|
|
218
|
-
result = _compile_once(path, profile, name, None, source, provenance)
|
|
221
|
+
result = _compile_once(path, profile, name, None, source, provenance, macros)
|
|
219
222
|
if profile.single_pass:
|
|
220
223
|
# CR300/CR350 stop pulling a `Public`/`Dim` initializer's next line once any error has been
|
|
221
224
|
# reported ahead of the declaration (oracle, 2026-10-10, `docs/checker.md` "Round 8"): re-run
|
|
@@ -233,7 +236,7 @@ def compile_file(
|
|
|
233
236
|
if bad is None or (limit is not None and bad >= limit):
|
|
234
237
|
break
|
|
235
238
|
limit = bad
|
|
236
|
-
result = _compile_once(path, profile, name, limit, source, provenance)
|
|
239
|
+
result = _compile_once(path, profile, name, limit, source, provenance, macros)
|
|
237
240
|
if result.program_ast is None:
|
|
238
241
|
return result
|
|
239
242
|
return _finish(result, path, warn_unused, source, signature)
|
|
@@ -246,12 +249,13 @@ def _compile_once(
|
|
|
246
249
|
pull_limit: tuple[int, ...] | None,
|
|
247
250
|
source: SourceProvider | None = None,
|
|
248
251
|
provenance: bool = True,
|
|
252
|
+
macros: bool = False,
|
|
249
253
|
):
|
|
250
254
|
from . import checker, parser
|
|
251
255
|
|
|
252
256
|
try:
|
|
253
257
|
program, diags = parser.parse_file(
|
|
254
|
-
path, profile=profile, pull_limit=pull_limit, source=source, provenance=provenance
|
|
258
|
+
path, profile=profile, pull_limit=pull_limit, source=source, provenance=provenance, macros=macros
|
|
255
259
|
)
|
|
256
260
|
except Exception as exc:
|
|
257
261
|
diags = Diagnostics()
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""`crbc --expand FILE`: opencrbasic's own macro step, not a reference-compiler feature.
|
|
2
|
+
|
|
3
|
+
The reference compilers reject `#Define` (`Unknown instruction`), so a program that uses macros is
|
|
4
|
+
expanded first into plain CRBasic, which any compiler (`crbc`, `CR6Comp.exe`) accepts and which is
|
|
5
|
+
the file to send to the logger. Macros expand inline, so they cost no call depth (the logger runs at
|
|
6
|
+
most four nested `Sub`/`Function` calls).
|
|
7
|
+
|
|
8
|
+
#Define DEBUG 1 object-like, one line
|
|
9
|
+
#Define LOGD(msg) DebugMsg = msg function-like, one line (an empty body is allowed)
|
|
10
|
+
#Macro LOGV(name, value) function-like, several lines
|
|
11
|
+
DebugMsg = name & "=" & value
|
|
12
|
+
CallTable Debug
|
|
13
|
+
#EndMacro
|
|
14
|
+
|
|
15
|
+
- Names and parameters are case-insensitive, like CRBasic. A function-like name not followed by
|
|
16
|
+
`(` is left alone; a macro is defined from its line on and cannot be redefined.
|
|
17
|
+
- Nothing inside a `"..."` string or a `'` comment is expanded. A one-line body loses its
|
|
18
|
+
trailing comment.
|
|
19
|
+
- A macro body is expanded again after substitution, without expanding itself (no recursion).
|
|
20
|
+
- Every other line, `#If` and the other reference directives included, is kept as written apart
|
|
21
|
+
from the expansion of macro names; the definition lines become comments so line numbers before
|
|
22
|
+
the first multi-line expansion stay the same. The lines of a multi-line expansion take the
|
|
23
|
+
indentation of the line that uses it.
|
|
24
|
+
- A macro call must close on its own line. Macros in `Include` files are not read or expanded.
|
|
25
|
+
- The language server expands macros the same way while analysing (`source.py`, the entry file
|
|
26
|
+
only), so a program with macros gets the diagnostics of its expansion, each on the line of the
|
|
27
|
+
macro use. `crbc` without `--expand` compiles like the reference and rejects `#Define`.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import re
|
|
31
|
+
import textwrap
|
|
32
|
+
|
|
33
|
+
_DEFINE_RE = re.compile(r"^\s*#\s*(Define|Macro)\s+([A-Za-z_]\w*)(?:\(([^)]*)\))?(.*)$", re.IGNORECASE)
|
|
34
|
+
_END_RE = re.compile(r"^\s*#\s*EndMacro\b", re.IGNORECASE)
|
|
35
|
+
# strings and comments are copied whole; a number or `&H`/`&B` literal is never a name
|
|
36
|
+
_TOKEN_RE = re.compile(r'"[^"\n]*"?|\'[^\n]*|\d\w*|&[HhBb]\w*|[A-Za-z_]\w*|.', re.DOTALL)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MacroError(Exception):
|
|
40
|
+
def __init__(self, line: int, message: str):
|
|
41
|
+
super().__init__(message)
|
|
42
|
+
self.line = line
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _Macro:
|
|
46
|
+
__slots__ = ("params", "body")
|
|
47
|
+
|
|
48
|
+
def __init__(self, params: list[str] | None, body: str):
|
|
49
|
+
self.params = params # None: object-like
|
|
50
|
+
self.body = body
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _strip_comment(text: str) -> str:
|
|
54
|
+
return "".join(t for t in _TOKEN_RE.findall(text) if not t.startswith("'"))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _call_args(text: str, i: int, line: int, name: str) -> tuple[list[str], int]:
|
|
58
|
+
"""The arguments of the call whose `(` is at `text[i]`, and the index after its `)`."""
|
|
59
|
+
args, current, depth = [], [], 0
|
|
60
|
+
for m in _TOKEN_RE.finditer(text, i + 1):
|
|
61
|
+
tok = m.group()
|
|
62
|
+
if tok == "\n":
|
|
63
|
+
break
|
|
64
|
+
if tok == ")" and depth == 0:
|
|
65
|
+
args.append("".join(current).strip())
|
|
66
|
+
return ([] if args == [""] else args), m.end()
|
|
67
|
+
if tok == "," and depth == 0:
|
|
68
|
+
args.append("".join(current).strip())
|
|
69
|
+
current = []
|
|
70
|
+
continue
|
|
71
|
+
depth += {"(": 1, ")": -1}.get(tok, 0)
|
|
72
|
+
current.append(tok)
|
|
73
|
+
raise MacroError(line, f"Macro {name}: missing ).")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _expand(text: str, macros: dict[str, _Macro], line: int, disabled: frozenset[str] = frozenset()) -> str:
|
|
77
|
+
out: list[str] = []
|
|
78
|
+
i = 0
|
|
79
|
+
while m := _TOKEN_RE.match(text, i):
|
|
80
|
+
tok, i = m.group(), m.end()
|
|
81
|
+
key = tok.upper()
|
|
82
|
+
macro = macros.get(key)
|
|
83
|
+
if macro is None or key in disabled:
|
|
84
|
+
out.append(tok)
|
|
85
|
+
continue
|
|
86
|
+
body = macro.body
|
|
87
|
+
if macro.params is not None:
|
|
88
|
+
j = i
|
|
89
|
+
while j < len(text) and text[j] in " \t":
|
|
90
|
+
j += 1
|
|
91
|
+
if j >= len(text) or text[j] != "(":
|
|
92
|
+
out.append(tok)
|
|
93
|
+
continue
|
|
94
|
+
args, i = _call_args(text, j, line, tok)
|
|
95
|
+
if len(args) != len(macro.params):
|
|
96
|
+
raise MacroError(line, f"Macro {tok} takes {len(macro.params)} argument(s), got {len(args)}.")
|
|
97
|
+
values = dict(zip(macro.params, args, strict=True))
|
|
98
|
+
body = "".join(values.get(t.upper(), t) for t in _TOKEN_RE.findall(body))
|
|
99
|
+
body = _expand(body, macros, line, disabled | {key})
|
|
100
|
+
head = text[text.rfind("\n", 0, m.start()) + 1 : m.start()]
|
|
101
|
+
indent = head[: len(head) - len(head.lstrip(" \t"))]
|
|
102
|
+
out.append(body.replace("\n", "\n" + indent))
|
|
103
|
+
return "".join(out)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Macros:
|
|
107
|
+
"""The macros of one file, fed its lines in order (`expand` here, the loader for the editor)."""
|
|
108
|
+
|
|
109
|
+
def __init__(self):
|
|
110
|
+
self.table: dict[str, _Macro] = {}
|
|
111
|
+
self._open: tuple[str, list[str] | None, int, list[str]] | None = None # a #Macro being read
|
|
112
|
+
|
|
113
|
+
def feed(self, text: str, line: int) -> str | None:
|
|
114
|
+
"""`text` with macros expanded, or None when it is part of a definition."""
|
|
115
|
+
if self._open is not None:
|
|
116
|
+
if _END_RE.match(text):
|
|
117
|
+
name, params, start, body = self._open
|
|
118
|
+
self._open = None
|
|
119
|
+
self._define(name, params, textwrap.dedent("\n".join(body)).strip("\n"), start)
|
|
120
|
+
else:
|
|
121
|
+
self._open[3].append(text)
|
|
122
|
+
return None
|
|
123
|
+
d = _DEFINE_RE.match(text)
|
|
124
|
+
if d is None:
|
|
125
|
+
return _expand(text, self.table, line)
|
|
126
|
+
kind, name, rest = d.group(1).upper(), d.group(2), d.group(4)
|
|
127
|
+
params = None if d.group(3) is None else [p.strip().upper() for p in d.group(3).split(",")]
|
|
128
|
+
if params == [""]:
|
|
129
|
+
params = []
|
|
130
|
+
if params is not None and (
|
|
131
|
+
not all(re.fullmatch(r"[A-Za-z_]\w*", p) for p in params) or len(set(params)) < len(params)
|
|
132
|
+
):
|
|
133
|
+
raise MacroError(line, f"Macro {name}: invalid parameter list.")
|
|
134
|
+
if kind == "MACRO":
|
|
135
|
+
if params is None or _strip_comment(rest).strip():
|
|
136
|
+
raise MacroError(line, f"#Macro {name} needs a (parameter list) and its body on the following lines.")
|
|
137
|
+
self._open = (name, params, line, [])
|
|
138
|
+
else:
|
|
139
|
+
self._define(name, params, _strip_comment(rest).strip(), line)
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def finish(self) -> None:
|
|
143
|
+
if self._open is not None:
|
|
144
|
+
raise MacroError(self._open[2], f"#Macro {self._open[0]} without #EndMacro.")
|
|
145
|
+
|
|
146
|
+
def _define(self, name: str, params: list[str] | None, body: str, line: int) -> None:
|
|
147
|
+
if name.upper() in self.table:
|
|
148
|
+
raise MacroError(line, f"Macro {name} is already defined.")
|
|
149
|
+
self.table[name.upper()] = _Macro(params, body)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def expand(text: str) -> str:
|
|
153
|
+
"""`text` with its `#Define`/`#Macro` definitions commented out and their uses expanded."""
|
|
154
|
+
newline = "\r\n" if "\r\n" in text else "\n"
|
|
155
|
+
macros = Macros()
|
|
156
|
+
out = []
|
|
157
|
+
for n, raw in enumerate(text.splitlines(), 1):
|
|
158
|
+
expanded = macros.feed(raw, n)
|
|
159
|
+
out.append("'" + raw if expanded is None else expanded.replace("\n", newline))
|
|
160
|
+
macros.finish()
|
|
161
|
+
return newline.join(out) + (newline if text.endswith(("\n", "\r")) else "")
|
|
@@ -3174,6 +3174,7 @@ def parse_file(
|
|
|
3174
3174
|
pull_limit: tuple[int, ...] | None = None,
|
|
3175
3175
|
source: SourceProvider | None = None,
|
|
3176
3176
|
provenance: bool = True,
|
|
3177
|
+
macros: bool = False,
|
|
3177
3178
|
) -> tuple[S.Program, Diagnostics]:
|
|
3178
3179
|
"""`provenance=False` skips the pieces/line-map/origin data only the language server reads (the CLI
|
|
3179
3180
|
path); everything the checker sees is unchanged. `profile` is normally left to the file's own extension (`profiles.for_file`, the historical
|
|
@@ -3191,6 +3192,7 @@ def parse_file(
|
|
|
3191
3192
|
_CR3XX_INCLUDE_EXTS.get((profile or _profile_for(path.name)).name),
|
|
3192
3193
|
source,
|
|
3193
3194
|
provenance,
|
|
3195
|
+
macros,
|
|
3194
3196
|
)
|
|
3195
3197
|
pull_log = diags.pull_log
|
|
3196
3198
|
parser = _Parser(profile or _profile_for(path.name), diags, provenance)
|
|
@@ -29,6 +29,7 @@ from pathlib import Path
|
|
|
29
29
|
from typing import NamedTuple
|
|
30
30
|
|
|
31
31
|
from .diagnostics import Diagnostics
|
|
32
|
+
from .macros import MacroError, Macros
|
|
32
33
|
from .sources import DiskSource, SourceProvider
|
|
33
34
|
|
|
34
35
|
_INCLUDE_RE = re.compile(r'^\s*Include\s*\(?\s*"([^"]*)"\s*\)?\s*$', re.IGNORECASE)
|
|
@@ -302,7 +303,9 @@ class _Loader:
|
|
|
302
303
|
cr3xx_exts: tuple[str, ...] | None = None,
|
|
303
304
|
source: SourceProvider | None = None,
|
|
304
305
|
provenance: bool = True,
|
|
306
|
+
macros: bool = False,
|
|
305
307
|
):
|
|
308
|
+
self.macros = Macros() if macros else None # the entry file's macros (`macros.py`)
|
|
306
309
|
self.provenance = provenance # False: skip `LogicalLine.pieces` (only the language server reads them)
|
|
307
310
|
self.source = source or DiskSource()
|
|
308
311
|
self.includes: dict[str, Path] = {} # include display name -> the path it resolved to
|
|
@@ -416,6 +419,22 @@ class _Loader:
|
|
|
416
419
|
if text.strip() == "":
|
|
417
420
|
i += 1
|
|
418
421
|
continue
|
|
422
|
+
if self.macros is not None and occ == 0:
|
|
423
|
+
try:
|
|
424
|
+
expanded = self.macros.feed(text, start_line)
|
|
425
|
+
except MacroError as e:
|
|
426
|
+
self.diags.error(e.line, str(e), filename)
|
|
427
|
+
expanded = None
|
|
428
|
+
if expanded != text:
|
|
429
|
+
# an expansion is reported, and spans, where the macro is used; no `pieces`
|
|
430
|
+
for part in (expanded or "").split("\n"):
|
|
431
|
+
if part.strip():
|
|
432
|
+
phys = (start_line, i + 1)
|
|
433
|
+
out.append(
|
|
434
|
+
LogicalLine(part, last_line - shift, filename, section, phys=phys, occurrence=occ)
|
|
435
|
+
)
|
|
436
|
+
i += 1
|
|
437
|
+
continue
|
|
419
438
|
pulls, bad = 0, None
|
|
420
439
|
orig = text
|
|
421
440
|
orig_pieces = pieces
|
|
@@ -570,13 +589,19 @@ def load_program(
|
|
|
570
589
|
cr3xx_exts: tuple[str, ...] | None = None,
|
|
571
590
|
source: SourceProvider | None = None,
|
|
572
591
|
provenance: bool = True,
|
|
592
|
+
macros: bool = False,
|
|
573
593
|
) -> tuple[list[LogicalLine], Diagnostics]:
|
|
574
594
|
"""Load `path` and every file it `Include`s/`IncludeSection`s, as one logical-line stream."""
|
|
575
595
|
path = Path(path)
|
|
576
596
|
diags = Diagnostics()
|
|
577
|
-
loader = _Loader(path.parent, diags, own_ext, pull_init, cr3xx_exts, source, provenance)
|
|
597
|
+
loader = _Loader(path.parent, diags, own_ext, pull_init, cr3xx_exts, source, provenance, macros)
|
|
578
598
|
loader.pull_limit = pull_limit
|
|
579
599
|
lines = loader.load_file(path, section=None, in_include=False)
|
|
600
|
+
try:
|
|
601
|
+
if loader.macros is not None:
|
|
602
|
+
loader.macros.finish()
|
|
603
|
+
except MacroError as e:
|
|
604
|
+
diags.error(e.line, str(e), path.name)
|
|
580
605
|
diags.pull_log = loader.pull_log
|
|
581
606
|
diags.pull_site = loader.site
|
|
582
607
|
diags.include_paths = loader.includes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opencrbasic-1.0.0 → opencrbasic-1.1.0}/src/opencrbasic/data/cr1000x/background_calibration.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|