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,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
+ )