# Yac **Repository Path**: hao-yiping/YetAnotherC ## Basic Information - **Project Name**: Yac - **Description**: No description available - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-10-01 - **Last Updated**: 2026-02-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Yac Yet Antoher C(是的,这又是一个爱好者搞出来的C语言)。仅包含语言的规范,词法规则与文法规则。编译器在另一个项目。 ## 介绍 其实也许称它为 Yet Another CPP更合适?算了名字不重要。作者正在学习代数曲线和递归论,这两个东西完了之后才会去琢磨类型系统的理论和lambda演算的理论,到那时才能完成完整的设计。但是并不妨碍在工程上琢磨,设计目标,设计理念和一些具体的设计。可以先把这些设计记录下来。构思这个东西断续小几年,中途常会有新的领悟。 ## 设计理念 随着认识的进步旧的设计理念变的不和时宜了。这门语言必然是一门系统级编程语言,设计目标必然是一个“更好的C++”的某种增减。但是似乎有很多问题要回答。 - 这门语言要用哪些特征作为竞争力争取哪些人作为最初的用户? - 仅就设计本身,如何追求做到“更好的C++”? ## 设计目标 系统级别的编程语言第一职能就是完成对硬件核心特征的抽象。体系结构和集成电路并不是千秋永固的,它是在不断进化的。最主流的系统编程语言C和C++可以完成对新出现的体系结构的核心特征的抽象。然而这种抽象不是这两个语言在设计之初原生的,而是在演进的过程中通过某种补丁的方式完成的。比如最著名的CUDA就是在C++的基础上添加了一些语法扩展,完成了对GPU的抽象。SIMD指令则完全依赖于外部库。体系结构的演进不会停止,越来越多的进展会在C和C++上打出更多的补丁。并且很多时候,软硬件应当是协同演进的。这种协同中,系统级编程语言占据了软件侧的核心角色,软件的落后会反过来滞锢硬件的发展。在这种软硬件的轻度失配中,蕴藏着Yac最大的原始市场机会。yac会努力的对某些硬件上的新设计和旧设计提供比传统系统编程语言更好抽象。这些事务包括对那些热衷于扩展自身的那些指令集(比如risc-V和作者自己的十分之一成品指令集Vacuum)对自身的扩展,比如各种不同CPU上的SIMD指令,冯诺依曼结构中的NUMA之类的东西。 此外C/C++作为一个老的编程语言,经过漫长的研究里面的有些东西被认为是兼容性包袱下的不良设计。不考虑和硬件的协同,yac作为一个编程语言本身的底子要努力做到最优。有一些设计作者已经想明白大概是怎么样做了,有信心做到比市面上的主流编程语言”偏序更优“。比如它的异常机制。有一些作者则只确定了大致的方向。比如它的安全机制和泛型机制。 在操作系统,编程语言,指令集等涉及到生态的领域。市场需要的是"fuck the old world out"的划时代创新,所有"me too, me better"都是自我感觉良好的垃圾。如果Yac成为一些新硬件的默认系统级编程语言,或者仅凭SIMD中心的编程能力吸引到一些用户。那么一些更优的设计有可能能作为一个市场放大器,但是远不足以仅仅凭此立足吸引到足够的用户。可惜作者能想到所有创新,虽然其中相当部分可以讲和旧的实现很不同,甚至更自信的讲站在巨人的肩膀上比C++的经典实现显著的优越,但是这些创新都很难带来剧烈的生产力和产业价值进步,不足以抵消生态冷启动的困难,所以这些创造都很尴尬。 ## 设计理念的碎片 ### 1 整体风格 - 1 在易读易写和易学的权衡中首先抛弃易写。其次抛弃易学。 - 2 追求简洁性:删去能删掉的每一个语法糖。自我意识不能过于旺盛,yac即使发展壮大,也不能占据开发者过高的心力比重。 - 3 追求边界与规则清晰简洁并且尽可能的符合直觉:记住可以做的,剩下的就是非法的。如果一种实现的行为和接口规定的行为一致,那么这种实现就会出现在标准里。 ### 2 模板与编译时运算机制 它的泛型机制依赖于类型系统的某些独特设计。它的类型系统内生了字面值和前后端符号量的概念,这给予了编译时运算以及静态反射的坚实抽象基础。此外,它的模板系统可以认为是一种重度依赖静态反射的编译时脚本运算,但是需要有一定约束来保证这种运算的图灵停机性,不能将它的能力扩充到图灵完备。 ### 3 安全机制 这是认识中改变最大的一部分。 这种改变基于我对rust的某种新的认知。系统级编程语言的设计中,安全性显然是个极大的问题。rust的做法我不模仿,但是有极大的启发作用。YAC的安全机制是双层次的,向一个未经深度学习的程序员暴露的安全机制是非常易用的,可以看作是对C/C++的继承和改良,这种机制不是严格的,会有一定的安全漏洞,尤其是对于复杂的多线程程序尤其如此。编译器在默认选项中支持这种机制,允许程序员快乐的写出不安全的程序。 这些安全机制可以进一步自然延申,进化成一种极度强大但是又使用困难的东西:语言伴生的形式证明系统。有一套公理来描述程序的每一个语句的语义行为,随后程序员自定义相容的公理和定理来证明自己的程序拥有某些特性。它仅仅把编译器用作定理证明验证器来验证程序员对有关自己程序的某些命题的形式证明过程是否正确,而不用作定理证明生成器来自动的证明程序员的程序是否有某种性质。(上面一句话提供的观察角度实际非常高位,理论上划分了两种完全不同的安全设计,在这种观点之下,rust和Cpp是在安全机制上的近亲,尽管世俗认为它们的安全机制是非常不同的。)它某种意义上也是一种编译时运算,通过编译选项选择启动和关闭,它开启后可以保证等同于rust的严格的内存安全。此类东西威力极大,可以覆盖包括内存安全在内的几乎全部安全机制,并且只要程序员能写出相应的安全证明,它不会像rust那样限制写法。比如程序员有能力通过形式系统证明内存安全,那么他可以先自由的写一个循环引用。然后给出形式证明,再在编译器那里完成验证。 这个机制不错,但是双层机制之间的开发难度曲线的断层过大了,大部分使用者仅仅能把它当作一个更安全一点的c++来用。“更安全一点的c++”在工程上不是错误,语言伴生的形式证明系统用于提供向高安全需求的兜底来拔高语言的上限用的。悲观的讲,这个设计可以被看成是面对潜在的安全能力批评的敷衍式回应:“我的安全性可以达到理论极限,只是不太好用而已”。但是随着AI的高速发展,形式证明系统的能力也在不断提高,这种语言内生的形式证明系统和AI编程说不定会有很好的结合。甚至可以成为AI的强化学习的数据源。顺便还能和lean抢饭碗。 ### 4 语树理念 这是一个非常重要的理念,它不是作者的原创,它是作者对产业现状的观察之后的提炼。这种理念很少有人显式的提及,但是一定会在未来发扬光大,即使yac完全失败这个理念也足以流传。 世界上有无数种完全的不同的编程语言,它们的设计都非常的不同,有时候社区会为了它们谁更好争执的面红耳赤。假如一个语言设计师的上级向他提出了一个问题和一个要求:这么多编程语言谁才是天下第一语言?你能不能设计一门凌驾于所有语言之上的超级语言?那么这个设计师的第一反应是质疑上司的专业性:"这个问题本身就暴露了您对编程语言设计的误解。内存管理就是一个完美的例子:GC 语言如 Java 和 Go 让程序员无需手动管理内存,但带来了运行时开销和不可预测的停顿;C 语言让程序员完全控制内存,但容易导致内存泄漏和悬空指针;Rust 通过所有权机制在编译期保证内存安全,但学习曲线陡峭且限制了某些写法。这三种设计各有优劣,适用于不同场景,没有绝对的'天下第一'。编程语言的设计本质上是在多个维度上做权衡,不存在一个在所有维度上都最优的'超级语言'。"但是,如果这个上司蛮横又专断,依然强迫设计师给出一个确定的回答,那么设计师该如何回应? 如果是我,我会这样说:“如果您强迫我一定要给出一个答案,那么我会说,如果可以把python 和C/C++看成一个语言复合体,那么这个复合体可以勉强称得上天下第一语言。” 它们两个语言可以视作一种复合体吗,某种意义上可以。python有着丰富的使用C/C++库的能力,这使得它在很多场景下都可以使用C/C++的高性能库。Python 作为一种脚本具有易用性、动态类型、快速开发的显著优势,而原生性能使它最大的缺陷,调用C/C++库的能力在极大程度上弥补了缺点,赋予它巨大的竞争力。 这就是语言树的设计背景:编程语言不是线性的进化关系,也不是杂乱无章的自由分布,而是一棵树型关系,每个节点都有其存在的合理性和适用场景。它们各自承担不同的功能,单一的语言由于设计权衡,无法在某个方向做到绝对最优。是所有语言构成的完整的生态系统,满足了市场上大部分的开发需求。然而语言树中的大部分语言,在设计时没有把与其它的语言的关系与协同作为设计的核心,最多把它们做成一种补充式的存在。比如说很多语言可以调用C/C++写的库,但是这种调用不是这些语言的机制的核心,只是为了能蹭一些现有语言的生态而已。然而事实上这种协同的威力和重要性超乎想象,如果python不能使用C/C++的库,那么性能问题立刻就会使这门天下最流行的脚本语言在它的主流使用场景被淘汰出局。 语言树的设计理念是这样的,在设计一门新语言的时候,不是孤立的进行设计,而是把和市面上已经存在的语言关系和协同作为设计的核心考量之一。也有时候可以同时设计多种语言,作为一个超级复合体,设计时给出这个复合体中成员的协同机制,以及它们之间的关系。这种设计的优点是显然的: 1. **生态价值最大化**:新语言无需从零开始构建生态,可以直接利用现有语言的库和工具,大幅降低用户迁移成本。它的代码也可以供给其他语言使用,这为其他语言的发展提供了强大的支持。 2. **分工协作**:每种语言在自己擅长的领域追求极致,通过协同机制形成优势互补的技术栈,整体能力远超单一语言。 3. **灵活性与适应性**:面对不同场景,开发者可以选择最合适的语言工具,而不是被迫用一种语言解决所有问题。 4. **进化阻力最小化**:新语言可以在现有语言生态的基础上生长,而不是与整个生态为敌,更容易被市场接受。 但是这种协同的实现则是一个相对复杂的问题,语言间的树状关系提供了最粗粒度的观察。一个节点可以直接和它的子节点协同,也可以和它的父节点协同。一个父节点的所有子节点则可以通过与它的父节点的某种合作机制达成协同。这种设计是必要的,假如一个父节点下有N个子节点,那么为所有子节点两两设计协同机制的复杂度是O(N^2)。这是一个非常高的不现实的复杂度,更糟糕的是随着新的子节点的加入,这种合作机制需要动态的不断更新。而通过父节点间接的合作的复杂度则是友好的线性,不同的子节点的设计者可以在不互相了解和沟通的情况下,通过仅仅处理与父节点的协作机制,借由父节点一劳永逸的和父节点下的所有子节点和未来子节点达成自然的合作。此外,如果这颗树上的所有成员都有和自己的父节点的合作机制,那么任意两个节点都可以借由它们的公共祖先相互协作。 那么一个节点如如何和自己的父节点协同呢?这依然是一个非常复杂的工程问题,但是我们依然可以提供一种粗粒度的视角。这种协同往往依赖于类型系统和接口的设计。在传统的语言规范设计原则中,通过行为和接口规定一个规范往往被认为比通过具体实现达成规范更优越,比如一个vector,通过实现进行规范时我们说它的本质是一个指针加上一个长度整数,通过行为进行规范时,我们说它是一种可以被随机读写,通过成员函数获取长度的对象。后者是更被推荐的做法。但是在协同设计时,权衡的天平会部分倒转,门们可以把它如何用父节点实现用作它的核心数据结构的规范。这种规范在传统的理念看来是不灵活的。但是却为语言之间的合作提供了确定性和前向兼容性。当然某些高级抽象依然可以采用行为规范。 我们可以详细介绍YAC如何践行自己的语树设计理念。作为一个系统级语言,YAC选择绑定Vacuum双层次指令集的虚拟指令层作为自己的父节点,这是其语树设计的核心决策。YAC的核心数据结构(如`array`容器、`vector`类型、`ptr`引用等)向Vacuum IR编译的结果有严格、稳定且具体的规范——包括内存布局、类型转换规则、函数调用约定等都被明确定义。这种规范确保了YAC代码编译到IR后的行为是可预测的,此时对于任意一个系统级编程语言,只要它也规定了向Vacuum IR编译的稳定规范,就可以与YAC无缝协同工作,无需为两两语言单独设计适配层。 此外,YAC内置了一套完整的编译器前端套件和IR操作工具链,包括词法分析器、语法分析器、类型检查器、代码生成器等核心组件。这些工具不仅服务于YAC自身的编译过程,更重要的是为其他语言的开发者提供了现成的基础设施。特别值得一提的是YAC的"第二指针"设计——这是一种由GC(垃圾回收)管理的指针类型,它并不主要服务于YAC的普通用户,而是专门为使用YAC开发GC语言的开发者准备的。第二指针的内存管理完全由YAC运行时的GC系统负责,开发者无需手动管理其生命周期,这使得YAC成为一个理想的编译器和解释器开发语言。 例如,当开发者使用YAC开发一门新的脚本语言时,可以利用YAC的编译器前端套件快速构建语法分析和类型检查系统,利用IR操作工具链生成Vacuum指令,同时通过第二指针实现脚本语言的内存自动管理。这样,新语言无需从头构建整个工具链,只需专注于自己的语法设计和语义实现,大大降低了语言开发的门槛。这种设计使得YAC不仅是一门系统级编程语言,更是语言树上的一个"孵化器",能够培育出更多专门化的子语言,共同丰富整个语言生态系统。此外它有一套编译器前端套件,一套操作IR的工具链,并且提供第二指针,第二指针是gc管理的指针。并不主要服务于yac的用户,而是服务于使用yac开发gc语言的人。使得yac是一个不错的编译器和解释器开发语言。 ### 5 体系结构适配 Yac选择vacuum的虚拟层作为自己的IR后会自动的接受Vacuum的功能模型。比如面向gpu的编程中它放弃了了SPMD,矢量类型数据仅仅接受谓词掩码。控制流本身是纯粹标量的。用簇容量为1的vacuum线程簇来代替传统的cpu线程。yac的类型系统原生就有`scalar`:标量(单个数值,默认属性)和`vector`:矢量(SIMD并行计算专用)属性区别。标量适用于程序员习惯的普通变量、计数器等,而`vector`他是按照元素数目而不是数据字节数来计算宽度的矢量数据类型。这也是继承自vacuum的特征。 Vacuum是很特殊的CPU-GPU两可性双层次指令集。Yac选择vacuum的虚拟层作为自己的IR后,被迫拥有了很强的SIMD指令能力。宽度如同`sizeof(size_t)`一样随平台而变,如果平台不支持,它的长度可以是1(等价于标量)或者是0(不支持)。此外Yac的控制流虽然也是CPU风格标量中心的,但是矢量运算的谓词化也提升到了一等公民,语法原生的地位。它开发一个CPU上的SIMD程序不需要系统库,也不需要内联汇编,它天然的矢量运算天然跨平台。对于开发CPU上的一些高性能计算程序是巨大利好。现有的CPU在模型上天然就是一个簇容量为1,SIMD宽度较窄的IPU(Vacuum指令集系统的术语)。所以Yac运行在CPU上是没有什么问题。 但是现有的主流GPGPU其实从模型上与vacuum体系结构中一个簇容量32,SIMD宽度为32的IPU是有一些区别。比如线程块它一旦占用SM就不会撤回,而线程簇调度上接近传统的CPU线程,占用核心后会因为切片耗尽失去核心的。所以它其实不能,至少不保证能运行在主流的GPGPU上,它并没有完整的继承Vacuum的CPU-GPU两可性。很有意思来了,某些TPU和NPU,它们虽然面向SIMD的任务不具备性能通用性,比如它们可能不支持fp64,但是它们的SM处理器里面有一个标量小核心,换言之它们也是拥有矢量指令的标量控制流中心的硬件,它们的小核心上也能运行操作系统,某种意义上它们有点模型上的两可性。Yac能给这类硬件做专属的系统级语言。此外Vacuum作为一个双层次指令集,它允许硬件厂商自定义任意虚拟层指令扩展而不破坏生态。这种自定义虚拟指令扩展的在系统级编程语言的暴露也会成为Yac的原生语法支持的一部分。 vacuum的自定义虚拟指令扩展机制也需要在yac这里得到支持。但是vacuum显然不是唯一一个热衷于扩展自身的指令集。RISC-V扩展的更普遍,yac对这类热衷于扩展自身的家伙有一套额外的支持机制。 ### 1 删去的东西 - 1 花括号的省略。 C语言中的控制流语句比如`if`,`else`,`for`等等后面可以选择使用花括号与否。如果仅仅跟着一条语句,是可以省略这种花括号的。但是Yac不行,每一个控制流语句后面必须跟着花括号。这样可以省略记忆至少两条规则,首先不需要记忆什么时候可以省略花括号这条规则本身。其次可以不去思考最近邻匹配规则比如: ```C++ if(bool expression1) if(bool expression2) stmt2; else stmt3; ``` 在C/C++语言中是合法的,但是在Yac中不是,仅仅能写出它的等价形式: ```C++ if(bool expression1) { if(bool expression2) { stmt2; } else { stmt3; } } ``` - 2 传统的`for`循环的删去 C/C++语言中最经典的`for`循环被删去了。多么沉痛的决定,我是多么的爱它,但是还是杀了它。 ```C++ for(stmt1; stmt2; stmt3) { } ``` 但是`for`关键字没有死去,它深度绑定了迭代器循环。依然会是程序员最常写的循环。 ```C++ for i in range(length) { stmts; } [array] list; sint sum = `0`; for v in range(list) { sum += v; } ``` 上述例子中的range本质是一个迭代器类型的构造函数。迭代器当然有语法级别,标准库级别,和用户自定义三个级别。 所以它依然有且仅有三种循环语句 `while`, `do-while`, `for-in`。 在容纳的语法规则有限的情况下,相比于删去C++语言的使用者习惯的东西是为了更好的加入新东西,比如`break`和`continue`。它允许远程跳转。 ``` C++ for i in range(length1) { for j in range(length2) { for k in range(length3) { break : break : continue; } } } ``` - 4 删去了每行的长度限制。 换行指标和空格,统一视作分隔符。分隔符在可许出现的地方可以出现任意多次包括零。 - 5 删去了标识符的长度限制。 所有的关键字分为两种,少部分的控制流和关键类型标识符被设定为第一类关键字。它们会争夺标识符的定义空间,标识符不能是第一类关键字。第二类关键字由 `#`开头不与标识符争夺定义空间。 第一类关键字有 `do`,`while`,`for`,`in`,`if`,`else`,`break`,`continue`, `call`, `return`十种控制流相关。 `vector`,`scalar`,`rdo`,`wrd`,`own`,`comp`, `disp`, 类型修饰相关。 - 5 删去了相当部分的运算符。 删去了全体位运算符,sizeof,与强制类型转换符,自增自减运算符,以及解引用运算符运算符。 作为一种替代,有`op`关键字, 用于那些适用场景不广泛,但是在某些场景下必须的运算符。 ``` C++ vleft = vright op `0xff0000ff`; ``` ### 2 C 类型系统:Yac语言类型系统设计总结(修订版) #### 一、核心语法框架:方括号标识类型,降低解析负担 为解决“类型与变量标识符混淆”问题,所有类型在使用时需用方括号`[]`包裹(原生类型可不省略),解析器通过`[类型] 变量名`的语法直接识别类型,无需依赖语义分析,完全适配LR1解析器能力。 - 示例:`[int] a;`(原生类型可省略为`int a;`)、`[struct User] u;`(自定义类型必须带方括号)。 #### 二、基础类型属性体系:属性前置无固定顺序,组合无冲突 类型属性(数据形态、内存布局、权限)仅需位于类型名之前,**无固定顺序**,解析器通过“属性间的兼容规则”判断合法性(如`disp`仅能与`vector`组合),无需依赖顺序优先级。 ##### 1. 数据形态(描述数据结构) - `scalar`:标量(单个数值,默认属性),适用于普通变量、计数器等。 示例:`[scalar sint] a;`(等价于`[sint] a;`,默认`sca`)、`[sint scalar] b;`(顺序不对,非法)。 - `vector`:矢量(SIMD并行计算专用),适用于图形渲染、数值计算等高性能场景。 示例:`[vector float] v;`、`[float vector] v2;`(顺序不对,非法)。 ##### 2. 内存布局(描述SIMD矢量类型存储方式)仅仅与`vector`组合的修饰 - `comp`:连续布局(内存地址连续,取址返回标量指针),适用于数组、字符串等需连续访问的数据,。 示例:`[comp vector float] cont_vec;`、`[vector float comp] cont_vec2;`(顺序不对,非法)。 - `disp`:离散布局(按最优对齐离散分布,取址返回矢量专用指针),**仅能与`vector`组合**(标量无需离散布局),适用于高性能对齐访问。 示例:`[disp vector float] disp_vec;`、`[vector disp float] disp_vec2;`(顺序不对,非法);`[disp scalar int]`(错误,`disp`与`scalar`冲突)。 ##### 3. 权限控制(描述操作权限)可与`sca`/`vec`组合。 - `rdo`:只读(仅允许读取,禁止修改),适用于常量、配置值, 示例:`[rdo sint] max_val = 100;`、`[sca sint rdo] max_val2;`(顺序不限,合法)。 - `wrd`:读写(允许读取和修改,默认属性),适用于普通变量, 示例:`[wrd sint] count = 0;`(等价于`[sint] count = 0;`,默认`wrd`)、`[sca wrd sint] count2;`(顺序不限,合法)。 - `own`:内存拥有(额外支持手动内存管理,如`alloc`/`free`),**是比`wrd`更高一级别的权限**(只读无需内存控制),适用于动态分配的数据。 示例:``[own array] dyn_num(`[1, 2]`);dyn_num.recapacity(3);``、`[own wrd int] dyn_num2;`(错误,`own`与`wrd`冲突)。 #### 三、自定义类型与别名:兼容C风格,解析无歧义 - **结构体**:声明和使用时必须显式加`struct`关键字(如`struct User { ... }; [struct User] u;`),解析器通过“`struct+标识符`”直接识别,无需语义分析。属性可前置任意顺序,示例:`[rdo struct User] u2;`、`[struct User rdo] u3;`(均合法)。 - **typedef别名**:沿用C风格定义(`typedef 原类型 别名;`),使用时需带方括号(如`typedef struct User UserAlias; [UserAlias] u;`),别名仅作为类型替代符号,属性可前置任意顺序,示例:`[vec comp UserAlias] u4;`、`[comp UserAlias vec] u5;`(均合法)。 #### 四、特殊类型:字面值与符号量,区分确定阶段 ##### 1. 字面值(`flit`,前端确定值) - 定义:编译前端(词法/语法分析阶段)即可确定的值,如字符串、固定数值,强制只读(隐含`rdo`语义)。 - 类型规则:纯数值化(无符号/长度属性),默认由后端适配变量类型。属性可前置任意顺序(需兼容)。 - 数值字面值:默认按“最小能容纳的无符号类型”推导(如`0xFF`→`uint8`),支持显式后缀(`u8`/`u16`)指定类型。示例:`[flit sca int] num = 123;`、`[sca flit int] num2 = 0xFFu8;`(顺序不限,合法)。 - 字符串字面值:默认`comp char[]`(连续字符数组),支持`[n]`指定长度(如`"hello"[10]`→`comp char[10]`)。示例:`[flit comp char*] msg = "hello";`、`[comp flit char*] msg2 = "world";`(顺序不限,合法)。 ##### 2. 符号量(分阶段确定值,强制只读) - **编译期符号量(`blit`)**:编译后期或链接时确定值(如结构体大小、静态常量),默认类型`uint`,支持显式指定(如`int`/`uint32`),属性可前置任意顺序(需兼容)。 示例:`[blit sca] struct_size = sizeof(struct User);`、`[sca blit uint32] static_const = 0x1234;`(顺序不限,合法)。 - **运行时符号量(`rtblit`)**:程序运行时确定值(如动态库导出常量),默认类型`uint`,支持显式指定,属性可前置任意顺序(需兼容)。 示例:`[rtblit sca int] dyn_lib_const = load_from_dylib("CONST");`、`[sca int rtblit] dyn_lib_const2;`(顺序不限,合法)。 #### 五、机器字长适配:兼容未来扩展 - 无符号变量默认绑定“机器字长”(用`uint`表示),后端根据当前机器字长(32位/64位/128位等)自动适配存储。属性可前置任意顺序,示例:`[wrd uint] max_uint = -1;`、`[uint wrd] max_uint2 = -1;`(均合法)。 - 负一赋值无符号变量时,后端按“全1二进制补全”规则处理(如32位机器存储`0xFFFFFFFF`,128位机器存储`0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF`)。 #### 总结 Yac语言类型系统以“**解析器低负担、用户低学习成本、未来兼容性**”为核心,通过方括号标识类型、属性前置无固定顺序(仅需无冲突)、阶段化特殊类型设计,既保留了语法灵活性,又解决了传统C/C++的类型歧义问题,为后续扩展(如静态反射)奠定了扎实基础。 ### 3 C 语言风格的指针与数组 Yac在语法中保留了C语言风格的指针,并且指针之间的类型转化风格更加自由无约束。对于一般的场景,我们当然强烈不推荐程序员使用C风格的指针。我们提供了一个第二类关键字`#dA_ngeOus_____shfdeerw`,它的原意是dangerous,我们故意拼写错误,并且使它的大小写规则和下划线音节划分变得滑稽,并且加了一串乱码破坏使用者的记忆和拼写。所有使用C风格指针的地方这个丑陋的关键字如影随形。当然对于更容易出现的一般危险的地方还有 `unsafe`和 `assert`。对于C语言风格指针的类型名也非常丑陋 `Cstyle___Ptr__No`。唯一的好消息是它是泛型/模板风格声明。Yac的编译器可以启用对是否使用C风格指针的检查。 ```C++ [real] target = `0.1`; #dA_ngeOus_____shfdeerw [Cstyle___Ptr__No] Cptr = ⌖ [sint] here = Cptr[0]; ``` 顺便一提,由于不想让人使用它,解引用运算符运算符也被删去了。只能使用类似数组下标的访问方式进行访问。 尽管如此不情愿,但是我们依然保留了C语言风格的指针是为了。 - 1 可以用于一些小众的场景比如系统库的编写。 - 2 作为概念上的脐带和胎盘,衍生出更高级的工具。 我们提供了两种功能的替代品用于代替C语言风格的指针,分别是`array`容器和`ptr`容器。`ptr`容器不能寻址,也不能从整数强制类型转换得到。所以它好像C++的引用或者别名那样直接可以代替容器中类型参与运算而不需要解引用。 ``` [wrd sint] value = `1`; [ptr] a &= value; a = `2`;// now value is 2 ``` 我们完全删去了C语言风格的数组,改用`array`基本容器代替。`array`基本容器是语法级别容器,而非标准库级别。它可以通过模板参数静态决定具体是哪一种`array`容器,可以是C++的vector那种存储capacity和长度尾插优化型的,也可以是C语言那种仅仅包含一个指针的静态长度型的。保留C语言风格指针的第一个目的其实可有可无, C语言风格的指针能做的 ,`array`容器和`ptr`容器几乎都能做,不能做的提供一个OS标准库,几乎也能做了。核心目的是第二个。`array`容器和`ptr`容器的实现依赖C风格的指针,而且,这种实现会直接暴露在语言标准中而不是躲藏在标准规定的行为后面。编译器可能会做某些优化改变具体的实现,但是程序员可观测行为上和依赖与C语言风格的指针的实现无异。 ### 4 独特的异常机制 :轻量双轨异常处理机制:设计逻辑与语法规则全解析 #### 一、核心设计理念 机制的本质是“用显式类型约束替代隐式语法标记”,我们把异常分为了两类并且提供分别的处理机制: 1. 业务异常(预期内、可恢复),本质是正确运行的程序遇到了某些错误的数据和IO,比如说编译器遇到了语法错误。通过**双返回机制**承载,强制显式处理,无异常路径在传统体系结构上单指令开销,某些新体系结构上可能是零开销的。异常发生时的性能相较于栈展开可控且轻微,也能保证资源的正确释放。 2. 系统故障(预期外、需隔离/终止),本质是程序本身的设计错误,或者计算机的系统级失效被运行时捕获。通过**Panic机制**处理,依托静态表格与函数返回类型辅助界定传播范围。无需栈展开,但是此时不保证资源能够顺利释放。仅仅帮助程序员协同做一些必要的清理工作和日志定位。你可以这样考虑,有些可能出大错场景比如申请内存和下标检查几乎是无处不在的。如果每处这样的地方都由业务异常机制进行兜底处理,那么程序的代码量和性能都会被显著伤害。所以panic是一个必要选项。类型系统不会去管一个函数是否能抛出panic。panic可以自由穿透的很远直到终止程序,也可以由程序员捕获。panic出现后,默认的一般处理方式是最小化损失后终止程序。但是程序员也可以选择捕获并处理它。 #### 二、双返回业务异常机制(处理可恢复错误) ##### 1. 语法规则 - **函数声明**:双返回函数需通过`exception`关键字指定异常类型,格式为`[返回类型] 函数名(参数列表) exception : [异常类型]`;单返回函数无`exception`声明,不支持异常抛出与接收,但可调用双返回函数(需显式处理其异常)。 - **异常抛出**:通过`throw`关键字抛出业务异常,仅双返回函数可使用`throw`,抛出值类型需与声明的异常类型一致。 - **异常捕获与最近邻匹配规则**: 调用双返回函数时,异常通过`catch`块捕获,遵循“最近邻匹配”原则: 1. 优先匹配当前基本块(如`if`块、`loop`块、函数体等代码块)内**同类型**的`catch`; 2. 若当前基本块无匹配`catch`,则向上级基本块(外层嵌套块)逐级查找最近的同类型`catch`; 3. 若函数内所有基本块均无匹配`catch`: - 若当前函数是双返回函数,异常自动上抛至调用者(需满足上抛类型兼容规则); - 若当前函数是单返回函数,编译报错(单返回函数必须显式处理所有可能的异常)。 - **上抛类型兼容规则**:双返回函数调用双返回函数时,若触发异常自动上抛,上层函数的异常类型必须与下层函数的异常类型兼容(完全一致、上层为下层超集、或支持隐式转换),编译期严格校验。 - **强制约束**:单返回函数调用双返回函数时,必须通过最近邻匹配规则在自身范围内显式捕获所有可能的异常,否则编译报错。 ##### 2. 设计逻辑 - 双返回函数调用时,编译器预压栈“第二返回地址”(异常处理入口),无异常时直接执行正常路径,无`if/else`分支开销; - 异常触发时,被调用者通过第二返回地址跳转到调用者的`catch`块,控制流转移依赖编译期预标记的基本块层级,不依赖栈展开; - 最近邻匹配规则通过编译期分析基本块嵌套关系实现:每个`catch`块在编译期被标记所属的基本块层级,异常触发时按层级从内到外查找,确保匹配效率(O(1)层级定位); - 异常类型由用户自定义(如错误码、结构化枚举),上抛兼容性通过类型系统保障,避免信息丢失。 ##### 代码示例(C风格) ```c // 下层双返回函数:异常类型为uint int read_value() exception : uint { if (/* 读取失败 */) throw 5; // 错误码5:读取失败 return 42; } // 上层双返回函数:异常类型为enum(兼容uint) typedef enum { ERR_READ = 5, ERR_CALC = 6 } AppError; int process() exception : AppError { int val; if (some_condition) { // 内层基本块:调用read_value() val = read_value(); catch uint err: { // 匹配uint类型,最近邻优先 if (err == 5) throw ERR_READ; // 转换为上层异常类型 } } else { // 另一基本块:无catch,异常会向上抛至process()函数体 val = read_value(); } // 函数体基本块:若else分支触发异常,会查找此处是否有匹配catch catch AppError err: { // 匹配AppError类型 return -1; // 局部处理,不向上抛 } return val * 2; } // 单返回函数:必须显式处理所有异常 void main() { int res = process(); // 最近邻匹配:先查main()函数体的catch catch AppError err: { printf("处理异常:%d\n", err); } catch panic: { printf("捕获Panic\n"); } } ``` #### 三、Panic故障机制(处理不可恢复/局部故障) ##### 1. 语法规则 - **Panic触发**:任何函数(包括单返回函数)均可通过`panic`关键字触发,格式为`panic [故障描述]`,无类型限制,可携带任意上下文(如字符串、栈信息)。 - **单返回函数内的Panic**:单返回函数触发`panic`后,因自身不支持双返回机制(无第二返回地址),无法上抛,直接进入终止流程:执行`on_panic_immediate`→查询静态表格确认是单返回函数→执行`on_panic_terminate`→终止进程。 - **双返回函数内的Panic传播**:双返回函数触发`panic`后,遵循与业务异常类似的“最近邻匹配”规则: 1. 优先查找当前基本块内的`catch panic`; 2. 无则向上级基本块逐级查找; 3. 函数内无匹配`catch panic`时,通过静态表格确认调用者是否为双返回函数,是则上抛,否则进入终止流程。 - **捕获限制**:`catch panic`仅能捕获当前调用链中双返回函数传播的`panic`,单返回函数触发的`panic`不可捕获(直接终止)。 ##### 2. 核心设计逻辑 - **静态地址表格**:编译期记录所有双返回函数的地址范围,`panic`传播时通过当前栈地址快速查询函数类型(双返回/单返回),确定是否可上抛; - **传播流程**: 1. `panic`触发时,立即执行`on_panic_immediate`(用户自定义,记录第一现场); 2. 按最近邻规则查找`catch panic`:找到则执行处理逻辑,未找到则通过静态表格判断是否可上抛(双返回函数允许上抛,单返回函数直接终止); 3. 上抛过程中,自动执行传播路径上的函数清理清单(编译期预生成,含局部资源析构逻辑),无需栈展开; 4. 最终传播至单返回函数或无处理者时,执行`on_panic_terminate`(用户自定义,最终清理),随后强制终止进程。 #### 四、关键特性与约束 ##### 1. 语法极简性 - 仅通过`exception`、`throw`、`panic`、`catch`四个核心语法元素实现全场景处理,无冗余关键字; - 基本块层级与函数返回类型天然界定异常/Panic的传播与匹配范围,无需显式声明。 ##### 2. 高性能保障 - 无异常路径:仅压栈第二返回地址(单指令开销),兼容内联优化(内联时自动消除地址压栈); - 异常/Panic路径:依赖编译期预标记的基本块层级与静态表格,查找匹配`catch`的复杂度为O(基本块嵌套深度),远低于栈展开; - 类型校验与匹配规则均在编译期完成,运行时无额外开销。 ##### 3. 安全性与灵活性 - 最近邻匹配规则确保异常/Panic被最相关的代码块处理,避免跨层误捕获; - 单返回函数的`panic`直接终止,双返回函数的`panic`可控传播,兼顾“快速止损”与“局部恢复”; - 用户自定义异常类型与`panic`处理函数,平衡标准化与个性化需求。 #### 五、总结 该机制通过“双返回承载可恢复异常+Panic处理不可预期故障”的双轨设计,结合“最近邻匹配”与“类型兼容”规则,在语法极简与功能完备间实现平衡。单返回函数可触发`panic`但强制终止,双返回函数支持异常/Panic的可控传播,全程依赖编译期静态信息保障性能与安全性,完美适配对高效、简洁、可靠有高要求的新语言设计。 ### 5 Yac语言的函数-例程二分法:场景分离的极致控制 在系统级编程中,“函数”承担着多重角色——既是高频调用的基础计算单元,又是复杂逻辑的封装载体。这种“万能性”往往意味着妥协:基础运算因兼容复杂逻辑而难以极致优化,复杂逻辑因缺乏明确约束而隐藏风险。Yac语言通过“函数(`func`)”与“例程(`routine`)”的二分设计,将两种场景彻底分离,用语法约束实现“轻量高效”与“灵活复杂”的精准适配,解决了C风格单一函数模型的固有矛盾。 #### 一、设计核心:用约束与灵活划分边界 Yac的二分法本质是“场景绑定的语法契约”:为高频、基础、可预测的逻辑设计“函数(`func`)”,通过强约束换取编译器极致优化;为复杂、有状态、需灵活组合的逻辑设计“例程(`routine`)”,通过松约束支持复杂场景——两者严格隔离,各司其职。 #### 二、函数(`func`):轻量、确定、编译器友好的计算单元 `func`是为“高频基础操作”量身定制的轻量结构,其核心是**通过编译期可验证的约束**,让编译器做最激进的优化: - **定义的刚性要求**:函数可以在声明后完成定义,但是一个函数只能调用已经完成定义的函数,只有过程才可以调用仅声明未调用的函数; - **无递归与调用顺序约束**:只能调用“定义早于自身”的函数,不许调用例程,调用链形成严格的有向无环图(DAG),编译期可精确计算最大栈深度(实时系统核心需求),从语法上杜绝递归栈溢出; - **可嵌入表达式**:支持作为表达式的一部分(如`sum + add(a, b)`),暗示其“纯计算”特性(但是可以有副作用),编译器可放心内联、重排或折叠为常量(如加密算法中的位运算); - **重载仅依赖参数类型**:调用匹配逻辑在编译期即可确定,无运行时开销,适合基础操作的多态需求(如`add(int, int)`与`add(float, float)`)。 - **不是函数式编程中函数的概念**:可以有完整的使用异常机制,可以有副作用比如打开文件。 这些约束让`func`成为“编译器的理想优化对象”,尤其适合内核快速路径、实时控制逻辑、数值计算等对性能与确定性要求极高的场景——其行为在编译期即可完全预测,无需为灵活性牺牲效率。 #### 三、例程(`routine`):复杂、灵活、模块化的逻辑载体 `routine`是为“复杂业务逻辑”设计的重量结构,通过放松约束支持灵活组合: - **声明与定义分离**:支持“头文件声明+实现文件定义”的模块化设计,适合大型逻辑封装(如网络协议处理、数据结构遍历); - **支持递归与自由调用**:可调用任意`func`或`routine`(包括自身),能处理需要循环/递归的复杂逻辑(如树的深度优先遍历); - **调用需单独成行**:强制与表达式分离,显式标记“可能有副作用”(如IO操作、状态修改),提升代码可读性(一眼区分“计算”与“操作”); - **重载依赖参数+返回值类型**:支持“同一操作根据上下文返回不同类型”(如`parse("123")`返回`int`,`parse("123.45")`返回`float`),配合多返回值特性(如`(result, error_code)`),完美适配复杂业务场景。 `routine`的灵活性使其成为复杂逻辑的天然载体,且因与`func`严格隔离(`func`不能调用`routine`),不会污染`func`的优化空间——高频路径用`func`保性能,复杂逻辑用`routine`保灵活,互不干扰。 #### 四、不可替代的约束:语法强制解决C的“人为不可靠性” Yac的二分法绝非“语法糖”,而是通过语法强制解决C语言“手动约束不可靠”的硬伤: - **实时系统的栈确定性**:C语言无法禁止递归,大型项目中难免因疏忽引入递归导致栈溢出;而`func`的DAG调用链让编译器可静态计算栈大小(如嵌入式ECU的控制函数栈必须≤512字节),这是安全关键场景的“生死线”; - **安全验证的可行性**:`func`的有限状态空间(无递归、无指针逃逸)让形式化验证工具可高效证明其正确性(如航空软件的DO-178B合规性),而C的函数因状态无限几乎无法验证; - **线程发射的强类型安全**:C用`void*`传递线程参数,既丑陋又易引发类型错误;Yac强制线程入口为`routine`,支持强类型参数(如`thread(uint id, params ···)`),编译期校验类型,彻底告别`void*`的隐患。 #### 五、成员方法的强制分类:语义与安全的双重保障 对于类型的成员方法,Yac同样通过二分法强化语义: - **必须为`func`的方法**:析构函数(`destructor`,需安全释放资源,禁止递归/副作用)、构造函数、运算符重载(如`==`,需纯计算无副作用)——这些方法的核心语义依赖`func`的约束,用`routine`会破坏安全性; - **可为`routine`的方法**:复杂业务逻辑(如`process_data()`)、有副作用的操作(如`connect()`)、递归逻辑(如`traverse()`)——这些方法需要`routine`的灵活性,且调用时机可控,无需编译期强约束。 #### 总结:二分法是“系统级语言的精准主义” Yac的函数-例程二分法,本质是用“语法明确性”替代“人为约定”:`func`的约束不是限制,而是为编译器提供“激进优化的通行证”;`routine`的灵活不是放纵,而是为复杂逻辑提供“安全封装的容器”。这种设计让Yac在性能、安全性与可读性之间找到完美平衡——既比C的单一函数模型更高效、更可靠,又比“万能抽象”更贴近系统级编程的本质需求。: #### 作者内心总结 上述都是AI的吹捧性总结,除了夸大其词外还有一些事实性错误。函数和例程二分虽然有一些好处,但是增加了语法复杂度,作者内心非常不情愿。只是凭直觉觉得这种二分还会有额外的好处。所以**暂定**保留这种设计。 ### 6 Yac语言模块设计:显式约束、序即安全与平权架构 Yac语言的模块系统以“无语法糖、显式依赖、序约束优先、模块平权”为核心,深度契合其“易读易写让位于规则清晰、安全引导而非强制”的设计理念,同时解决了跨文件组织、模块间依赖、类型一致性、全局变量冲突等核心问题,为系统级编程提供灵活且可控的代码组织方案。 #### 一、模块的核心定位:编译单元与平权架构 Yac的模块是最小编译单元,可由单个或多个`.yac`源码文件组成,所有模块地位完全平等——无“主模块”与“从模块”之分,编译时可指定任意模块中的任意`routine`作为进程入口,适配系统级编程中“多入口场景”(如内核模块、服务进程、工具程序)的需求。 完全放弃了C语言`#include`,模块是最小独立编译单元,模块本身可以由一个或者多个源文件构成。模块完成编译后自动生成一个模块接口文件 `.yacif`,无需程序员显示书写接口。 #### 二、模块内文件组织:显式前后驱与DAG拼接序 模块内多文件的依赖关系通过“显式前后驱声明”定义,确保编译时的确定性与可预测性: - 每个文件开头需用`#pre`(前驱文件)和`#suc`(后继文件)声明依赖,例如`#pre "base.yac"; #suc "logic.yac";`,编译器强制双向校验——若A的`#suc`包含B,则B的`#pre`必须包含A,避免依赖矛盾。 - 依赖无环。 - 模块入口文件(第一个拼接的文件)需满足`#pre`为空,且首个有效信息为`#module`声明(含模块名、版本等元信息),例如`#module core v1.0; #pre ; #suc "data.yac";`。 - 编译时,模块内文件按“有向无环图(DAG)”指定的顺序拼接为临时整体文件,确保所有元素(类型、`func`、`routine`)的可见性严格遵循“定义早于使用”的序约束,为编译器静态分析与优化奠定基础。 #### 三、模块间依赖:显式预加载与编译序DAG 模块间依赖拒绝隐式关联,通过“全局编译序DAG”与`#pre-import`预加载机制实现可控的跨模块访问: - 所有模块的编译顺序由程序员通过构建脚本指定为全局严格DAG(禁止循环),先编译模块的完整接口(`.yacif`)可供后编译模块直接引用,无需额外声明。 - 若先编译模块需引用后编译模块的元素(违序依赖),需通过`#pre-import`显式手写接口——禁止从外部文件导入接口,确保程序员完全掌控依赖范围。语法格式为`#pre-import <来源模块> { <接口内容> }`,接口内容仅包含声明性信息(如`struct B::Foo;`、`[int] func B::add([int] a, [int] b);`),且必须显式标注来源模块。 - 预加载的元素仅支持“声明性使用”:先编译模块可通过`routine`调用后编译模块的`func`(`func`本身仅能调用已定义的`func`,不可后向调用),但不可访问`struct`成员或直接执行实现,需在链接阶段验证接口一致性。 #### 四、类型一致性:全局唯一标识与自动别名映射 为解决“禁止隐式穿透”与“类型一致性”的矛盾,Yac采用“全局唯一标识+编译器自动别名”机制,确保跨模块类型无歧义、无冗余: - 任何类型的全局唯一标识为`来源模块名::类型名`(如模块B中的`struct BB`标识为`B::BB`),编译器内部维护类型元数据字典,确保同一类型仅存储一次,避免二进制膨胀。 - 当模块A的`struct AA`包含`B::BB`作为成员时,编译器自动生成别名`A::B::BB`(本质是`B::BB`的别名,非新类型)。模块C导入A后,可通过`A::A::BB`间接使用该类型,无需显式导入B;若C需直接使用`B::BB`,则必须显式`#import B`,禁止隐式直接引用。 - 无论通过间接别名(`A::B::BB`)还是直接标识(`B::BB`)访问,编译器均判定为同一类型,支持直接赋值、类型转换,无额外开销,彻底解决跨模块类型互操作的一致性问题。 #### 五、全局变量:模块隔离与冲突规避 Yac的全局变量遵循“模块名+变量名”双唯一规则,从设计层面规避多模块共享导致的冲突: - 全局变量默认仅模块内可见,需通过`#extern`显式声明为向外暴露,其全局唯一标识为`模块名::变量名`(如`LibX::mkl_threads`、`LibY::mkl_threads`),不同模块的同名全局变量独立存储,无覆盖或冲突风险。 - 设计层面引导程序员用“私有状态+显式接口”替代裸全局变量:推荐将全局变量设为模块私有,通过`routine`暴露配置接口(如`set_mkl_threads([int] num)`),减少无边界全局状态带来的不可控性。 #### 六、安全设计:显式代价与可控风险 Yac的模块系统延续了“安全非强制,不安全用法需付出显式代价”的理念: - 安全的核心来自“序约束”与“显式声明”:`func`的严格序约束避免递归与栈溢出,显式导入禁止隐式依赖,类型唯一标识避免类型混淆,全局变量隔离规避冲突。 #### 总结 Yac的模块系统是其“规则清晰、显式优先、序即安全”设计理念的集中体现:通过显式前后驱、编译序DAG、预加载机制解决依赖问题,通过全局唯一标识保障类型与变量的一致性,通过模块平权与入口灵活适配系统级场景,最终实现“按规则写即安全,底层操作不妥协”的核心目标,为Vacuum指令集适配与SIMD高性能编程提供了可靠的代码组织基础。