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.
Files changed (117) hide show
  1. opencrbasic/__init__.py +3 -0
  2. opencrbasic/__main__.py +5 -0
  3. opencrbasic/analysis.py +184 -0
  4. opencrbasic/bindings.py +1034 -0
  5. opencrbasic/catalog.py +173 -0
  6. opencrbasic/checkconfig.py +144 -0
  7. opencrbasic/checker/__init__.py +977 -0
  8. opencrbasic/checker/cr3xx.py +3118 -0
  9. opencrbasic/checker/ess.py +81 -0
  10. opencrbasic/checker/expressions.py +3581 -0
  11. opencrbasic/checker/instr_data.py +1802 -0
  12. opencrbasic/checker/instr_measure.py +1547 -0
  13. opencrbasic/checker/instr_rules.py +3540 -0
  14. opencrbasic/checker/instructions.py +17 -0
  15. opencrbasic/checker/intervals.py +678 -0
  16. opencrbasic/checker/longconv.py +133 -0
  17. opencrbasic/checker/scan_time.py +580 -0
  18. opencrbasic/checker/storage.py +456 -0
  19. opencrbasic/checker/structs.py +240 -0
  20. opencrbasic/checker/structure.py +6213 -0
  21. opencrbasic/checker/symbols.py +609 -0
  22. opencrbasic/checker/terminals.py +249 -0
  23. opencrbasic/checker/unused.py +186 -0
  24. opencrbasic/checker/var_types.py +564 -0
  25. opencrbasic/checker/warnings.py +661 -0
  26. opencrbasic/checks.py +491 -0
  27. opencrbasic/cli.py +391 -0
  28. opencrbasic/commands.py +128 -0
  29. opencrbasic/compiler.py +320 -0
  30. opencrbasic/consteval.py +327 -0
  31. opencrbasic/data/background_calibration.json +4 -0
  32. opencrbasic/data/before_beginprog.json +99 -0
  33. opencrbasic/data/cr1000x/background_calibration.json +4 -0
  34. opencrbasic/data/cr1000x/before_beginprog.json +138 -0
  35. opencrbasic/data/cr1000x/ess_variables.json +1087 -0
  36. opencrbasic/data/cr1000x/fnotch.json +29 -0
  37. opencrbasic/data/cr1000x/functions.json +1337 -0
  38. opencrbasic/data/cr1000x/instructions.json +4836 -0
  39. opencrbasic/data/cr1000x/measure.json +121 -0
  40. opencrbasic/data/cr1000x/mode.json +111 -0
  41. opencrbasic/data/cr1000x/pipeline_tasks.json +88 -0
  42. opencrbasic/data/cr1000x/settings_fields.json +321 -0
  43. opencrbasic/data/cr1000x/status_members.json +318 -0
  44. opencrbasic/data/cr1000x/symbols.json +872 -0
  45. opencrbasic/data/cr1000x/terminals.json +225 -0
  46. opencrbasic/data/cr300/before_beginprog.json +42 -0
  47. opencrbasic/data/cr300/ess_variables.json +1087 -0
  48. opencrbasic/data/cr300/fnotch.json +22 -0
  49. opencrbasic/data/cr300/functions.json +1261 -0
  50. opencrbasic/data/cr300/instructions.json +2837 -0
  51. opencrbasic/data/cr300/measure.json +33 -0
  52. opencrbasic/data/cr300/mode.json +121 -0
  53. opencrbasic/data/cr300/settings_fields.json +259 -0
  54. opencrbasic/data/cr300/status_members.json +303 -0
  55. opencrbasic/data/cr300/symbols.json +584 -0
  56. opencrbasic/data/cr300/terminals.json +193 -0
  57. opencrbasic/data/cr350/before_beginprog.json +44 -0
  58. opencrbasic/data/cr350/ess_variables.json +1087 -0
  59. opencrbasic/data/cr350/fnotch.json +22 -0
  60. opencrbasic/data/cr350/functions.json +1262 -0
  61. opencrbasic/data/cr350/instructions.json +2848 -0
  62. opencrbasic/data/cr350/measure.json +35 -0
  63. opencrbasic/data/cr350/mode.json +121 -0
  64. opencrbasic/data/cr350/settings_fields.json +292 -0
  65. opencrbasic/data/cr350/status_members.json +303 -0
  66. opencrbasic/data/cr350/symbols.json +572 -0
  67. opencrbasic/data/cr350/terminals.json +190 -0
  68. opencrbasic/data/ess_variables.json +1087 -0
  69. opencrbasic/data/fnotch.json +34 -0
  70. opencrbasic/data/functions.json +1337 -0
  71. opencrbasic/data/instructions.json +4965 -0
  72. opencrbasic/data/measure.json +123 -0
  73. opencrbasic/data/mode.json +121 -0
  74. opencrbasic/data/notices_cr6.json +890 -0
  75. opencrbasic/data/pipeline_tasks.json +84 -0
  76. opencrbasic/data/settings_fields.json +322 -0
  77. opencrbasic/data/status_members.json +319 -0
  78. opencrbasic/data/status_table.json +684 -0
  79. opencrbasic/data/symbols.json +902 -0
  80. opencrbasic/data/terminals.json +195 -0
  81. opencrbasic/data/type_codes.json +249 -0
  82. opencrbasic/diagnostics.py +196 -0
  83. opencrbasic/editing.py +827 -0
  84. opencrbasic/formatting.py +310 -0
  85. opencrbasic/helpdocs.py +677 -0
  86. opencrbasic/hover.py +667 -0
  87. opencrbasic/inlay.py +169 -0
  88. opencrbasic/lexer.py +183 -0
  89. opencrbasic/lsp/__init__.py +5 -0
  90. opencrbasic/lsp/__main__.py +7 -0
  91. opencrbasic/lsp/config.py +202 -0
  92. opencrbasic/lsp/features.py +602 -0
  93. opencrbasic/lsp/server.py +741 -0
  94. opencrbasic/lsp/session.py +1189 -0
  95. opencrbasic/lsp/worker.py +128 -0
  96. opencrbasic/mode.py +143 -0
  97. opencrbasic/navigation.py +401 -0
  98. opencrbasic/parser.py +3264 -0
  99. opencrbasic/positions.py +274 -0
  100. opencrbasic/preprocess.py +269 -0
  101. opencrbasic/profiles.py +266 -0
  102. opencrbasic/provenance.py +17 -0
  103. opencrbasic/quickfix.py +331 -0
  104. opencrbasic/rename.py +662 -0
  105. opencrbasic/signature.py +33 -0
  106. opencrbasic/source.py +600 -0
  107. opencrbasic/sources.py +125 -0
  108. opencrbasic/syntax.py +337 -0
  109. opencrbasic/tables.py +2173 -0
  110. opencrbasic/tables_hook.py +14 -0
  111. opencrbasic/tdf.py +142 -0
  112. opencrbasic/timing.py +565 -0
  113. opencrbasic-1.0.0.dist-info/METADATA +223 -0
  114. opencrbasic-1.0.0.dist-info/RECORD +117 -0
  115. opencrbasic-1.0.0.dist-info/WHEEL +4 -0
  116. opencrbasic-1.0.0.dist-info/entry_points.txt +3 -0
  117. opencrbasic-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -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())
@@ -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
+ )