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
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Structure-variable use in assignments: an unknown member (`inp.flagX`) and, on CR300/CR350,
|
|
2
|
+
a structure on one side of `=` only. Rules and oracle evidence: `docs/checker.md` "Structure
|
|
3
|
+
member and assignment checks" (`tests/fixtures/decl_structs.json`).
|
|
4
|
+
|
|
5
|
+
Deliberately narrow: only a plain (non-compound) `Assignment` whose every name is a declared
|
|
6
|
+
variable of a known type is judged; anything else (undefined names, aliases, pointers, nested
|
|
7
|
+
structure members, functions) is left silent, the safe side of AGENTS.md.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .. import syntax as S
|
|
11
|
+
from ..diagnostics import Diagnostic, Diagnostics
|
|
12
|
+
from . import expressions, structure
|
|
13
|
+
|
|
14
|
+
_SCALAR_TYPES = frozenset({"FLOAT", "LONG", "BOOLEAN", "STRING", "DOUBLE"})
|
|
15
|
+
_WRONG_TYPE = "Structured Variable assignment illegal, wrong type"
|
|
16
|
+
_ILLEGAL = "Illegal structure type assignment"
|
|
17
|
+
_UNMODELLED = [("?", "", 0)]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _structure_members(program: S.Program) -> dict[str, dict[str, str]]:
|
|
21
|
+
"""Upper-case `StructureType` name -> {upper-case member name -> upper-case type}."""
|
|
22
|
+
result: dict[str, dict[str, str]] = {}
|
|
23
|
+
current: dict[str, str] | None = None
|
|
24
|
+
for st in program.statements:
|
|
25
|
+
if (
|
|
26
|
+
isinstance(st, S.Instruction)
|
|
27
|
+
and st.keyword.upper() == "STRUCTURETYPE"
|
|
28
|
+
and st.args
|
|
29
|
+
and isinstance(st.args[0], S.Name)
|
|
30
|
+
):
|
|
31
|
+
current = result.setdefault(st.args[0].name.upper(), {})
|
|
32
|
+
elif isinstance(st, S.Instruction) and st.keyword.upper() == "ENDSTRUCTURETYPE":
|
|
33
|
+
current = None
|
|
34
|
+
elif current is not None and isinstance(st, S.Declaration) and st.kind == "STRUCTUREMEMBER":
|
|
35
|
+
for v in st.vars:
|
|
36
|
+
current[v.name.upper()] = (v.type or "FLOAT").upper()
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _variable_kinds(program: S.Program, structs: dict) -> dict[str, str]:
|
|
41
|
+
"""Upper-case variable name -> `"STRUCT:<type>"` or its scalar type. A name declared twice
|
|
42
|
+
differently, or of any other type (pointer, `UInt2`, ...), is left out."""
|
|
43
|
+
kinds: dict[str, str] = {}
|
|
44
|
+
clash: set[str] = set()
|
|
45
|
+
for st in program.statements:
|
|
46
|
+
if not (isinstance(st, S.Declaration) and st.kind in ("PUBLIC", "DIM", "STATIC")):
|
|
47
|
+
continue
|
|
48
|
+
for v in st.vars:
|
|
49
|
+
kind = (v.type or "FLOAT").upper()
|
|
50
|
+
if v.paren_dim: # a parenthesised dimension makes a Float array (docs/checker.md)
|
|
51
|
+
kind = "FLOAT"
|
|
52
|
+
if kind in structs:
|
|
53
|
+
kind = f"STRUCT:{kind}"
|
|
54
|
+
elif kind not in _SCALAR_TYPES:
|
|
55
|
+
clash.add(v.name.upper())
|
|
56
|
+
continue
|
|
57
|
+
if kinds.setdefault(v.name.upper(), kind) != kind:
|
|
58
|
+
clash.add(v.name.upper())
|
|
59
|
+
return {k: v for k, v in kinds.items() if k not in clash}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _names(expr: S.Expr | None) -> list[str]:
|
|
63
|
+
"""Every variable-like name inside `expr`, or `["?"]` for a node this module does not model."""
|
|
64
|
+
if expr is None or isinstance(expr, (S.Number, S.String)):
|
|
65
|
+
return []
|
|
66
|
+
if isinstance(expr, S.Name):
|
|
67
|
+
return [expr.name.upper()]
|
|
68
|
+
if isinstance(expr, S.Index):
|
|
69
|
+
return [expr.name.upper()] + [n for a in expr.args for n in _names(a)]
|
|
70
|
+
if isinstance(expr, S.Unary):
|
|
71
|
+
return _names(expr.operand)
|
|
72
|
+
if isinstance(expr, S.Binary):
|
|
73
|
+
return _names(expr.left) + _names(expr.right)
|
|
74
|
+
return ["?"]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _member_chain(expr: S.Member) -> tuple[S.Name | S.Index, list[str]] | None:
|
|
78
|
+
"""`(root, [member, ...])` for `a.b.c` / `a(i).b`, or `None` when an inner link has index
|
|
79
|
+
arguments."""
|
|
80
|
+
path: list[str] = []
|
|
81
|
+
node: S.Expr | None = expr
|
|
82
|
+
while isinstance(node, S.Member):
|
|
83
|
+
if node is not expr and node.args:
|
|
84
|
+
return None
|
|
85
|
+
path.append(node.member)
|
|
86
|
+
node = node.base
|
|
87
|
+
return (node, path[::-1]) if isinstance(node, (S.Name, S.Index)) else None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _member_errors(expr: S.Expr | None, kinds: dict[str, str], structs: dict) -> list[tuple]:
|
|
91
|
+
"""`(base spelling, path text, index)` for each member access in `expr` naming a member its
|
|
92
|
+
structure lacks (or descending below a scalar member); `_UNMODELLED` for anything else."""
|
|
93
|
+
if isinstance(expr, S.Member):
|
|
94
|
+
chain = _member_chain(expr)
|
|
95
|
+
root = chain[0] if chain else None
|
|
96
|
+
kind = kinds.get(root.name.upper()) if root is not None else None
|
|
97
|
+
if chain is None or (kind is not None and not kind.startswith("STRUCT:")):
|
|
98
|
+
return _UNMODELLED # (`G.x`, `Sample1.x` on a plain variable: other messages)
|
|
99
|
+
if kind is None:
|
|
100
|
+
return [] # a table or system-table reference (`Public.Record`)
|
|
101
|
+
root, path = chain
|
|
102
|
+
first = structs[kind[7:]].get(path[0].upper())
|
|
103
|
+
index = 1
|
|
104
|
+
if expr.args:
|
|
105
|
+
a = expr.args[0] if len(expr.args) == 1 else None
|
|
106
|
+
if not isinstance(a, S.Number) or not isinstance(a.value, int):
|
|
107
|
+
return _UNMODELLED
|
|
108
|
+
index = a.value
|
|
109
|
+
if first is None or (len(path) > 1 and first in _SCALAR_TYPES):
|
|
110
|
+
found = [(root.name, ".".join(path), index)]
|
|
111
|
+
elif first in structs or len(path) > 1:
|
|
112
|
+
return _UNMODELLED # nested structure members are not modelled
|
|
113
|
+
else:
|
|
114
|
+
found = []
|
|
115
|
+
for a in root.args if isinstance(root, S.Index) else []:
|
|
116
|
+
found += _member_errors(a, kinds, structs)
|
|
117
|
+
return found
|
|
118
|
+
if isinstance(expr, S.Index):
|
|
119
|
+
return [e for a in expr.args for e in _member_errors(a, kinds, structs)]
|
|
120
|
+
if isinstance(expr, S.Unary):
|
|
121
|
+
return _member_errors(expr.operand, kinds, structs)
|
|
122
|
+
if isinstance(expr, S.Binary):
|
|
123
|
+
return _member_errors(expr.left, kinds, structs) + _member_errors(expr.right, kinds, structs)
|
|
124
|
+
return []
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def check_members(program: S.Program, profile, diags: Diagnostics, flat: S.Program) -> None:
|
|
128
|
+
"""An unknown structure member in an assignment. CR6/CR1000X (pass 1): `In structure inp,
|
|
129
|
+
could not find .flagX`, one line per access, left side first. CR300/CR350: `Could not find
|
|
130
|
+
inp.flagX(1).` per access, and a bad left side ends the statement with `Invalid assignment in
|
|
131
|
+
expression: <line>` (oracle, 2026-10-03)."""
|
|
132
|
+
structs = _structure_members(program)
|
|
133
|
+
if not structs:
|
|
134
|
+
return
|
|
135
|
+
kinds = _variable_kinds(program, structs)
|
|
136
|
+
declared = {v.name.upper() for st in program.statements if isinstance(st, S.Declaration) for v in st.vars}
|
|
137
|
+
for st in program.statements: # (`Sub S(C As Pt)`: `C.y` is checked too, oracle 2026-10-12)
|
|
138
|
+
if isinstance(st, S.SubDef):
|
|
139
|
+
for p in st.params:
|
|
140
|
+
if p.type and p.type.upper() in structs and p.name.upper() not in declared:
|
|
141
|
+
kinds.setdefault(p.name.upper(), f"STRUCT:{p.type.upper()}")
|
|
142
|
+
known = expressions._known_names(program, profile)
|
|
143
|
+
for st in flat.statements:
|
|
144
|
+
if (
|
|
145
|
+
not isinstance(st, S.Assignment)
|
|
146
|
+
or st.compound
|
|
147
|
+
or expressions._undefined_names(st.target, known)
|
|
148
|
+
or expressions._undefined_names(st.value, known)
|
|
149
|
+
):
|
|
150
|
+
continue
|
|
151
|
+
left, right = _member_errors(st.target, kinds, structs), _member_errors(st.value, kinds, structs)
|
|
152
|
+
if any(e[0] == "?" for e in left + right) or not (left or right):
|
|
153
|
+
continue
|
|
154
|
+
if not profile.single_pass:
|
|
155
|
+
for base, path, _ in left + right:
|
|
156
|
+
diags.error(st.line, f"In structure {base}, could not find .{path}", st.file)
|
|
157
|
+
elif left:
|
|
158
|
+
base, path, index = left[0]
|
|
159
|
+
sep = "\n" if profile.fallback_echo_newline and st.at_eol else ""
|
|
160
|
+
diags.items += [
|
|
161
|
+
Diagnostic("error", st.line, f"Could not find {base}.{path}({index}).", st.file, cascade=False),
|
|
162
|
+
Diagnostic(
|
|
163
|
+
"error", st.line, f"Invalid assignment in expression: {st.text}{sep}.", st.file, cascade=False
|
|
164
|
+
),
|
|
165
|
+
]
|
|
166
|
+
else:
|
|
167
|
+
diags.items += [
|
|
168
|
+
Diagnostic("error", st.line, f"Could not find {base}.{path}({index}).", st.file, cascade=False)
|
|
169
|
+
for base, path, index in right
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _operand_kind(expr: S.Expr | None, kinds: dict[str, str], structs: dict) -> str | None:
|
|
174
|
+
"""`"S"` (one structure), `"SA()"` (`name()` of a structure array), `"N"` (a non-structure
|
|
175
|
+
value), `"M"` (a scalar structure member, which no target rejects), or `None` when not
|
|
176
|
+
modelled."""
|
|
177
|
+
if isinstance(expr, (S.Number, S.String)):
|
|
178
|
+
return "N"
|
|
179
|
+
if isinstance(expr, (S.Name, S.Index)):
|
|
180
|
+
kind = kinds.get(expr.name.upper())
|
|
181
|
+
if kind is None:
|
|
182
|
+
return None
|
|
183
|
+
if not kind.startswith("STRUCT:"):
|
|
184
|
+
return "N"
|
|
185
|
+
return "SA()" if isinstance(expr, S.Index) and not expr.args else "S"
|
|
186
|
+
if isinstance(expr, S.Member):
|
|
187
|
+
chain = _member_chain(expr)
|
|
188
|
+
kind = kinds.get(chain[0].name.upper()) if chain else None
|
|
189
|
+
members = structs.get(kind[7:], {}) if kind and kind.startswith("STRUCT:") else {}
|
|
190
|
+
if chain and len(chain[1]) == 1 and members.get(chain[1][0].upper()) in _SCALAR_TYPES:
|
|
191
|
+
return "M" # a scalar member (`inp.value`): accepted on either kind of target
|
|
192
|
+
return None
|
|
193
|
+
if isinstance(expr, (S.Unary, S.Binary)):
|
|
194
|
+
names = _names(expr)
|
|
195
|
+
if "?" in names or any(n not in kinds for n in names):
|
|
196
|
+
return None
|
|
197
|
+
# `-inp` is a non-structure value; a binary expression over a structure is not modelled
|
|
198
|
+
if isinstance(expr, S.Binary) and any(kinds[n].startswith("STRUCT:") for n in names):
|
|
199
|
+
return None
|
|
200
|
+
return "N"
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def check_struct_assignment(program: S.Program, profile, diags: Diagnostics, flat: S.Program) -> None:
|
|
205
|
+
"""CR300/CR350: `=` with a structure on one side only (oracle, 2026-10-03). Structure target,
|
|
206
|
+
other source: `Structured Variable assignment illegal, wrong type`; other target, structure
|
|
207
|
+
source: `Illegal structure type assignment`; a `name()` structure array on either side adds
|
|
208
|
+
`Invalid, or out of place expression: <line>`, and as target always gives the wrong-type line.
|
|
209
|
+
CR6/CR1000X accept all of these."""
|
|
210
|
+
if not profile.single_pass:
|
|
211
|
+
return
|
|
212
|
+
structs = _structure_members(program)
|
|
213
|
+
if not structs:
|
|
214
|
+
return
|
|
215
|
+
kinds = _variable_kinds(program, structs)
|
|
216
|
+
for st in flat.statements:
|
|
217
|
+
if not isinstance(st, S.Assignment) or st.compound or not isinstance(st.target, (S.Name, S.Index)):
|
|
218
|
+
continue
|
|
219
|
+
left, right = _operand_kind(st.target, kinds, structs), _operand_kind(st.value, kinds, structs)
|
|
220
|
+
if left is None or right is None:
|
|
221
|
+
continue
|
|
222
|
+
if left == "SA()":
|
|
223
|
+
messages = [_WRONG_TYPE, None]
|
|
224
|
+
elif left == "S" and right == "N":
|
|
225
|
+
messages = [_WRONG_TYPE]
|
|
226
|
+
elif left == "S" and right == "SA()":
|
|
227
|
+
messages = [None]
|
|
228
|
+
elif left == "N" and right == "S":
|
|
229
|
+
messages = [_ILLEGAL]
|
|
230
|
+
elif left == "N" and right == "SA()":
|
|
231
|
+
messages = [_ILLEGAL, None]
|
|
232
|
+
else:
|
|
233
|
+
continue
|
|
234
|
+
for m in messages:
|
|
235
|
+
# (no `Too many parameters` cascade: oracle, 2026-10-03)
|
|
236
|
+
diags.items.append(
|
|
237
|
+
Diagnostic(
|
|
238
|
+
"error", st.line, m or structure._out_of_place(profile, st.text, st.at_eol), st.file, cascade=False
|
|
239
|
+
)
|
|
240
|
+
)
|