feat(cadfs): expand selector provenance coverage

This commit is contained in:
2026-09-14 11:00:14 +08:00
parent 994d06aaea
commit de21ee8a5b
45 changed files with 19837 additions and 842 deletions
+21 -2
View File
@@ -49,6 +49,12 @@ class Parser:
def primary(self) -> Any:
token = self.pop()
if token.value in {"+", "-"}:
# FeatureScript permits a signed parenthesized scalar such as
# ``-(138.6) / 2 * mm``. Keep it in the existing arithmetic AST
# so every downstream constant/units validator sees the same
# expression shape as a binary subtraction.
return Call("__binary__", [0.0, token.value, self.primary()], token.line)
if token.value == "(":
value = self.expression(); self.accept(")"); return value
if token.kind == "string": return _string(token.value)
@@ -151,6 +157,20 @@ def _arg_map(call: Call) -> dict[str, Any]:
return next((arg for arg in reversed(call.args) if isinstance(arg, dict)), {})
def _feature_id(call: Call) -> str | None:
"""Return the declared ID for one direct FeatureScript feature call.
FeatureScript operations share the ``operation(context, id + "F...",
definition)`` shape. Retaining this generic boundary makes an unknown
source operation visible to the capability registry and lowering instead
of silently omitting it because its name is absent from a parser list.
"""
if len(call.args) < 2 or call.args[0] != "context":
return None
feature_id = symbolic_string(call.args[1])
return feature_id if feature_id.startswith("F") else None
def parse_featurescript(source: str, sample_id: str = "unknown") -> ModelIR:
parser = Parser(source); calls = parser.statements()
version = re.search(r"\bFeatureScript\s+(\d+(?:\.\d+)*)\s*;", source)
@@ -175,8 +195,7 @@ def parse_featurescript(source: str, sample_id: str = "unknown") -> ModelIR:
if model.sketches:
args = _arg_map(call); eid = str(call.args[1]) if len(call.args) > 1 else f"E{len(model.sketches[-1].entities)}"
model.sketches[-1].entities.append(FeatureIR(eid, call.name, args, line_start=call.line, raw_source=call.name))
elif call.name in {"extrude", "revolve", "fillet", "chamfer", "hole", "linearPattern", "mirror", "cPlane", "referenceAxis", "shell", "loft", "sweep", "circularPattern", "booleanBodies", "deleteBodies", "transform", "draft", "thicken", "split", "moveFace", "replaceFace", "deleteFace", "derive"}:
fid = symbolic_string(call.args[1]) if len(call.args) > 1 else f"feature_{len(model.features)}"
elif (fid := _feature_id(call)) is not None:
feature_ir = FeatureIR(fid, call.name, _arg_map(call), line_start=call.line, raw_source=call.name)
model.features.append(feature_ir); model.steps.append(feature_ir)
return model