opencrbasic 1.0.0__py3-none-any.whl
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/__init__.py +3 -0
- opencrbasic/__main__.py +5 -0
- opencrbasic/analysis.py +184 -0
- opencrbasic/bindings.py +1034 -0
- opencrbasic/catalog.py +173 -0
- opencrbasic/checkconfig.py +144 -0
- opencrbasic/checker/__init__.py +977 -0
- opencrbasic/checker/cr3xx.py +3118 -0
- opencrbasic/checker/ess.py +81 -0
- opencrbasic/checker/expressions.py +3581 -0
- opencrbasic/checker/instr_data.py +1802 -0
- opencrbasic/checker/instr_measure.py +1547 -0
- opencrbasic/checker/instr_rules.py +3540 -0
- opencrbasic/checker/instructions.py +17 -0
- opencrbasic/checker/intervals.py +678 -0
- opencrbasic/checker/longconv.py +133 -0
- opencrbasic/checker/scan_time.py +580 -0
- opencrbasic/checker/storage.py +456 -0
- opencrbasic/checker/structs.py +240 -0
- opencrbasic/checker/structure.py +6213 -0
- opencrbasic/checker/symbols.py +609 -0
- opencrbasic/checker/terminals.py +249 -0
- opencrbasic/checker/unused.py +186 -0
- opencrbasic/checker/var_types.py +564 -0
- opencrbasic/checker/warnings.py +661 -0
- opencrbasic/checks.py +491 -0
- opencrbasic/cli.py +391 -0
- opencrbasic/commands.py +128 -0
- opencrbasic/compiler.py +320 -0
- opencrbasic/consteval.py +327 -0
- opencrbasic/data/background_calibration.json +4 -0
- opencrbasic/data/before_beginprog.json +99 -0
- opencrbasic/data/cr1000x/background_calibration.json +4 -0
- opencrbasic/data/cr1000x/before_beginprog.json +138 -0
- opencrbasic/data/cr1000x/ess_variables.json +1087 -0
- opencrbasic/data/cr1000x/fnotch.json +29 -0
- opencrbasic/data/cr1000x/functions.json +1337 -0
- opencrbasic/data/cr1000x/instructions.json +4836 -0
- opencrbasic/data/cr1000x/measure.json +121 -0
- opencrbasic/data/cr1000x/mode.json +111 -0
- opencrbasic/data/cr1000x/pipeline_tasks.json +88 -0
- opencrbasic/data/cr1000x/settings_fields.json +321 -0
- opencrbasic/data/cr1000x/status_members.json +318 -0
- opencrbasic/data/cr1000x/symbols.json +872 -0
- opencrbasic/data/cr1000x/terminals.json +225 -0
- opencrbasic/data/cr300/before_beginprog.json +42 -0
- opencrbasic/data/cr300/ess_variables.json +1087 -0
- opencrbasic/data/cr300/fnotch.json +22 -0
- opencrbasic/data/cr300/functions.json +1261 -0
- opencrbasic/data/cr300/instructions.json +2837 -0
- opencrbasic/data/cr300/measure.json +33 -0
- opencrbasic/data/cr300/mode.json +121 -0
- opencrbasic/data/cr300/settings_fields.json +259 -0
- opencrbasic/data/cr300/status_members.json +303 -0
- opencrbasic/data/cr300/symbols.json +584 -0
- opencrbasic/data/cr300/terminals.json +193 -0
- opencrbasic/data/cr350/before_beginprog.json +44 -0
- opencrbasic/data/cr350/ess_variables.json +1087 -0
- opencrbasic/data/cr350/fnotch.json +22 -0
- opencrbasic/data/cr350/functions.json +1262 -0
- opencrbasic/data/cr350/instructions.json +2848 -0
- opencrbasic/data/cr350/measure.json +35 -0
- opencrbasic/data/cr350/mode.json +121 -0
- opencrbasic/data/cr350/settings_fields.json +292 -0
- opencrbasic/data/cr350/status_members.json +303 -0
- opencrbasic/data/cr350/symbols.json +572 -0
- opencrbasic/data/cr350/terminals.json +190 -0
- opencrbasic/data/ess_variables.json +1087 -0
- opencrbasic/data/fnotch.json +34 -0
- opencrbasic/data/functions.json +1337 -0
- opencrbasic/data/instructions.json +4965 -0
- opencrbasic/data/measure.json +123 -0
- opencrbasic/data/mode.json +121 -0
- opencrbasic/data/notices_cr6.json +890 -0
- opencrbasic/data/pipeline_tasks.json +84 -0
- opencrbasic/data/settings_fields.json +322 -0
- opencrbasic/data/status_members.json +319 -0
- opencrbasic/data/status_table.json +684 -0
- opencrbasic/data/symbols.json +902 -0
- opencrbasic/data/terminals.json +195 -0
- opencrbasic/data/type_codes.json +249 -0
- opencrbasic/diagnostics.py +196 -0
- opencrbasic/editing.py +827 -0
- opencrbasic/formatting.py +310 -0
- opencrbasic/helpdocs.py +677 -0
- opencrbasic/hover.py +667 -0
- opencrbasic/inlay.py +169 -0
- opencrbasic/lexer.py +183 -0
- opencrbasic/lsp/__init__.py +5 -0
- opencrbasic/lsp/__main__.py +7 -0
- opencrbasic/lsp/config.py +202 -0
- opencrbasic/lsp/features.py +602 -0
- opencrbasic/lsp/server.py +741 -0
- opencrbasic/lsp/session.py +1189 -0
- opencrbasic/lsp/worker.py +128 -0
- opencrbasic/mode.py +143 -0
- opencrbasic/navigation.py +401 -0
- opencrbasic/parser.py +3264 -0
- opencrbasic/positions.py +274 -0
- opencrbasic/preprocess.py +269 -0
- opencrbasic/profiles.py +266 -0
- opencrbasic/provenance.py +17 -0
- opencrbasic/quickfix.py +331 -0
- opencrbasic/rename.py +662 -0
- opencrbasic/signature.py +33 -0
- opencrbasic/source.py +600 -0
- opencrbasic/sources.py +125 -0
- opencrbasic/syntax.py +337 -0
- opencrbasic/tables.py +2173 -0
- opencrbasic/tables_hook.py +14 -0
- opencrbasic/tdf.py +142 -0
- opencrbasic/timing.py +565 -0
- opencrbasic-1.0.0.dist-info/METADATA +223 -0
- opencrbasic-1.0.0.dist-info/RECORD +117 -0
- opencrbasic-1.0.0.dist-info/WHEEL +4 -0
- opencrbasic-1.0.0.dist-info/entry_points.txt +3 -0
- opencrbasic-1.0.0.dist-info/licenses/LICENSE +21 -0
opencrbasic/__init__.py
ADDED
opencrbasic/__main__.py
ADDED
opencrbasic/analysis.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Analysis facade for editor tooling: one call that runs the CLI's compile driver over a program
|
|
2
|
+
whose files may have unsaved edits, and returns structured diagnostics plus the file dependencies
|
|
3
|
+
the result was computed from. No second pipeline: `analyze` is `compiler.compile_file` with a
|
|
4
|
+
`SourceProvider`. See `docs/lsp-api.md` "Analysis facade (API_VERSION 1)".
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from types import MappingProxyType
|
|
11
|
+
|
|
12
|
+
from . import checkconfig, checks, positions, profiles
|
|
13
|
+
from .compiler import compile_file
|
|
14
|
+
from .sources import DiskSource, LimitedSource, OverlaySource, RecordingSource, SourceProvider
|
|
15
|
+
|
|
16
|
+
API_VERSION = 2 # 2: findings, notices, config_error, AnalysisRequest.checks (M6)
|
|
17
|
+
DOCS = "https://github.com/UtrechtUniversity/firnmelt-datalogger/blob/main/opencrbasic/"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _no_overlays() -> Mapping[Path, bytes]:
|
|
21
|
+
return MappingProxyType({})
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _no_sizes() -> Mapping[Path, int]:
|
|
25
|
+
return MappingProxyType({})
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class AnalysisRequest:
|
|
30
|
+
entry: Path # the entry program (made absolute, not symlink-resolved)
|
|
31
|
+
logger: str = "cr6" # profile name, never inferred from the extension
|
|
32
|
+
overlays: Mapping[Path, bytes] = field(default_factory=_no_overlays) # unsaved buffers, encoded
|
|
33
|
+
warn_unused: bool = False
|
|
34
|
+
signature: bool = False # compute ProgSignature (reads every included file again)
|
|
35
|
+
max_file_bytes: int | None = None # a larger file reads as missing and is listed in `oversized`
|
|
36
|
+
checks: Mapping[str, str] = field(default_factory=lambda: MappingProxyType({})) # LSP severities by code
|
|
37
|
+
notices: bool = False # also the coverage notices (`checks.notices`)
|
|
38
|
+
config_root: Path | None = None # `opencrbasic.toml` is not searched above this directory
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class AnalysisDiagnostic:
|
|
43
|
+
severity: str # "error" | "warning"
|
|
44
|
+
message: str # compiler text, unchanged
|
|
45
|
+
line: int # compiler-reported line (0: none)
|
|
46
|
+
file: str # compiler display name ("" or entry name: the entry)
|
|
47
|
+
path: Path | None # resolved file; None when the file cannot be resolved
|
|
48
|
+
rendered: str # `Diagnostic.cr6comp()` text
|
|
49
|
+
span: tuple[int, int] | None = None # physical first/last line in `path`'s file; None: unknown
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class AnalysisFinding:
|
|
54
|
+
"""An opencrbasic check (`channel` "check") or a coverage notice ("notice"); never a compiler
|
|
55
|
+
diagnostic (`PLAN.md` "Three diagnostic channels")."""
|
|
56
|
+
|
|
57
|
+
channel: str # "check" | "notice"
|
|
58
|
+
code: str # `OCB...`, or "notice"
|
|
59
|
+
severity: str # check: "error" | "warning" | "info"; notice: "hint"
|
|
60
|
+
message: str
|
|
61
|
+
line: int
|
|
62
|
+
file: str # display name as for `AnalysisDiagnostic.file`
|
|
63
|
+
path: Path | None
|
|
64
|
+
link: str # URL of the evidence
|
|
65
|
+
span: tuple[int, int] | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class Dependencies:
|
|
70
|
+
reads: Mapping[Path, str | None] # path -> sha256 hex of the bytes, None: missing
|
|
71
|
+
listings: Mapping[Path, tuple[str, ...] | None] # directory -> sorted file names, None: no dir
|
|
72
|
+
oversized: Mapping[Path, int] = field(default_factory=_no_sizes) # path -> size, over the limit
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class AnalysisResult:
|
|
77
|
+
api_version: int
|
|
78
|
+
request: AnalysisRequest
|
|
79
|
+
ok: bool
|
|
80
|
+
mode: str | None
|
|
81
|
+
diagnostics: tuple[AnalysisDiagnostic, ...] # `result_lines` order, not cut by the 4096-byte buffer
|
|
82
|
+
rendered_lines: tuple[str, ...] # == CompileResult.result_lines(show_signature=...)
|
|
83
|
+
implementation_failures: tuple[str, ...]
|
|
84
|
+
dependencies: Dependencies
|
|
85
|
+
program: object # parsed AST (None when parsing failed); unstable
|
|
86
|
+
findings: tuple[AnalysisFinding, ...] = () # checks, then notices if requested; only when `ok`
|
|
87
|
+
config_error: str | None = None # an invalid `opencrbasic.toml` (no checks ran)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _config(entry: Path, source, root: Path | None) -> tuple[checkconfig.Config, str | None]:
|
|
91
|
+
"""`opencrbasic.toml` as `checkconfig.find` finds it, but not above `root`, read through
|
|
92
|
+
`source` so unsaved text is used and every probed path (found or missing) is a recorded
|
|
93
|
+
dependency."""
|
|
94
|
+
parents = entry.resolve().parents
|
|
95
|
+
if root is not None and root.resolve() in parents:
|
|
96
|
+
parents = parents[: parents.index(root.resolve()) + 1]
|
|
97
|
+
for directory in parents:
|
|
98
|
+
path = directory / checkconfig.FILE_NAME
|
|
99
|
+
data = source.read(path)
|
|
100
|
+
if data is None:
|
|
101
|
+
continue
|
|
102
|
+
try:
|
|
103
|
+
return checkconfig.loads(data.decode("utf-8"), path, checks.DEFAULT_SEVERITY), None
|
|
104
|
+
except UnicodeDecodeError as e:
|
|
105
|
+
return checkconfig.Config(), f"{path}: cannot read: {e}"
|
|
106
|
+
except checkconfig.ConfigError as e:
|
|
107
|
+
return checkconfig.Config(), str(e)
|
|
108
|
+
return checkconfig.Config(), None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def analyze(request: AnalysisRequest) -> AnalysisResult:
|
|
112
|
+
entry = Path(request.entry).absolute()
|
|
113
|
+
overlay = OverlaySource(DiskSource(), request.overlays)
|
|
114
|
+
limited = None if request.max_file_bytes is None else LimitedSource(overlay, request.max_file_bytes)
|
|
115
|
+
inner: SourceProvider = overlay if limited is None else limited
|
|
116
|
+
recorder = RecordingSource(inner)
|
|
117
|
+
result = compile_file(
|
|
118
|
+
entry,
|
|
119
|
+
profiles.get(request.logger),
|
|
120
|
+
warn_unused=request.warn_unused,
|
|
121
|
+
source=recorder,
|
|
122
|
+
signature=request.signature,
|
|
123
|
+
)
|
|
124
|
+
includes = getattr(result.program_ast, "include_paths", None) or {}
|
|
125
|
+
lowered = {name.lower(): path for name, path in includes.items()}
|
|
126
|
+
drive = result.profile.include_drive_prefix
|
|
127
|
+
line_map = getattr(result.program_ast, "line_map", None)
|
|
128
|
+
|
|
129
|
+
def where(file: str, line: int) -> tuple[Path | None, tuple[int, int] | None]:
|
|
130
|
+
if not file or file.lower() == result.program.lower():
|
|
131
|
+
path = entry
|
|
132
|
+
else:
|
|
133
|
+
path = includes.get(file) or lowered.get(file.lower())
|
|
134
|
+
return path, positions.line_span(line_map, file or result.program, line) if line else None
|
|
135
|
+
|
|
136
|
+
diagnostics = []
|
|
137
|
+
for d in sorted(result.diagnostics.items, key=result._sort_key):
|
|
138
|
+
path, span = where(d.file, d.line)
|
|
139
|
+
diagnostics.append(
|
|
140
|
+
AnalysisDiagnostic(d.severity, d.message, d.line, d.file, path, d.cr6comp(result.program, drive), span)
|
|
141
|
+
)
|
|
142
|
+
findings: list[AnalysisFinding] = []
|
|
143
|
+
config_error = None
|
|
144
|
+
if result.ok: # checks are sound only on a program the reference accepts
|
|
145
|
+
config, config_error = _config(entry, recorder, request.config_root)
|
|
146
|
+
if config_error is None:
|
|
147
|
+
checks.analyze(result, entry, config, lsp=dict(request.checks))
|
|
148
|
+
for f in result.findings:
|
|
149
|
+
path, span = where(f.file, f.line)
|
|
150
|
+
findings.append(
|
|
151
|
+
AnalysisFinding(
|
|
152
|
+
"check", f.code, f.severity, f.message, f.line, f.file, path, DOCS + checks.EVIDENCE[f.code], span
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
for n in checks.notices(result.program_ast, result.profile) if request.notices else ():
|
|
156
|
+
path, span = where(n["file"], n["line"])
|
|
157
|
+
rules = n["rules"]
|
|
158
|
+
findings.append(
|
|
159
|
+
AnalysisFinding(
|
|
160
|
+
"notice",
|
|
161
|
+
"notice",
|
|
162
|
+
"hint",
|
|
163
|
+
f"opencrbasic does not model {len(rules)} reference rule{'s' * (len(rules) != 1)} "
|
|
164
|
+
f"of {n['instruction']}: " + "; ".join(rules),
|
|
165
|
+
n["line"],
|
|
166
|
+
n["file"],
|
|
167
|
+
path,
|
|
168
|
+
DOCS + checks.EVIDENCE["notice"],
|
|
169
|
+
span,
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
return AnalysisResult(
|
|
173
|
+
API_VERSION,
|
|
174
|
+
request,
|
|
175
|
+
result.ok,
|
|
176
|
+
result.mode,
|
|
177
|
+
tuple(diagnostics),
|
|
178
|
+
tuple(result.result_lines(show_signature=request.signature)),
|
|
179
|
+
tuple(result.implementation_exceptions),
|
|
180
|
+
Dependencies(dict(recorder.reads), dict(recorder.listings), {} if limited is None else dict(limited.oversized)),
|
|
181
|
+
result.program_ast,
|
|
182
|
+
tuple(findings),
|
|
183
|
+
config_error,
|
|
184
|
+
)
|