Files
cdsl-cad/cadfs_to_cdsl/tests/test_parser.py
T
likang 994d06aaea feat(selector): 增加离线候选遍历与严格回放验证 Demo
- 新增 selector_candidate_demo,移除 provenance intent 后枚举候选 selector
- 对候选分支执行有界重建与严格 STEP 比较
- 仅在候选遍历完整且唯一 strict 通过时生成 selector 映射记录
- 增加 selector 候选搜索、预算限制和记录生成的测试
- 保持生产 selector resolver 不受 Demo 逻辑影响
- 更新 CADFS 能力台账,记录 IMPRINT 派生 profile 的 lineage selector 缺口
2026-09-10 15:12:57 +08:00

91 lines
4.3 KiB
Python

from __future__ import annotations
import unittest
from cadfs_to_cdsl.featurescript_lexer import lex
from cadfs_to_cdsl.featurescript_parser import parse_featurescript
from cadfs_to_cdsl.ir import Call
from cadfs_to_cdsl.lowering import _queries, _source_refs
from cadfs_to_cdsl.query_parser import parse_query
from cadfs_to_cdsl.units import length_mm
SOURCE = r'''
FeatureScript 1511;
export const f = defineFeature(function(context, id, definition) {
{ var Q0; Q0=qCreatedBy(makeId("Top.planeOp"), FACE);
var sketch = newSketch(context, id + "F0", {"sketchPlane":qUnion([Q0])});
skCircle(sketch, "E0", {"center":v(0, 0) * mm, "radius":9.53 * mm}); skSolve(sketch); }
{ var Q0; Q0=qSketchRegion(id + "F0", true);
extrude(context, id + "F1", {"entities":qUnion([Q0]), "depth":120 * mm}); }
});
'''
TRANSFORM_SOURCE = SOURCE.replace(
'\n});\n',
'''
transform(context, id + "F2", {"entities":qCreatedBy(id + "F1", BODY), "transformType":TransformType.TRANSLATION_3D, "dx":10 * mm, "dy":0 * mm, "dz":0 * mm, "makeCopy":false});
});
''',
)
class ParserTests(unittest.TestCase):
def test_lexer_ignores_comments_and_preserves_lines(self):
tokens = lex('// a\nfoo(/*b*/"x")')
self.assertEqual([token.value for token in tokens[:-1]], ["foo", "(", '"x"', ")"])
self.assertEqual(tokens[0].line, 2)
def test_nested_feature_script(self):
model = parse_featurescript(SOURCE, "00000173")
self.assertEqual([step.feature_id for step in model.steps], ["F0", "F1"])
self.assertEqual(model.sketches[0].entities[0].operation, "skCircle")
self.assertIsInstance(model.features[0].params["entities"], Call)
def test_transform_is_preserved_as_a_feature(self):
model = parse_featurescript(TRANSFORM_SOURCE, "transform")
self.assertEqual([step.feature_id for step in model.steps], ["F0", "F1", "F2"])
self.assertEqual(model.features[-1].operation, "transform")
self.assertEqual(model.features[-1].params["transformType"], "TransformType.TRANSLATION_3D")
def test_query_parser(self):
query = Call("makeQuery", [Call("__binary__", ["id", "+", "F1.opExtrude"]), "CAP_EDGE", "EDGE", {"isStart": False, "x": Call("sQuery", [Call("__binary__", ["id", "+", "F0.wireOp"]), "EDGE", "E0"])}])
value = parse_query(query)
self.assertEqual((value.owner_feature, value.source_sketch, value.source_entity), ("F1", "F0", "E0"))
self.assertFalse(value.is_start)
self.assertEqual(value.ast["call"], "makeQuery")
self.assertEqual(value.ast["args"][0]["call"], "__binary__")
def test_source_version_and_standard_library_are_retained(self):
source = '''FeatureScript 1511;
import(path : "onshape/std/geometry.fs", version : "1511.0");
import(version : "1511.0", path : "onshape/std/common.fs");
export const f = defineFeature(function(context, id, definition) {});'''
model = parse_featurescript(source, "versioned")
self.assertEqual(model.featurescript_version, "1511")
self.assertEqual(model.standard_library, "onshape/std/geometry.fs")
self.assertEqual(model.standard_library_version, "1511.0")
self.assertEqual(model.standard_library_imports, [
{"path": "onshape/std/geometry.fs", "version": "1511.0"},
{"path": "onshape/std/common.fs", "version": "1511.0"},
])
def test_block_scoped_query_aliases_do_not_use_the_last_assignment(self):
source = r'''
{ var Q0;
{ var subQ0=sQuery(id + "F2.wireOp", EDGE, "E2"); Q0=makeQuery(id + "F4.opExtrude", "SWEPT_FACE", FACE, {"derivedFrom":subQ0}); }
var Q1;
{ var subQ0=sQuery(id + "F2.wireOp", EDGE, "E4"); Q1=makeQuery(id + "F4.opExtrude", "SWEPT_FACE", FACE, {"derivedFrom":subQ0}); }
chamfer(context, id + "F5", {"entities":qUnion([Q0, Q1]), "width":5 * mm}); }
'''
model = parse_featurescript(source, "scoped-query")
queries = _queries(model.features[0].params["entities"])
self.assertEqual([_source_refs(query)[0] for query in queries], [("F2", "E2"), ("F2", "E4")])
def test_safe_units(self):
self.assertEqual(length_mm("2 * inch"), 50.8)
self.assertEqual(length_mm("25.4 / 2 * mm"), 12.7)
with self.assertRaises(ValueError): length_mm("external.value * mm")
if __name__ == "__main__": unittest.main()