opencrbasic 1.1.1__tar.gz → 1.2.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.1.1 → opencrbasic-1.2.0}/PKG-INFO +1 -1
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/pyproject.toml +1 -1
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/__init__.py +1 -1
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/__init__.py +4 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/cr3xx.py +265 -46
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/expressions.py +311 -62
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/instr_data.py +216 -49
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/instr_measure.py +297 -47
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/instr_rules.py +306 -57
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/intervals.py +2 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/structure.py +120 -20
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/symbols.py +10 -5
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/terminals.py +184 -6
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/var_types.py +35 -6
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/warnings.py +8 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/cli.py +6 -1
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/measure.json +3 -1
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/terminals.json +2 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/measure.json +7 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/status_members.json +0 -1
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/terminals.json +1 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/measure.json +7 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/terminals.json +1 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/measure.json +3 -1
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/notices_cr6.json +2 -61
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/terminals.json +11 -1
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/parser.py +96 -26
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/syntax.py +5 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/tables.py +13 -9
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/.gitignore +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/LICENSE +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/README.md +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/__main__.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/analysis.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/bindings.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/catalog.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checkconfig.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/ess.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/instructions.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/longconv.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/scan_time.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/storage.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/structs.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checker/unused.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/checks.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/commands.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/compiler.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/consteval.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/background_calibration.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/before_beginprog.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/background_calibration.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/before_beginprog.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/ess_variables.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/fnotch.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/functions.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/instructions.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/mode.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/pipeline_tasks.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/settings_fields.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/status_members.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr1000x/symbols.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/before_beginprog.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/ess_variables.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/fnotch.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/functions.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/instructions.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/mode.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/settings_fields.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr300/symbols.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/before_beginprog.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/ess_variables.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/fnotch.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/functions.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/instructions.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/mode.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/settings_fields.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/status_members.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/cr350/symbols.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/ess_variables.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/fnotch.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/functions.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/instructions.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/mode.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/pipeline_tasks.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/settings_fields.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/status_members.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/status_table.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/symbols.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/data/type_codes.json +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/diagnostics.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/editing.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/formatting.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/helpdocs.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/hover.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/inlay.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/lexer.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/lsp/__init__.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/lsp/__main__.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/lsp/config.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/lsp/features.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/lsp/server.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/lsp/session.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/lsp/worker.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/macros.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/mode.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/navigation.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/positions.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/preprocess.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/profiles.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/provenance.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/quickfix.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/rename.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/signature.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/source.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/sources.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/tables_hook.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/tdf.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.0}/src/opencrbasic/timing.py +0 -0
- {opencrbasic-1.1.1 → opencrbasic-1.2.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.2.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
|
|
@@ -111,6 +111,7 @@ from . import (
|
|
|
111
111
|
storage,
|
|
112
112
|
structs,
|
|
113
113
|
structure,
|
|
114
|
+
terminals,
|
|
114
115
|
var_types,
|
|
115
116
|
warnings,
|
|
116
117
|
)
|
|
@@ -265,6 +266,7 @@ def _check(program: S.Program, profile: Profile, diags: Diagnostics, result) ->
|
|
|
265
266
|
del diags.items[program.hash_cut :] # parser findings of later lines
|
|
266
267
|
program = structure.open_program_at_slowsequence(program, profile)
|
|
267
268
|
env = consteval.Env(profile, dict(program.consts))
|
|
269
|
+
expressions.set_arity_context(program, profile)
|
|
268
270
|
|
|
269
271
|
_run_pass(structure.check_pass1, program, profile, diags, env)
|
|
270
272
|
# Also pass 1 (unconditional -- oracle-confirmed, `docs/loggers.md`): a `Const`'s own value
|
|
@@ -411,6 +413,7 @@ def _check(program: S.Program, profile: Profile, diags: Diagnostics, result) ->
|
|
|
411
413
|
# "stop entirely at the first placement error" behaviour.
|
|
412
414
|
pass2_start = len(diags.items)
|
|
413
415
|
_run_pass(instr_rules.check_spi_open_order, _with_inline(program), profile, leading)
|
|
416
|
+
_run_pass(terminals.check_i2c_open_order, _with_inline(program), profile, ranges, env)
|
|
414
417
|
# Block-closer messages come before a same-line placement error (`EndIf illegally
|
|
415
418
|
# occurs before BeginProg.` follows `END IF without IF...`).
|
|
416
419
|
# CR300/CR350: collected aside in `leading` (printed first on their line, but they do not
|
|
@@ -743,6 +746,7 @@ def _check(program: S.Program, profile: Profile, diags: Diagnostics, result) ->
|
|
|
743
746
|
# instructions placed outside `DataTable...EndTable`, and an orphan `EndTable`.
|
|
744
747
|
table_stop = _run_pass_capture(instr_data.check_table_structure, _with_inline(program), profile, diags)
|
|
745
748
|
_run_pass(instr_data.check_fieldnames, program, profile, diags, env)
|
|
749
|
+
_run_pass(instr_data.check_fieldclassify, program, profile, diags, env)
|
|
746
750
|
apply_diags = Diagnostics()
|
|
747
751
|
_run_pass(structure.check_apply_restart_sequence, program, profile, apply_diags)
|
|
748
752
|
for extra in apply_diags.items: # in source order among the tier-C findings
|
|
@@ -7,6 +7,7 @@ evidence: `docs/loggers.md`.
|
|
|
7
7
|
|
|
8
8
|
import collections
|
|
9
9
|
import dataclasses
|
|
10
|
+
import math
|
|
10
11
|
import re
|
|
11
12
|
from collections.abc import Set as AbstractSet
|
|
12
13
|
from typing import TypedDict
|
|
@@ -14,7 +15,7 @@ from typing import TypedDict
|
|
|
14
15
|
from .. import bindings, consteval, lexer
|
|
15
16
|
from .. import syntax as S
|
|
16
17
|
from ..diagnostics import Diagnostic, Diagnostics
|
|
17
|
-
from . import expressions, instr_rules, structure, symbols
|
|
18
|
+
from . import expressions, instr_rules, longconv, structure, symbols
|
|
18
19
|
|
|
19
20
|
# `SerialOpen`'s own `ComPort` argument (position 0, the only position checked here) is the one
|
|
20
21
|
# `Serial*` function on CR300/CR350 whose compile-time validator additionally requires a genuine
|
|
@@ -40,6 +41,8 @@ _SERIALOPEN = "SERIALOPEN"
|
|
|
40
41
|
_SERIALOPEN_PORTS = {
|
|
41
42
|
"cr6": frozenset(range(1, 19)) | frozenset(range(31, 100)),
|
|
42
43
|
"cr1000x": frozenset(range(1, 15)) | frozenset(range(31, 100)),
|
|
44
|
+
"cr300": frozenset(range(1, 13)) | frozenset(range(32, 48)),
|
|
45
|
+
"cr350": frozenset(range(1, 18)) | frozenset(range(32, 48)),
|
|
43
46
|
}
|
|
44
47
|
_COMPORT_ARG_POSITION = 0
|
|
45
48
|
# Every `Serial*` function whose first argument is a `ComPort` (`data/<logger>/functions.json`:
|
|
@@ -79,6 +82,33 @@ _COMMS_MODES = {
|
|
|
79
82
|
"COMC3": frozenset({0, 1, 2, 3, 4, 6}),
|
|
80
83
|
"COMC7": frozenset({0, 1, 2, 3, 4, 6}),
|
|
81
84
|
}
|
|
85
|
+
_CR350_MAX_COMMS_MODE = {1: 0, 2: 0, 3: 0, 4: 1, 7: 1, 8: 1, 9: 1, 10: 1}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _check_cr350_comms_mode(
|
|
89
|
+
stmt: S.Call, port: int, profile, consts: dict, diags: Diagnostics, invalid_format: bool, shifted: bool
|
|
90
|
+
) -> None:
|
|
91
|
+
maximum = _CR350_MAX_COMMS_MODE.get(port)
|
|
92
|
+
if maximum is None or (len(stmt.args) < 5 if shifted else len(stmt.args) != 6):
|
|
93
|
+
return
|
|
94
|
+
mode_arg = stmt.args[4 if shifted else 5]
|
|
95
|
+
if longconv._float_typed(mode_arg, frozenset()):
|
|
96
|
+
return
|
|
97
|
+
env = consteval.Env(profile, consts)
|
|
98
|
+
try:
|
|
99
|
+
raw = consteval.value_of(mode_arg, env)
|
|
100
|
+
except consteval.NotConstant:
|
|
101
|
+
return
|
|
102
|
+
if not consteval.is_finite_number(raw) or (
|
|
103
|
+
isinstance(raw, float) and (not -(2**31) <= raw < 2**31 or raw != int(raw))
|
|
104
|
+
):
|
|
105
|
+
return
|
|
106
|
+
fmt = longconv.long_value(stmt.args[2], env, profile) if not shifted else None
|
|
107
|
+
if not shifted and not invalid_format and (fmt is None or not -1 <= fmt <= 15):
|
|
108
|
+
return
|
|
109
|
+
value = longconv.long_value(mode_arg, env, profile)
|
|
110
|
+
if value is not None and (mode := value & 0xFF) > maximum:
|
|
111
|
+
diags.error(stmt.line, f"{mode} is an invalid CommsMode parameter.", stmt.file)
|
|
82
112
|
|
|
83
113
|
|
|
84
114
|
def _commsmode_problem(
|
|
@@ -94,7 +124,7 @@ def _commsmode_problem(
|
|
|
94
124
|
mode = consteval.value_of(stmt.args[index], env)
|
|
95
125
|
except consteval.NotConstant, consteval.DivideByZero, TypeError, ValueError:
|
|
96
126
|
return None
|
|
97
|
-
if isinstance(mode, bool) or not
|
|
127
|
+
if isinstance(mode, bool) or not consteval.is_finite_number(mode) or mode != int(mode):
|
|
98
128
|
return None
|
|
99
129
|
for key, modes in _COMMS_MODES.items():
|
|
100
130
|
record = profile.symbols.get(key)
|
|
@@ -132,6 +162,34 @@ def _format_needs_com1_4(stmt: S.Call, profile, consts: dict | None) -> bool:
|
|
|
132
162
|
return 1 <= port <= 8 and 16 <= fmt < 2**31
|
|
133
163
|
|
|
134
164
|
|
|
165
|
+
def _non_finite(arg: S.Expr | None, profile, consts: dict | None) -> str | None:
|
|
166
|
+
"""`"divide"` for a division by zero, `"float"` for an infinite or NaN constant: neither is
|
|
167
|
+
a constant integer (oracle, all four loggers, 2026-11-14,
|
|
168
|
+
`tests/fixtures/r150_serialopen_non_finite.json`)."""
|
|
169
|
+
if arg is None:
|
|
170
|
+
return None
|
|
171
|
+
if isinstance(arg, S.Name) and arg.name.upper() == "INF" and "INF" not in (consts or {}):
|
|
172
|
+
return "float"
|
|
173
|
+
try:
|
|
174
|
+
value = consteval.value_of(arg, consteval.Env(profile, dict(consts or {})))
|
|
175
|
+
except consteval.DivideByZero:
|
|
176
|
+
return "divide"
|
|
177
|
+
except consteval.NotConstant, TypeError, ValueError, OverflowError, ZeroDivisionError:
|
|
178
|
+
return None
|
|
179
|
+
return "float" if isinstance(value, float) and not math.isfinite(value) else None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _invalid_cr3xx_serial_format(arg: S.Expr, env: consteval.Env) -> bool:
|
|
183
|
+
"""The integer format field on CR300/CR350 rejects options starting at 16 (round 134)."""
|
|
184
|
+
if not isinstance(arg, S.Name) and consteval.is_float(arg, env):
|
|
185
|
+
return False
|
|
186
|
+
try:
|
|
187
|
+
value = consteval.value_of(arg, env)
|
|
188
|
+
except consteval.NotConstant, ValueError, OverflowError:
|
|
189
|
+
return False
|
|
190
|
+
return consteval.is_finite_number(value) and 16 <= value <= 2**31 and value == int(value)
|
|
191
|
+
|
|
192
|
+
|
|
135
193
|
def _non_numeric(expr, profile, consts: dict | None, variable_names) -> bool:
|
|
136
194
|
if expressions._variable_refs(expr, variable_names):
|
|
137
195
|
return True
|
|
@@ -143,7 +201,7 @@ def _non_numeric(expr, profile, consts: dict | None, variable_names) -> bool:
|
|
|
143
201
|
return isinstance(value, str) or expressions._not_constant_integer(expr)
|
|
144
202
|
|
|
145
203
|
|
|
146
|
-
def _check_baud(stmt: S.Call, profile, diags: Diagnostics, consts: dict | None = None) -> None:
|
|
204
|
+
def _check_baud(stmt: S.Call, profile, diags: Diagnostics, consts: dict | None = None, *, port11: bool = False) -> None:
|
|
147
205
|
port = stmt.args[0] if stmt.args else None
|
|
148
206
|
rs232 = isinstance(port, S.Name) and port.name.upper() == "COMRS232"
|
|
149
207
|
# CR6/CR1000X check every valid port (oracle, 2026-10-25, round 35: ComUSB, ComC1, ComSDC7,
|
|
@@ -152,7 +210,7 @@ def _check_baud(stmt: S.Call, profile, diags: Diagnostics, consts: dict | None =
|
|
|
152
210
|
isinstance(port, S.Name)
|
|
153
211
|
and (port.name.upper() in profile.symbols or port.name.upper() in {str(c).upper() for c in consts or {}})
|
|
154
212
|
)
|
|
155
|
-
if len(stmt.args) < 2 or not (rs232 or (not profile.single_pass and known_port)):
|
|
213
|
+
if len(stmt.args) < 2 or not (rs232 or port11 or (not profile.single_pass and known_port)):
|
|
156
214
|
return
|
|
157
215
|
arg = stmt.args[1]
|
|
158
216
|
sign = 1
|
|
@@ -209,9 +267,11 @@ def check(program: S.Program, profile, diags: Diagnostics) -> None:
|
|
|
209
267
|
alongside `expressions.check_consts`.
|
|
210
268
|
"""
|
|
211
269
|
cr3 = profile.name in ("cr300", "cr350")
|
|
270
|
+
from .instr_data import _with_inline
|
|
271
|
+
|
|
212
272
|
known = expressions._known_names(program, profile)
|
|
213
273
|
variable_names = symbols.declared_variable_names(program)
|
|
214
|
-
for stmt in program.statements:
|
|
274
|
+
for stmt in _with_inline(program.statements) if cr3 else program.statements:
|
|
215
275
|
if not isinstance(stmt, S.Call):
|
|
216
276
|
continue
|
|
217
277
|
name = stmt.name.upper()
|
|
@@ -231,6 +291,8 @@ def check(program: S.Program, profile, diags: Diagnostics) -> None:
|
|
|
231
291
|
continue
|
|
232
292
|
is_variable = bool(expressions._variable_refs(arg, variable_names))
|
|
233
293
|
ports = _SERIALOPEN_PORTS.get(profile.name)
|
|
294
|
+
port = None
|
|
295
|
+
sep = "\n" if cr3 and stmt.at_eol else ""
|
|
234
296
|
if ports and not (names or is_variable or isinstance(arg, S.String)):
|
|
235
297
|
# a constant ComPort outside the port codes ends the statement (oracle, round 36/37,
|
|
236
298
|
# docs/checker.md#serialopen-comport-codes-round-37)
|
|
@@ -238,17 +300,62 @@ def check(program: S.Program, profile, diags: Diagnostics) -> None:
|
|
|
238
300
|
port = consteval.value_of(arg, consteval.Env(profile, dict(program.consts or {})))
|
|
239
301
|
except consteval.NotConstant, TypeError, ValueError, ZeroDivisionError:
|
|
240
302
|
port = None
|
|
303
|
+
if cr3 and isinstance(port, bool):
|
|
304
|
+
port = -int(port)
|
|
305
|
+
if cr3 and isinstance(port, (int, float)) and port in (-(2**31), 2**31):
|
|
306
|
+
port = max(-(2**31 - 1), min(2**31 - 1, int(port)))
|
|
307
|
+
elif (
|
|
308
|
+
consteval.is_finite_number(port)
|
|
309
|
+
and not isinstance(port, bool)
|
|
310
|
+
and (
|
|
311
|
+
port != int(port)
|
|
312
|
+
or not -(2**31) <= port < 2**31
|
|
313
|
+
or (cr3 and expressions._not_constant_integer(arg))
|
|
314
|
+
)
|
|
315
|
+
) or (cr3 and isinstance(port, str)):
|
|
316
|
+
diags.error(stmt.line, "ComPort 0 illegal.", stmt.file)
|
|
317
|
+
diags.error(stmt.line, f"Parameter must be a constant integer: {stmt.text}{sep}.", stmt.file)
|
|
318
|
+
continue
|
|
241
319
|
if consteval.is_finite_number(port) and not isinstance(port, bool):
|
|
242
|
-
if port != int(port) or not -(2**31) <= port < 2**31:
|
|
243
|
-
diags.error(stmt.line, "ComPort 0 illegal.", stmt.file)
|
|
244
|
-
diags.error(stmt.line, f"Parameter must be a constant integer: {stmt.text}.", stmt.file)
|
|
245
|
-
continue
|
|
246
320
|
if int(port) not in ports:
|
|
247
|
-
diags.
|
|
248
|
-
|
|
321
|
+
diags.items.append(
|
|
322
|
+
Diagnostic("error", stmt.line, f"ComPort {int(port)} illegal.", stmt.file, trigger=cr3)
|
|
323
|
+
)
|
|
324
|
+
if profile.name != "cr350":
|
|
325
|
+
diags.error(
|
|
326
|
+
stmt.line, f"No Matching, or out of place ) in expression: {stmt.text}{sep}.", stmt.file
|
|
327
|
+
)
|
|
249
328
|
continue
|
|
250
|
-
_check_baud(stmt, profile, diags, program.consts)
|
|
329
|
+
_check_baud(stmt, profile, diags, program.consts, port11=cr3 and port == 11)
|
|
251
330
|
constant_port = not (names or is_variable or isinstance(arg, S.String))
|
|
331
|
+
format_arg = stmt.args[2] if len(stmt.args) > 2 else None
|
|
332
|
+
invalid_format = (
|
|
333
|
+
cr3
|
|
334
|
+
and constant_port
|
|
335
|
+
and consteval.is_finite_number(port)
|
|
336
|
+
and format_arg is not None
|
|
337
|
+
and not expressions._variable_refs(format_arg, variable_names)
|
|
338
|
+
and _invalid_cr3xx_serial_format(format_arg, consteval.Env(profile, dict(program.consts)))
|
|
339
|
+
)
|
|
340
|
+
if invalid_format:
|
|
341
|
+
diags.error(stmt.line, "Invalid format option.", stmt.file)
|
|
342
|
+
cr3_shifted = (
|
|
343
|
+
cr3
|
|
344
|
+
and constant_port
|
|
345
|
+
and format_arg is not None
|
|
346
|
+
and not invalid_format
|
|
347
|
+
and (
|
|
348
|
+
_non_numeric(format_arg, profile, program.consts, variable_names)
|
|
349
|
+
or not isinstance(format_arg, S.Name)
|
|
350
|
+
and longconv._float_typed(format_arg, frozenset())
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
if cr3_shifted:
|
|
354
|
+
if profile.name == "cr350" and consteval.is_finite_number(port):
|
|
355
|
+
_check_cr350_comms_mode(stmt, int(port), profile, program.consts, diags, False, True)
|
|
356
|
+
elif len(stmt.args) > 5:
|
|
357
|
+
diags.error(stmt.line, f"No Matching, or out of place ) in expression: {stmt.text}{sep}.", stmt.file)
|
|
358
|
+
continue
|
|
252
359
|
# CR6/CR1000X: a String or variable `SerialOpenFormat` makes the 5th argument the CommsMode, with
|
|
253
360
|
# no TXDelay range and at most 5 arguments (oracle, 2026-10-28, round 57,
|
|
254
361
|
# docs/checker.md#serialopen-format-and-commsmode-round-57)
|
|
@@ -260,7 +367,7 @@ def check(program: S.Program, profile, diags: Diagnostics) -> None:
|
|
|
260
367
|
and _non_numeric(stmt.args[2], profile, program.consts, variable_names)
|
|
261
368
|
)
|
|
262
369
|
# a bare undeclared TXDelay is lost in the same comma recovery (decompiled `FUN_00494aa0`,
|
|
263
|
-
# oracle round 58, docs/checker.md#serialopen-recovery-
|
|
370
|
+
# oracle round 58, docs/checker.md#serialopen-recovery-rounds-58-and-142)
|
|
264
371
|
shifted = shifted or (not cr3 and constant_port and bare_unknown_delay(stmt, known))
|
|
265
372
|
if (
|
|
266
373
|
not cr3
|
|
@@ -296,16 +403,12 @@ def check(program: S.Program, profile, diags: Diagnostics) -> None:
|
|
|
296
403
|
stmt.line, f"{int(value)} usec delay before transmit is illegal, must be 0..14000000", stmt.file
|
|
297
404
|
)
|
|
298
405
|
# a 6th argument that is no constant integer counts as mode 0, then is rejected (same probes)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
and (
|
|
306
|
-
expressions._not_constant_integer(stmt.args[5])
|
|
307
|
-
or bool(expressions._variable_refs(stmt.args[5], variable_names))
|
|
308
|
-
)
|
|
406
|
+
check_mode = not cr3 and constant_port and not shifted and len(stmt.args) > 5 and stmt.args[5] is not None
|
|
407
|
+
mode_kind = _non_finite(stmt.args[5], profile, program.consts) if check_mode else None
|
|
408
|
+
bad_mode = check_mode and (
|
|
409
|
+
expressions._not_constant_integer(stmt.args[5])
|
|
410
|
+
or bool(expressions._variable_refs(stmt.args[5], variable_names))
|
|
411
|
+
or mode_kind is not None
|
|
309
412
|
)
|
|
310
413
|
if (
|
|
311
414
|
not cr3
|
|
@@ -321,19 +424,55 @@ def check(program: S.Program, profile, diags: Diagnostics) -> None:
|
|
|
321
424
|
else:
|
|
322
425
|
diags.warning(stmt.line, problem[1], stmt.file)
|
|
323
426
|
if bad_mode:
|
|
324
|
-
|
|
427
|
+
verb = "Divide by zero in expression" if mode_kind == "divide" else "Parameter must be a constant integer"
|
|
428
|
+
diags.error(stmt.line, f"{verb}: {stmt.text}.", stmt.file)
|
|
325
429
|
continue
|
|
326
430
|
if shifted and len(stmt.args) > 5:
|
|
327
431
|
diags.error(stmt.line, f"No Matching, or out of place ) in expression: {stmt.text}.", stmt.file)
|
|
328
432
|
continue
|
|
329
433
|
if (
|
|
330
|
-
not (
|
|
434
|
+
not (names or is_variable or isinstance(arg, S.String))
|
|
331
435
|
and len(stmt.args) > 4
|
|
332
436
|
and expressions._variable_refs(stmt.args[4], variable_names)
|
|
333
437
|
):
|
|
334
438
|
# a variable `BufferSize` (oracle, CR6/CR1000X, 2026-10-24, round 34)
|
|
335
|
-
diags.error(stmt.line, f"Parameter must be a constant integer: {stmt.text}.", stmt.file)
|
|
439
|
+
diags.error(stmt.line, f"Parameter must be a constant integer: {stmt.text}{sep}.", stmt.file)
|
|
336
440
|
continue
|
|
441
|
+
if constant_port and len(stmt.args) > 4 and (kind := _non_finite(stmt.args[4], profile, program.consts)):
|
|
442
|
+
# an infinite `BufferSize`, or one that divides by zero (CR300/CR350: one message for both)
|
|
443
|
+
verb = (
|
|
444
|
+
"Divide by zero in expression"
|
|
445
|
+
if kind == "divide" and not cr3
|
|
446
|
+
else "Parameter must be a constant integer"
|
|
447
|
+
)
|
|
448
|
+
diags.error(stmt.line, f"{verb}: {stmt.text}{sep}.", stmt.file)
|
|
449
|
+
continue
|
|
450
|
+
if profile.name == "cr300" and port == 11 and len(stmt.args) > 4:
|
|
451
|
+
buffer = stmt.args[4]
|
|
452
|
+
if not expressions._not_constant_integer(buffer):
|
|
453
|
+
try:
|
|
454
|
+
size = consteval.value_of(buffer, consteval.Env(profile, dict(program.consts)))
|
|
455
|
+
except consteval.NotConstant:
|
|
456
|
+
size = None
|
|
457
|
+
if consteval.is_finite_number(size) and 0 < size < 2**31:
|
|
458
|
+
diags.items.append(Diagnostic("error", stmt.line, "Out of memory.", stmt.file, trigger=True))
|
|
459
|
+
if profile.name == "cr350" and consteval.is_finite_number(port):
|
|
460
|
+
_check_cr350_comms_mode(stmt, int(port), profile, program.consts, diags, invalid_format, False)
|
|
461
|
+
if profile.name == "cr300" and constant_port and len(stmt.args) == 6 and stmt.args[5] is not None:
|
|
462
|
+
mode_arg = stmt.args[5]
|
|
463
|
+
env = consteval.Env(profile, dict(program.consts))
|
|
464
|
+
try:
|
|
465
|
+
mode = consteval.value_of(mode_arg, env)
|
|
466
|
+
except consteval.NotConstant:
|
|
467
|
+
mode = None
|
|
468
|
+
if (
|
|
469
|
+
not consteval.is_finite_number(mode)
|
|
470
|
+
or mode != int(mode)
|
|
471
|
+
or not isinstance(mode_arg, S.Name)
|
|
472
|
+
and consteval.is_float(mode_arg, env)
|
|
473
|
+
or expressions._variable_refs(mode_arg, variable_names)
|
|
474
|
+
):
|
|
475
|
+
diags.error(stmt.line, f"Parameter must be a constant integer: {stmt.text}{sep}.", stmt.file)
|
|
337
476
|
if names or is_variable or isinstance(arg, S.String):
|
|
338
477
|
# A string or variable ComPort, all four loggers, pass 1 (oracle, 2026-10-01:
|
|
339
478
|
# `SerialOpen("fuzz",...)`). CR300/CR350 echo the line end (`docs/loggers.md`).
|
|
@@ -883,18 +1022,38 @@ def check_float_destinations(program: S.Program, profile, diags: Diagnostics) ->
|
|
|
883
1022
|
String variable, a literal, an expression) gives `Illegal destination variable type,
|
|
884
1023
|
expecting Float.` (oracle, CR300, 2026-10-01: `PanelTemp(L,60)`, `PanelTemp(Nope,60)`,
|
|
885
1024
|
`VoltSe(L,1,mV1000,...)`, `Battery(L)`)."""
|
|
1025
|
+
from .instr_data import _destination_env, constant_destination
|
|
1026
|
+
|
|
886
1027
|
if profile.name not in ("cr300", "cr350"):
|
|
887
1028
|
return
|
|
888
1029
|
types: dict[str, str] | None = None
|
|
889
1030
|
scoped: dict[int, dict[str, str]] = {}
|
|
890
1031
|
known = ds = None
|
|
1032
|
+
constant_names = profile.symbols.keys() | expressions._EXTRA_PREDEFINED_SYMBOLS
|
|
1033
|
+
function_names = set(profile.functions)
|
|
1034
|
+
destination_env = _destination_env(consteval.Env(profile))
|
|
1035
|
+
destination_variables = symbols.declared_variable_names(program)
|
|
891
1036
|
params: dict[str, str] = {} # the enclosing routine's parameters hide a global (round 31)
|
|
1037
|
+
scalar_params: set[str] = set()
|
|
892
1038
|
per_line = collections.Counter((st.line, st.file) for st in program.statements)
|
|
893
1039
|
for stmt in program.statements:
|
|
894
1040
|
if isinstance(stmt, S.SubDef):
|
|
895
1041
|
params = {p.name.upper(): (p.type or "FLOAT").upper() for p in stmt.params}
|
|
1042
|
+
scalar_params = {
|
|
1043
|
+
p.name.upper()
|
|
1044
|
+
for p in stmt.params
|
|
1045
|
+
if not p.dim_exprs and (p.dims == 0 or p.type is None) and (p.type or "").upper() != "STRING"
|
|
1046
|
+
}
|
|
1047
|
+
if stmt.keyword.upper() == "FUNCTION":
|
|
1048
|
+
function_names.add(stmt.name.upper())
|
|
1049
|
+
elif isinstance(stmt, S.ConstDecl):
|
|
1050
|
+
name = stmt.name.upper()
|
|
1051
|
+
constant_names.add(name)
|
|
1052
|
+
if name in program.consts:
|
|
1053
|
+
destination_env.consts[name] = program.consts[name]
|
|
896
1054
|
elif S.ends_routine(stmt):
|
|
897
1055
|
params = {}
|
|
1056
|
+
scalar_params = set()
|
|
898
1057
|
if (
|
|
899
1058
|
not isinstance(stmt, S.Instruction)
|
|
900
1059
|
or not stmt.args
|
|
@@ -903,12 +1062,19 @@ def check_float_destinations(program: S.Program, profile, diags: Diagnostics) ->
|
|
|
903
1062
|
):
|
|
904
1063
|
continue
|
|
905
1064
|
info = profile.instructions.get(stmt.keyword.upper())
|
|
906
|
-
|
|
1065
|
+
send_variables = stmt.keyword.upper() == "SENDVARIABLES"
|
|
1066
|
+
named_destination = send_variables or stmt.keyword.upper() in {"MOVE", "ERASE", "READIO", "REALTIME"}
|
|
1067
|
+
invalid_destination = (
|
|
907
1068
|
next(
|
|
908
1069
|
(
|
|
909
1070
|
i
|
|
910
1071
|
for i, (code, a) in enumerate(zip(info["params"], stmt.args))
|
|
911
|
-
if code in (27, 29)
|
|
1072
|
+
if code in (27, 29)
|
|
1073
|
+
and (
|
|
1074
|
+
constant_destination(a, destination_env, params.keys() | destination_variables)
|
|
1075
|
+
if named_destination
|
|
1076
|
+
else a is not None and not isinstance(a, S.String) and not _expr_names(a)
|
|
1077
|
+
)
|
|
912
1078
|
),
|
|
913
1079
|
None,
|
|
914
1080
|
)
|
|
@@ -916,13 +1082,12 @@ def check_float_destinations(program: S.Program, profile, diags: Diagnostics) ->
|
|
|
916
1082
|
else None
|
|
917
1083
|
)
|
|
918
1084
|
if (
|
|
919
|
-
|
|
1085
|
+
invalid_destination is not None
|
|
920
1086
|
and per_line[(stmt.line, stmt.file)] == 1
|
|
921
|
-
and not any((d.line, d.file) == (stmt.line, stmt.file) for d in diags.errors)
|
|
1087
|
+
and (send_variables or not any((d.line, d.file) == (stmt.line, stmt.file) for d in diags.errors))
|
|
922
1088
|
):
|
|
923
|
-
#
|
|
924
|
-
#
|
|
925
|
-
# `Too many parameters: 1.`)
|
|
1089
|
+
# The argument list ends after a rejected destination. The next token is
|
|
1090
|
+
# reported as excess (oracle, CR300/CR350, rounds 31 and 133).
|
|
926
1091
|
at = next(
|
|
927
1092
|
(i for i, d in enumerate(diags.items) if d.file == stmt.file and d.line > stmt.line), len(diags.items)
|
|
928
1093
|
)
|
|
@@ -930,15 +1095,23 @@ def check_float_destinations(program: S.Program, profile, diags: Diagnostics) ->
|
|
|
930
1095
|
Diagnostic(
|
|
931
1096
|
"error",
|
|
932
1097
|
stmt.line,
|
|
933
|
-
f"Invalid Variable or Array: {expressions._arg_text(stmt,
|
|
1098
|
+
f"Invalid Variable or Array: {expressions._arg_text(stmt, invalid_destination).strip()}.",
|
|
934
1099
|
stmt.file,
|
|
935
1100
|
)
|
|
936
1101
|
]
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1102
|
+
following = invalid_destination + 1
|
|
1103
|
+
if len(stmt.args) > following and stmt.args[following] is not None:
|
|
1104
|
+
raw = expressions._arg_text(stmt, following)
|
|
1105
|
+
text = instr_rules._next_simple_token(raw) or raw
|
|
941
1106
|
new.append(Diagnostic("error", stmt.line, f"Too many parameters: {text}.", stmt.file))
|
|
1107
|
+
if send_variables:
|
|
1108
|
+
# The destination aborts before SendVariables reads its remaining arguments.
|
|
1109
|
+
at -= sum(
|
|
1110
|
+
(d.line, d.file) == (stmt.line, stmt.file) and d.severity == "error" for d in diags.items[:at]
|
|
1111
|
+
)
|
|
1112
|
+
diags.items[:] = [
|
|
1113
|
+
d for d in diags.items if (d.line, d.file) != (stmt.line, stmt.file) or d.severity != "error"
|
|
1114
|
+
]
|
|
942
1115
|
diags.items[at:at] = new
|
|
943
1116
|
continue
|
|
944
1117
|
if not info or not info["params"] or info["params"][0] != _FLOAT_DEST:
|
|
@@ -948,14 +1121,33 @@ def check_float_destinations(program: S.Program, profile, diags: Diagnostics) ->
|
|
|
948
1121
|
ds = expressions.program_dims(program, profile, known)
|
|
949
1122
|
assert known is not None and ds is not None
|
|
950
1123
|
arg = stmt.args[0]
|
|
1124
|
+
assert arg is not None
|
|
951
1125
|
if isinstance(arg, S.Name) and arg.name.upper() in profile.functions:
|
|
952
1126
|
continue # a bare function name is `Expression expects (` instead (expressions.py)
|
|
953
1127
|
key = (stmt.line, stmt.file)
|
|
1128
|
+
if isinstance(arg, S.Index) and arg.name.upper() in scalar_params and any(a is not None for a in arg.args):
|
|
1129
|
+
raw = expressions._arg_text(stmt, 0).strip()
|
|
1130
|
+
diags.items[:] = [
|
|
1131
|
+
d
|
|
1132
|
+
for d in diags.items
|
|
1133
|
+
if (d.line, d.file) != key
|
|
1134
|
+
or not d.message.startswith(("Dimensions not declared:", "Too many parameters:"))
|
|
1135
|
+
]
|
|
1136
|
+
diags.items.append(
|
|
1137
|
+
Diagnostic("error", stmt.line, f"Dimensions not declared: {raw}.", stmt.file, trigger=True)
|
|
1138
|
+
)
|
|
1139
|
+
if len(stmt.args) > 1 and stmt.args[1] is not None:
|
|
1140
|
+
raw = expressions._arg_text(stmt, 1)
|
|
1141
|
+
token = instr_rules._next_simple_token(raw) or raw
|
|
1142
|
+
diags.error(stmt.line, f"Too many parameters: {token}.", stmt.file)
|
|
1143
|
+
continue
|
|
954
1144
|
undefined = [n for n in _expr_names(arg) if n.upper() not in known]
|
|
1145
|
+
variables = {**types, **scoped.get(id(stmt), {}), **params}
|
|
1146
|
+
call = isinstance(arg, S.Index) and arg.name.upper() not in variables and arg.name.upper() in function_names
|
|
955
1147
|
expression = False
|
|
956
1148
|
dims = False
|
|
957
1149
|
if undefined:
|
|
958
|
-
if isinstance(arg, (S.Binary, S.Unary)):
|
|
1150
|
+
if isinstance(arg, (S.Binary, S.Unary)) or call:
|
|
959
1151
|
# an arithmetic expression is no destination: after its names, `Dimensions not
|
|
960
1152
|
# declared` or the destination-type error (oracle, CR300, 2026-10-03)
|
|
961
1153
|
expression = True
|
|
@@ -963,11 +1155,21 @@ def check_float_destinations(program: S.Program, profile, diags: Diagnostics) ->
|
|
|
963
1155
|
elif not isinstance(arg, (S.Name, S.Index)):
|
|
964
1156
|
continue
|
|
965
1157
|
keep = [profile.unresolved_symbol_message.format(name=n) for n in undefined]
|
|
966
|
-
elif
|
|
1158
|
+
elif (
|
|
1159
|
+
call
|
|
1160
|
+
or _is_bad_destination(arg, variables)
|
|
1161
|
+
or isinstance(arg, S.Name)
|
|
1162
|
+
and arg.name.upper() not in variables
|
|
1163
|
+
and arg.name.upper() in constant_names
|
|
1164
|
+
):
|
|
967
1165
|
keep = []
|
|
968
1166
|
else:
|
|
969
1167
|
continue
|
|
970
1168
|
own = [d for d in diags.items if (d.line, d.file) == key and d.severity == "error"]
|
|
1169
|
+
if call and any(
|
|
1170
|
+
d.message.startswith(("Incomplete expression:", "Invalid, or out of place expression:")) for d in own
|
|
1171
|
+
):
|
|
1172
|
+
continue
|
|
971
1173
|
if not keep and any(d.message.startswith("Dimensions not declared") for d in own):
|
|
972
1174
|
continue # (`Battery(L(2))` for a Long scalar: over-indexing is reported first)
|
|
973
1175
|
if keep and [d.message for d in own[: len(keep)]] != keep:
|
|
@@ -1236,12 +1438,18 @@ def _report_unregistered(
|
|
|
1236
1438
|
if isinstance(st, S.SubDef) and id(st) in tainted_subs and st.keyword == "FUNCTION"
|
|
1237
1439
|
}
|
|
1238
1440
|
affected: set[tuple[int, str]] = set()
|
|
1441
|
+
removed_params: set[str] = set()
|
|
1239
1442
|
for st in program.statements[taint_at + 1 :]:
|
|
1240
|
-
if
|
|
1443
|
+
if isinstance(st, S.SubDef):
|
|
1444
|
+
removed_params = {p.name.upper() for p in st.params} if id(st) in tainted_subs else set()
|
|
1445
|
+
continue
|
|
1446
|
+
if isinstance(st, S.Instruction) and structure._ends_sub(st, st.keyword.upper()):
|
|
1447
|
+
removed_params.clear()
|
|
1448
|
+
if id(st) in dropped or id(st) in rewritten:
|
|
1241
1449
|
continue
|
|
1242
1450
|
names: set[str] = set()
|
|
1243
1451
|
unused._walk_stmt(st, names)
|
|
1244
|
-
if names & unregistered or (not isinstance(st, S.Call) and names & functions):
|
|
1452
|
+
if names & unregistered or names & removed_params or (not isinstance(st, S.Call) and names & functions):
|
|
1245
1453
|
affected.add((st.line, st.file))
|
|
1246
1454
|
if not affected:
|
|
1247
1455
|
return
|
|
@@ -2426,6 +2634,7 @@ _ARITY_PREFIXES = {
|
|
|
2426
2634
|
"many": "No Matching, or out of place ) in expression: ",
|
|
2427
2635
|
"few": "Expression expects , : ",
|
|
2428
2636
|
"empty": "Invalid, or out of place expression: ",
|
|
2637
|
+
"network_constint": "Parameter must be a constant integer: ",
|
|
2429
2638
|
}
|
|
2430
2639
|
|
|
2431
2640
|
|
|
@@ -2459,7 +2668,7 @@ def _arity_walk(expr: S.Expr | None, known: set[str], profile, state: _ArityStat
|
|
|
2459
2668
|
)
|
|
2460
2669
|
args, stop = args[:at], True
|
|
2461
2670
|
state.setdefault("kind", "many")
|
|
2462
|
-
elif problem in ("few", "empty"):
|
|
2671
|
+
elif problem in ("few", "empty", "network_constint"):
|
|
2463
2672
|
state.setdefault("kind", problem)
|
|
2464
2673
|
elif problem:
|
|
2465
2674
|
state["bad"] = True
|
|
@@ -2512,7 +2721,13 @@ def order_names_around_arity(program: S.Program, profile, diags: Diagnostics) ->
|
|
|
2512
2721
|
if state.get("bad") or kind is None:
|
|
2513
2722
|
continue
|
|
2514
2723
|
arity = [(i, d) for i, d in own if d.message.startswith(_ARITY_PREFIXES[kind])]
|
|
2515
|
-
if
|
|
2724
|
+
if (
|
|
2725
|
+
not arity
|
|
2726
|
+
and kind != "network_constint"
|
|
2727
|
+
and isinstance(stmt, S.Assignment)
|
|
2728
|
+
and own
|
|
2729
|
+
and expressions._undefined_names(stmt.target, known)
|
|
2730
|
+
):
|
|
2516
2731
|
# an unresolved target hides the pass-1 arity check, but the parse still reaches it
|
|
2517
2732
|
arity = [
|
|
2518
2733
|
(
|
|
@@ -2758,9 +2973,13 @@ def order_invalid_assignments(program: S.Program, profile, diags: Diagnostics) -
|
|
|
2758
2973
|
expression:` line (oracle, 2026-10-10: `Sec = Nope`, `6 = estimated`), which pass 1 emitted first."""
|
|
2759
2974
|
if profile.name not in ("cr300", "cr350"):
|
|
2760
2975
|
return
|
|
2761
|
-
|
|
2976
|
+
conditions = {id(st) for st in program.statements if isinstance(st, S.If)}
|
|
2977
|
+
markers = [
|
|
2978
|
+
d for d in diags.items if d.stmt not in conditions and d.message.startswith("Invalid assignment in expression:")
|
|
2979
|
+
]
|
|
2980
|
+
for key in {(d.line, d.file) for d in markers}:
|
|
2762
2981
|
own = [d for d in diags.items if (d.line, d.file) == key]
|
|
2763
|
-
marks = [d for d in
|
|
2982
|
+
marks = [d for d in markers if (d.line, d.file) == key]
|
|
2764
2983
|
group = [d for d in own if d not in marks] + marks
|
|
2765
2984
|
first = next(i for i, d in enumerate(diags.items) if (d.line, d.file) == key)
|
|
2766
2985
|
rest = [d for d in diags.items if (d.line, d.file) != key]
|