首页 / 资讯中心 / 文章详情

Penrose Substance 索引语句(Indexed Statements)全解析:模板标识符、范围展开与 where 条件过滤

Penrose Substance 索引语句(Indexed Statements)全解析:模板标识符、范围展开与 where 条件过滤 ★ FEATURED ARTICLE
开发工具数据可视化【免费下载链接】penroseCreate beautiful diagrams just by typing notation in plain text.项目地址https://gitcode.com/gh_mirrors/pe/penrose点击查看免费下载索引语句Indexed Statements是 Penrose 的 Substance 声明式语言中用于批量生成对象与关系的关键语法通过模板标识符加for i in [x, y]索引子句一行代码即可展开为成百上千条普通语句是绘制海量点、边、迭代序列等高密度图元时的核心手段。本文以官方参考文档 indexed-statements.md 为骨架结合 Substance 语法文件 与 Substance 编译器实现 的源码证据完整讲解索引语句的展开规则、多变量笛卡尔积、where条件过滤、已知解析缺陷及与重复语义的交互读完即可在自己的 Domain Style Substance 三元组中熟练编写高表达力的索引式图程序。什么是索引语句Substance 语言允许用户定义可展开为多条语句的索引表达式。一条索引语句就是一条普通的 Substance 单条语句对象声明、谓词应用、函数/构造器绑定、标签语句等只不过其中的标识符带有模板后缀并追加一个索引子句indexing clause。最常见的用法是声明一组索引变量Vector v_i for i in [0, 2] -- 等价于 Vector v_0, v_1, v_2这里的v_i就是一个模板标识符templated identifier。它仍是合法的 Substance 标识符由字母、数字与下划线组成区别在于最后一个下划线及其后的子串表示一个索引变量。上例中_i表示索引变量i其取值来自范围[0, 2]即0, 1, 2因此该行被展开为三条语句Vector v_0、Vector v_1、Vector v_2。范围i in [x, y]的语义:::infoi in [x, y]其中x、y为给定数值要求i是整数、i ≥ x且i ≤ y。因此类似Vector v_i for i in [3, 0]的写法不产生任何展开结果——不存在同时满足至少为 3 且至多为 0的整数i。 :::这一点与编译器的实现完全一致在 Substance.ts 的evalISet函数 中引擎会先检查范围上下界high、low是否为整数Number.isInteger否则抛出BadSetIndexRangeError随后当high low时直接返回空列表return ok([])表示该索引集为空。展开时使用Range(low, high 1)生成闭区间[low, high]内的全部整数值——注意两端都是包含的。语法层面的支撑在 Substance.ne 中索引语句由以下规则解析stmt_iset - stmt __ iset任意普通语句stmt后紧跟一个iset索引子句iset - for sepBy1[range_assign, ,] (where boolean_expr)?for后至少一个范围赋值sepBy1保证非空多个范围用逗号分隔where子句可选range_assign - identifier in int_range形如i in [0, 2]int_range - [ number , number ]闭区间语法。同时for、in、where、mod均被注册为关键字见 Substance.ne 词法定义因此不能把它们当作普通标识符使用。解析结果被标记为StmtSet节点编译期再交给checkStmtISetHelper做类型检查与展开见下文展开的实现一节。模板标识符与普通标识符的遮蔽Shadowing当模板标识符与程序中的普通标识符同名时在索引语句内部普通标识符会被遮蔽shadow只要某个标识符出现在索引语句中且它的下划线后缀碰巧与索引变量同名那么它将被视为模板标识符而不再指向此前声明的对象。参考文档给出了完整的对照示例Vector v_i, v_j, vec1 Vector v_i for i in [0, 10] -- ok: 展开为 v_0, v_1, v_2, ..., v_10 Vector v_j for i in [0, 1] -- error: 范围 i in [0, 1] 没有定义 j 的取值 -- 注意此处的 v_j 并不指代上面声明的 v_j -- 因为它出现在索引语句中被视为模板标识符 Orthogonal(v_i, vec1) for i in [0, 3] -- ok: 展开为 Orthogonal(v_0, vec1); Orthogonal(v_1, vec1); -- Orthogonal(v_2, vec1); Orthogonal(v_3, vec1) -- 注意vec1 不含下划线因此被当作普通 Substance 变量 Orthogonal(v_i, v_j) for i in [0, 5] -- error: 范围 i in [0, 5] 没有定义 j 的取值这里的关键判别规则是标识符中最后一个下划线之后的子串必须与某个已声明的索引变量同名才会被当作模板标识符进行替换否则如vec1保持普通变量语义。而一旦判定为模板标识符即使它与之前声明过的对象重名也会被遮蔽不再引用旧对象。多索引变量与笛卡尔积展开当一条索引语句包含多个模板变量和多个范围时Substance 会取各范围取值的笛卡尔积Cartesian product——即所有索引组合都会被枚举。官方文档示例Orthogonal(v_i, v_j) for i in [0, 1], j in [1, 2] -- 展开为 Orthogonal(v_0, v_1); Orthogonal(v_0, v_2); -- Orthogonal(v_1, v_1); Orthogonal(v_1, v_2)即i ∈ {0, 1}、j ∈ {1, 2}的 2×24 种组合全部生成。这正是evalISet中 笛卡尔积实现 的行为每个范围被展开为[[name, value]]形式的候选值列表例如i in [1, 3]生成[[i, 1]], [[i, 2]], [[i, 3]]然后通过cartesianProduct逐步合并所有变量得到一组组索引变量 → 整数值的替换substitution。最后如果存在where条件再对每个替换求值布尔表达式仅保留条件为真的组合。需要注意同一索引子句中不允许重复声明同一个索引变量——evalISet会先做去重检查发现重复即抛出DuplicateIndexError。例如for i in [0, 2], i in [1, 3]是非法的。where 条件过滤Conditional Filtering有时我们并不想要全部笛卡尔积组合。Substance 允许在where子句中写一个布尔表达式来过滤组合所有使布尔表达式为假的组合都会被丢弃。官方文档给出了五类典型场景Vector v_i for i in [0, 10] where i % 2 0 -- 偶数下标0, 2, 4, 6, 8, 10 Orthogonal(v_i, v_j) for i in [0, 2], j in [0, 2] where i j -- 三角范围[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2] Orthogonal(v_i, v_j) for i in [0, 3], j in [0, 3] where i 1 j -- 相邻对[0, 1], [1, 2], [2, 3] Edge(v_i, v_j) for i in [0, 4], j in [0, 4] where j (i 1) mod 5 -- 循环对[0, 1], [1, 2], [2, 3], [3, 4], [4, 0] Orthogonal(v_i, v_j) for i in [0, 3], j in [0, 3] where i % 2 0 j i 1 -- 两两不相交的对[0, 1], [2, 3]布尔表达式的构成where子句中的布尔表达式可以包含布尔常量true与false一元逻辑运算符!逻辑非后接一个布尔表达式二元逻辑运算符逻辑与、||逻辑或连接两个布尔表达式数值比较等于、!不等于、小于、大于、小于等于、大于等于比较两个数值表达式。数值表达式的构成数值表达式可以包含浮点常量注意范围[x, y]的上下界虽按数值解析但引擎要求其为整数值范围中定义的索引变量如for i in [0, 2]中的i一元数值运算符-取负后接数值表达式二元数值运算符加、-减、*乘、/除、%或mod取模二者在语法树中统一映射为%操作符、^幂连接两个数值表达式。从 Substance.ne 表达式文法 可以看到完整的层级boolean_expr||→boolean_term、!→boolean_factor括号、布尔常量、比较表达式数值侧为expr/-→term*///%/mod/一元-→factor^、括号、数字、标识符。运算符优先级与括号默认的运算优先级与其他主流编程语言一致^的绑定最强其次依次是* / % mod、 -再往下是比较运算符最后是!、、||。括号( )可以用来覆盖默认优先级boolean_factor与factor均允许括号包裹子表达式如示例中的(i 1) mod 5。在evalCondSubstance.ts中与||都实现了短路求值short-circuiting的左侧为假时直接返回假而不求值右侧||的左侧为真时直接返回真。这不仅是一种性能优化也保证了某些组合下不求值右侧表达式例如避免0 % 0之类的除零错误的行为可预期。已知缺陷1/-1的 tokenizer 解析问题:::warning由于内部 tokenizer 的一个缺陷形如1的表达式无论出现在程序中的什么位置总是被解析为单个 token1表示整数正一而不是与1两个 token同理-1总是被解析为单个 token-1而不是-与1。因此21总是被解释为2与1而不是预期的2、、12-1同理被解释为2与-1。换句话说它们被当作两个数字紧挨着而不是一个数字、一个运算符、另一个数字。该缺陷会导致如下报错Error: Syntax error at line 1 col 39: Node n0_i for i in [0,15] where i 21 ^ Unexpected int_literal token: 1.因为运算符被吸收进了1这个 token解析器再也找不到运算符了。:::从词法层面看根因在 Substance.ne 的 float_literal 规则float_literal: /([-]?([0-9]*[.])?[0-9])/允许数字字面量以可选的正负号开头因此1、-1会被词法分析器基于 moo 的正则优先级匹配整体吞成一个 token抢先于二元运算符/-被匹配。该问题已被记录在项目的问题追踪器中issue #1516。规避方法除非确实需要写-1、3这类带符号的数否则务必在与-运算符两侧留出空格写成2 1、n - 1等形式即可绕开该缺陷。这也是官方文档中所有示例如i 1 j都带空格的原因。索引语句与重复语义Duplications的交互索引语句展开后生成的是若干条普通 Substance 语句因此原有的重复语义同样适用对象重复声明是错误同一名字的对象只能声明一次谓词关系重复应用是允许的同一关系的多次声明不构成错误。官方文档示例Vector v_0 Vector v_i for i in [0, 2] -- error: v_0 已被声明不能重复声明 Vector v_i, v_j for i in [0, 2], j in [0, 2] -- error: v_0 已被声明不能重复声明 Orthogonal(v_0, v_1) Orthogonal(v_i, v_j) for i in [0, 2], j in [0, 2] where i ! j -- ok: 重复的谓词应用是允许的第一条Vector v_i for i in [0, 2]展开后包含Vector v_0与上文手动声明的Vector v_0冲突因此报错而Orthogonal谓词即使展开结果与手动声明的Orthogonal(v_0, v_1)重合也不会报错。这一语义意味着在编写索引声明时要留意展开结果是否会与你已声明的对象重名而谓词类索引语句则可以放心地与其他索引语句产生重叠的关系声明。访问索引集合中的单个元素索引语句展开时会把索引变量替换为整数值的字符串从而生成形如v_0、v_2的标识符。这些生成后的标识符与普通标识符完全等价只要它们真实存在即确实被某条索引语句展开出来就可以在后续语句中直接引用Vector v_i for i in [0, 2] LinearlyDependent(v_0, v_2) -- ok注意此处v_0、v_2能够被引用前提是上文的Vector v_i for i in [0, 2]恰好覆盖了这两个下标如果引用v_3则会因对象未声明而报错。这一特性使得我们可以先批量声明、后选择性使用在构造依赖特定下标之间的关系时非常实用例如只让首尾向量满足共线关系。展开的实现原理与真实用例编译期的展开流程在 Substance.ts 中索引语句的展开发生在编译的类型检查阶段checkStmt遇到StmtSet节点时调用 checkStmtISetHelper根据内部语句的类型分发Decl、DeclList、Bind、DeclBind、ApplyPredicate、AutoLabel、LabelDecl、NoLabel均支持索引化其余类型返回UnsupportedIndexingError。这意味着对象声明、绑定、谓词应用乃至 Label/AutoLabel/NoLabel 标签语句都可以带索引子句evalISet完成范围合法性检查、笛卡尔积展开与where过滤产出一组Map索引变量, 数值替换对每个替换调用对应的substISet*函数如substISetPredicate、substISetDeclBind、substISetLabelDecl等见 Substance.ts 第 709 行起的替换函数族把语句中的模板标识符逐一替换为具体下标生成独立的编译后语句。解析层的测试用例见 SubstanceParser.test.ts覆盖了声明索引Set A for i in [0, 10]、多范围索引for i in [0, 10], j in [1, 5]、绑定式索引Set A : MakeSet(hello_j) for j in [0, 20]、带where的谓词索引Edge(a_i, a_j) for i in [0, 20], j in [20, 30] where ...以及标签索引Label x_i abcde for i in [0, 10]。仓库中的真实用例packages/examples中有大量索引语句的实际运用可以作为最佳实践参考curve-examples/curve.substancePoint p_i for i in [1, 16]声明 16 个采样点再用In(c, p_i) for i in [1, 16]把它们批量放入曲线cgeometric-queries/disjoint/disjoint.substanceLine L_i for i in [1, 100]一行声明 100 条线段配合 Style 端的约束批量布局fractals/chaos-game/sierpinski-triangle.substancePoint p_i : NextPoint(p_j) for i in [1, 500], j in [0, 500] where i j 1通过绑定式索引语句:迭代生成 500 个点是索引 多变量 where 条件三者组合的典型范例fractals/ifs/ifs.substancePoint p_i : NextPoint(p_j, f_k) for i in [1, 500], j in [0, 500], k in [1, 2] where i 2 * j k演示了三个索引变量与更复杂条件表达式的用法。结合 Domain 概述 与 Style 概述 可以理解完整的定位索引语句只负责声明什么对象、什么关系具体如何绘制仍完全由 Style 程序决定。掌握了本节的展开规则、where过滤、遮蔽语义与/-空格规避技巧你就能在 Substance 概述 与 单条语句参考 的基础上高效写出规模化的 Penrose 图程序。赞分享开发工具数据可视化【免费下载链接】penroseCreate beautiful diagrams just by typing notation in plain text.项目地址https://gitcode.com/gh_mirrors/pe/penrose点击查看免费下载相关推荐AutoX常见问题解答新手必知的15个关键问题AutoX常见问题解答新手必知的15个关键问题 AutoX是一款强大的Android平台JavaScript自动化工具无需root权限即可实现UI自动化操作移动开发RPAOPA 数据过滤策略的 Partial Evaluation 原理详解从 Rego 条件集到 SQL WHERE 子句OPA 数据过滤策略的 Partial Evaluation 原理详解从 Rego 条件集到 SQL WHERE 子句 导读 本文以 Open Policy后端认证鉴权云原生SwiftFormat where子句格式化约束条件样式全解析SwiftFormat where子句格式化约束条件样式全解析 引言where子句的格式困境 在Swift开发中泛型约束和条件扩展的where子句常常成为开发工具代码质量CLI上一篇NBTExplorer终极指南3分钟掌握我的世界数据编辑拯救你的游戏存档下一篇NBTExplorer终极指南如何轻松编辑我的世界游戏数据创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站