Compare commits

..

39 Commits

Author SHA1 Message Date
PuqiAR 68e6552d07 Rewrite: C++ → Rust, Lexer + Error system complete
全量 Rust 重写 Fig 编译器前端。

Lexer: 43 个测试,千行压力测试通过,完整 UTF-8 支持(中文标识符/字符串),运算符最长匹配打表,单行/多行注释 + 错误恢复。

Error System: Diagnostic trait + Warning/Error/Critical 三级严重度,源码上下文渲染(Unicode 波浪线、中英双语 i18n),SourcePosition/SourceRange 为 LSP 准备,Related 关联信息,thrower 宏追踪编译器源码位置。

Build: cargo + build.rs(git hash + 编译时间戳)。

删除全部 C++ 源码。
2026-07-23 22:50:37 +08:00
PuqiAR 82c7c7178f Refactor: 更新OpCode枚举,添加详细注释 2026-07-23 13:01:56 +08:00
PuqiAR 9f7fe79a2e Made some preparations to refactor semantic analysis and type system 2026-07-21 22:52:17 +08:00
PuqiAR 0f5304001c 优化注释 2026-07-04 19:32:01 +08:00
PuqiAR 680197aafe Refactor: 重构Parser和AST结构,以支持新的语言特性
- 更新了 ParserTest,以改进文件路径处理和输出格式。
- 在 StmtParser 中新增了 parseConstDecl 和 parseForStmt 方法,用于处理常量声明和 for 循环。
- TypeExpr现归类为Expr。TypeExpr属于Expr,语义阶段视为Expr
- 添加了新的 AST 节点:PostfixExpr、TernaryExpr、ForStmt 和 ImportStmt,用于表示新的语法结构。
2026-06-06 22:12:04 +08:00
PuqiAR 4f87078a87 Plan 2026-06-01 18:52:06 +08:00
PuqiAR 9338c21449 *(无用) feat: 在VM.cpp中添加likely属性以优化分支预测
refact: 在xmake.lua中优化构建设置
2026-05-10 21:25:05 +08:00
PuqiAR 98de782760 feat: 增加repl入口,-r/--repl。 添加计时选项。 -- 我发现一个问题,analyzer没法保存环境。完了。 2026-04-30 21:24:11 +08:00
PuqiAR fafa2b4946 feat: 在解析器中实现 Lambda 和 new 表达式
- 增加了对 Lambda 表达式的初步解析支持,包括参数处理和返回类型。Lambda闭包尚未支持。
- 引入了用于对象初始化的新的表达式,支持可选的命名参数。
- 改进了表达式语法错误的错误报告。
- 更新了解析器和分析器以处理新的表达式类型并验证其语义。
- 修改了现有测试以涵盖新功能并确保其正确性。
- 改进了各种解析和语义错误的诊断。
2026-04-12 10:07:51 +08:00
PuqiAR 570a87c3cd feat: 增加函数类型表达式支持,更新解析器和分析器 2026-03-18 17:30:09 +08:00
PuqiAR e1d9812f92 refact:实现参数解析器和入口点
- 新增了一个名为 ArgumentParser 的类来处理命令行参数,其中包括用于显示帮助、显示版本和显示许可证的标志。
- 更新了 main.cpp 以使用 ArgumentParser 来改进命令行界面。
- 创建了 Entry.cpp 和 Entry.hpp 来封装虚拟机执行逻辑,从而实现更好的关注点分离。
- 调整了 xmake.lua 以包含 ArgumentParser 和 Entry 组件的新源文件。
- 强化了命令行使用的错误处理和用户反馈。
2026-03-14 14:18:21 +08:00
PuqiAR 6bcc98bdb3 删除无用的东西233 2026-03-13 20:11:46 +08:00
PuqiAR 91b5a0e384 feat: 增加一个很简单的repl(还不能用) 写了readme 2026-03-13 01:28:43 +08:00
PuqiAR c0eacfd236 refactor: 修改Disassembler使用CoreIO的stream 2026-03-11 21:51:58 +08:00
PuqiAR 51a939ac45 对编译器和虚拟机进行重构,以支持闭包和垃圾回收功能
- 去除了不再使用的结构,并更新了编译器以处理新的闭包语义。
- 改进了 Compiler,使其能够生成带有源位置跟踪的指令。
- 在 FunctionObject 和 VM 中引入了作用域变量管理,以支持动态闭包。
- 实现了使用标记-扫描(Mark-And-Sweep) (Tri-Color tracing) 算法的垃圾回收机制,包括对作用域变量的处理。
- 在 VM 中增加了函数加载和作用域变量检索的支持。
- 更新了对象模型,包括引入 InstanceObject 并改进内存管理。
- 添加了用于调试的全局变量打印功能。
2026-03-11 16:53:10 +08:00
PuqiAR 0f635ccf2b 重构类型系统并改进诊断功能
- 更新了类型系统,新增了类型并优化了结构。
- 引入了基类型和派生类,用于函数、结构体和接口类型。
- 实现了类型上下文,用于管理内置类型和类型解析。
- 添加了诊断类,用于收集和报告警告和错误。
- 通过改进错误处理增强了虚拟机执行,以应对递归限制问题。
- 实现了反汇编器,将字节码转换为代码,以改善调试和分析。
- 添加了新的抽象语法树节点,用于成员表达式、对象初始化、接口和结构体定义。
- 引入了语义错误测试,包括重定义、未声明的变量和无效的结构字段。
2026-03-10 12:33:17 +08:00
PuqiAR 90448006ff refactor: 引入 Arena 内存池并优化指令分发,为类型系统重构做准备 2026-03-08 15:59:55 +08:00
PuqiAR 91e4eb734e feat: 使用Computed Goto优化指令分发机制和算术运算处理 2026-03-07 21:33:55 +08:00
PuqiAR 6dbecbbdc0 feat: 重构编译器以支持函数定义和调用,添加新的字节码以支持函数调用
另外,我很高兴地宣布,fib(40) 递归法 在我的平台, i5-13490f,只需要 6600ms, fib(30) 56ms
这是历史性的一刻!
2026-03-07 00:34:52 +08:00
PuqiAR 1fe9ccf7ea feat: 实现控制流语句并优化类型解析
- 新增了 ReturnStmt、BreakStmt 和 ContinueStmt 结构,以支持 AST 中的控制流。
- 引入了 TypeInfo 和 TypeContext 以实现更好的类型管理和解析。
- 对分析器进行了增强,能够处理函数定义和返回语句,包括类型检查和错误处理。
- 更新了编译器和解析器以适应新的控制流语句和类型解析逻辑。
- 重构了现有代码以提高清晰度和可维护性,包括对表达式和语句中的类型处理的更改。
- 删除了过时的 String 和 Struct 定义,代之以 StringObject 和 StructObject 以实现更好的对象表示。
2026-02-28 20:42:15 +08:00
PuqiAR bb23ddf9fa feat: 添加函数定义和类型表达式支持,重构解析器以处理新语法(函数定义) 2026-02-26 14:41:41 +08:00
PuqiAR 12dc31a6c0 fix: 修复了在解析字面量时的条件逻辑,确保整数转换在浮点数转换之后进行 2026-02-25 19:15:25 +08:00
PuqiAR a0fb8cdffb feat: 添加“while 语句”支持,并对解析器进行重构以处理控制流相关内容
- 引入了 WhileStmt 结构来表示 while 循环语句。
- 在解析器中实现了对 while 语句的解析逻辑。
- 在分析器中为 while 语句添加了语义分析。
- 重构了现有的解析器方法,以利用 StateProtector 进行状态管理。
- 更新了对各种表达式和语句的错误处理。
- 移除了未使用的终止符管理方法,并简化了表达式解析。
- 将 FigLSPServer.cpp 重命名为 LSPServer.cpp,并调整了构建配置。
- 增强了重复声明和类型错误的错误报告。
- 在多个文件中改进了代码格式和一致性。
2026-02-25 17:31:00 +08:00
PuqiAR b7bb889676 feat: 增加了analyzer, compiler不再分析并且报错, 只生产 bytecode, analyzer作为前端结束最后一道防线检查代码,同时引入一个简单的LSP 2026-02-23 19:57:28 +08:00
PuqiAR 852dd27836 feat: 优化表达式解析器,添加终止符重置功能及代码格式调整 2026-02-23 15:03:53 +08:00
PuqiAR abdb1d2fb0 feat: 添加跳转指令支持及条件语句编译实现 2026-02-20 17:02:13 +08:00
PuqiAR eb20993e27 feat: 添加If语句及块语句解析支持 2026-02-20 15:46:33 +08:00
PuqiAR 2631f76da1 feat: Implement compiler and virtual machine for Fig language
- Added Compiler class with methods for compiling programs, statements, and expressions.
- Introduced Proto structure to hold compiled bytecode and constants.
- Implemented expression compilation including literals, identifiers, and infix expressions.
- Developed statement compilation for variable declarations and expression statements.
- Created a VM class to execute compiled bytecode with support for arithmetic and comparison operations.
- Added Object and Value classes for handling different data types and memory management.
- Implemented String and Struct objects for enhanced data representation.
- Established a parser for parsing variable declarations and statements.
- Included tests for the VM and object representations.
2026-02-20 14:05:56 +08:00
PuqiAR f2e899c7a7 更换新Logo! 然后为拓展增加Logo。 2026-02-18 14:28:19 +08:00
PuqiAR c81da16dfb 修复EOF飘逸(去除末尾\n)以及其他修复... 2026-02-18 00:16:59 +08:00
PuqiAR 663fe39070 添加缺失的 doxy 2026-02-17 14:13:57 +08:00
PuqiAR 6b75e028ff 修复右结合绑定力错误 2026-02-17 14:03:48 +08:00
PuqiAR 878157c2fc 完成Parser定义以及表达式解析 2026-02-14 23:03:46 +08:00
PuqiAR 35e479fd05 完成表达式Ast定义。修改了format文件 2026-02-14 18:00:46 +08:00
PuqiAR 51e831cc6a 添加了注释文档 2026-02-14 15:32:11 +08:00
PuqiAR 35b98c4d7f 完成Lexer实现,100%可靠 2026-02-14 14:54:44 +08:00
PuqiAR 877253cbbc 完成 Error定义和ErrorLog. 以及一些相关的东西 2026-02-13 23:11:37 +08:00
PuqiAR cfcdfde170 结构调整2 2026-02-12 14:55:48 +08:00
PuqiAR 5e75402b43 项目结构调整 2026-02-12 14:55:34 +08:00
81 changed files with 3325 additions and 6821 deletions
-214
View File
@@ -1,214 +0,0 @@
# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto
Language: Cpp
# BasedOnStyle: LLVM
# 访问说明符(public、private等)的偏移
AccessModifierOffset: -4
# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行)
AlignAfterOpenBracket: Align
# 连续赋值时,对齐所有等号
AlignConsecutiveAssignments: false
# 连续声明时,对齐所有声明的变量名
AlignConsecutiveDeclarations: false
# 右对齐逃脱换行(使用反斜杠换行)的反斜杠
AlignEscapedNewlines: Right
# 水平对齐二元和三元表达式的操作数
AlignOperands: true
# 对齐连续的尾随的注释
AlignTrailingComments: true
# 允许函数声明的所有参数在放在下一行
AllowAllParametersOfDeclarationOnNextLine: true
# 允许短的块放在同一行
AllowShortBlocksOnASingleLine: true
# 允许短的case标签放在同一行
AllowShortCaseLabelsOnASingleLine: true
# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All
AllowShortFunctionsOnASingleLine: Inline
# 允许短的if语句保持在同一行
AllowShortIfStatementsOnASingleLine: true
# 允许短的循环保持在同一行
AllowShortLoopsOnASingleLine: true
# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数),
# AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义)
AlwaysBreakAfterReturnType: None
# 总是在多行string字面量前换行
AlwaysBreakBeforeMultilineStrings: false
# 总是在template声明后换行
AlwaysBreakTemplateDeclarations: true
# false表示函数实参要么都在同一行,要么都各自一行
BinPackArguments: false
# false表示所有形参要么都在同一行,要么都各自一行
BinPackParameters: false
# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效
BraceWrapping:
# class定义后面
AfterClass: true
# 控制语句后面
AfterControlStatement: true
# enum定义后面
AfterEnum: true
# 函数定义后面
AfterFunction: true
# 命名空间定义后面
AfterNamespace: true
# struct定义后面
AfterStruct: true
# union定义后面
AfterUnion: true
# extern之后
AfterExternBlock: false
# catch之前
BeforeCatch: true
# else之前
BeforeElse: true
# 缩进大括号
IndentBraces: false
# 分离空函数
SplitEmptyFunction: true
# 分离空语句
SplitEmptyRecord: false
# 分离空命名空间
SplitEmptyNamespace: false
# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行)
BreakBeforeBinaryOperators: NonAssignment
# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似),
# Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似),
# Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom
# 注:这里认为语句块也属于函数
BreakBeforeBraces: Custom
# 在三元运算符前换行
BreakBeforeTernaryOperators: false
# 在构造函数的初始化列表的冒号后换行
BreakConstructorInitializers: AfterColon
#BreakInheritanceList: AfterColon
BreakStringLiterals: false
# 每行字符的限制,0表示没有限制
ColumnLimit: 120
CompactNamespaces: true
# 构造函数的初始化列表要么都在同一行,要么都各自一行
ConstructorInitializerAllOnOneLineOrOnePerLine: true
# 构造函数的初始化列表的缩进宽度
ConstructorInitializerIndentWidth: 4
# 延续的行的缩进宽度
ContinuationIndentWidth: 4
# 去除C++11的列表初始化的大括号{后和}前的空格
Cpp11BracedListStyle: true
# 继承最常用的指针和引用的对齐方式
DerivePointerAlignment: false
# 固定命名空间注释
FixNamespaceComments: true
# 缩进case标签
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
# 缩进宽度
IndentWidth: 4
# 函数返回类型换行时,缩进函数声明或函数定义的函数名
IndentWrappedFunctionNames: false
# 保留在块开始处的空行
KeepEmptyLinesAtTheStartOfBlocks: false
# 连续空行的最大数量
MaxEmptyLinesToKeep: 1
# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All
NamespaceIndentation: All
# 指针和引用的对齐: Left, Right, Middle
PointerAlignment: Right
# 允许重新排版注释
ReflowComments: true
# 允许排序#include
SortIncludes: false
# 允许排序 using 声明
SortUsingDeclarations: false
# 在C风格类型转换后添加空格
SpaceAfterCStyleCast: true
# true -> (int) 0.1 false-> (int)0.1
# 在Template 关键字后面添加空格
SpaceAfterTemplateKeyword: true
# 在赋值运算符之前添加空格
SpaceBeforeAssignmentOperators: true
# SpaceBeforeCpp11BracedList: true
# SpaceBeforeCtorInitializerColon: true
# SpaceBeforeInheritanceColon: true
# 开圆括号之前添加一个空格: Never, ControlStatements, Always
SpaceBeforeParens: ControlStatements
# SpaceBeforeRangeBasedForLoopColon: true
# 在空的圆括号中添加空格
SpaceInEmptyParentheses: false
# 在尾随的评论前添加的空格数(只适用于//)
SpacesBeforeTrailingComments: 1
# 在尖括号的<后和>前添加空格
SpacesInAngles: false
# 在C风格类型转换的括号中添加空格
SpacesInCStyleCastParentheses: false
# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格
SpacesInContainerLiterals: true
# 在圆括号的(后和)前添加空格
SpacesInParentheses: false
# 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响
SpacesInSquareBrackets: false
# 标准: Cpp03, Cpp11, Auto
Standard: Auto
# tab宽度
TabWidth: 4
# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always
UseTab: Never
+15
View File
@@ -0,0 +1,15 @@
# Xmake cache
.xmake/
build/
# MacOS Cache
.DS_Store
.vscode
.VSCodeCounter
/test.fig
# Added by cargo
/target
Generated
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "fig"
version = "0.6.0"
+6
View File
@@ -0,0 +1,6 @@
[package]
name = "fig"
version = "0.6.0"
edition = "2021"
[dependencies]
+11
View File
@@ -0,0 +1,11 @@
function fib(n)
if (n <= 1) then
return n
else
return fib(n - 1) + fib(n - 2) end
end
local start = os.clock()
local result = fib(30)
local endt = os.clock()
print(result, " cost: ", (endt - start) * 1000, "ms")
@@ -1,11 +1,11 @@
from time import time as tt from time import time as tt
def fib(x:int) -> int: def fib(x:int) -> int:
if x <= 1: return x; if x <= 1: return x
return fib(x-1) + fib(x-2) return fib(x-1) + fib(x-2)
if __name__ == '__main__': if __name__ == '__main__':
t0 = tt() t0 = tt()
result = fib(30) result = fib(35)
t1 = tt() t1 = tt()
print('cost: ',t1-t0, 'result:', result) print('cost: ',t1-t0, 'result:', result)
+5
View File
@@ -0,0 +1,5 @@
out
dist
node_modules
.vscode-test/
*.vsix
+5
View File
@@ -0,0 +1,5 @@
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'out/test/**/*.test.js',
});
+11
View File
@@ -0,0 +1,11 @@
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/eslint.config.mjs
**/*.map
**/*.ts
**/.vscode-test.*
+9
View File
@@ -0,0 +1,9 @@
# Change Log
All notable changes to the "fig-vscode" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release
+46
View File
@@ -0,0 +1,46 @@
MIT License (Fig)
Copyright (c) 2025 PuqiAR <im@puqiar.top>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
2. This project includes code from the following projects with their respective licenses:
- argparse (MIT License)
Copyright (c) 2018 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- magic_enum (MIT License)
Copyright (c) 2019 - 2024 Daniil Goncharov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+3
View File
@@ -0,0 +1,3 @@
# fig-vscode README
Fuck.
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
import typescriptEslint from "typescript-eslint";
export default [{
files: ["**/*.ts"],
}, {
plugins: {
"@typescript-eslint": typescriptEslint.plugin,
},
languageOptions: {
parser: typescriptEslint.parser,
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"@typescript-eslint/naming-convention": ["warn", {
selector: "import",
format: ["camelCase", "PascalCase"],
}],
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
},
}];
+4
View File
@@ -0,0 +1,4 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" fill="#F28C28"/>
<path d="M135 100 H395 V195 H230 V265 H370 V360 H230 V430 H135 V100 Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 244 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M135 100 H395 V195 H230 V265 H370 V360 H230 V430 H135 V100 Z" fill="#F28C28"/>
</svg>

After

Width:  |  Height:  |  Size: 196 B

+147
View File
@@ -0,0 +1,147 @@
{
"name": "fig-vscode",
"version": "0.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fig-vscode",
"version": "0.5.0",
"dependencies": {
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "^20.6.0",
"@types/vscode": "^1.109.0",
"typescript": "^5.2.2"
},
"engines": {
"vscode": "^1.108.0"
}
},
"node_modules/@types/mocha": {
"version": "10.0.10",
"resolved": "https://registry.npmmirror.com/@types/mocha/-/mocha-10.0.10.tgz",
"integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.33",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.33.tgz",
"integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/vscode": {
"version": "1.109.0",
"resolved": "https://registry.npmmirror.com/@types/vscode/-/vscode-1.109.0.tgz",
"integrity": "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw==",
"dev": true,
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/vscode-jsonrpc": {
"version": "8.2.0",
"resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/vscode-languageclient": {
"version": "9.0.1",
"resolved": "https://registry.npmmirror.com/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz",
"integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==",
"license": "MIT",
"dependencies": {
"minimatch": "^5.1.0",
"semver": "^7.3.7",
"vscode-languageserver-protocol": "3.17.5"
},
"engines": {
"vscode": "^1.82.0"
}
},
"node_modules/vscode-languageclient/node_modules/minimatch": {
"version": "5.1.7",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.7.tgz",
"integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/vscode-languageserver-protocol": {
"version": "3.17.5",
"resolved": "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
"license": "MIT",
"dependencies": {
"vscode-jsonrpc": "8.2.0",
"vscode-languageserver-types": "3.17.5"
}
},
"node_modules/vscode-languageserver-types": {
"version": "3.17.5",
"resolved": "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
"license": "MIT"
}
}
}
+62
View File
@@ -0,0 +1,62 @@
{
"name": "fig-vscode",
"displayName": "Fig Language",
"description": "VSCode extension for Fig language with syntax highlighting and lsp support",
"version": "0.5.0",
"publisher": "PuqiAR",
"engines": {
"vscode": "^1.108.0"
},
"categories": [
"Programming Languages"
],
"repository": {
"url": "https://github.com/PuqiAR/Fig"
},
"activationEvents": [
"onLanguage:fig"
],
"main": "./out/extension.js",
"contributes": {
"languages": [
{
"id": "fig",
"aliases": [
"Fig",
"fig"
],
"extensions": [
".fig"
],
"configuration": "./language-configuration.json",
"icon": {
"light": "./images/Logo.svg",
"dark": "./images/LogoDark.svg"
}
}
],
"grammars": [
{
"language": "fig",
"scopeName": "source.fig",
"path": "./syntaxes/fig.tmLanguage.json"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile",
"test": "node ./out/test/runTest.js"
},
"dependencies": {
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "^20.6.0",
"@types/vscode": "^1.108.0",
"typescript": "^5.9.3"
}
}
+44
View File
@@ -0,0 +1,44 @@
import * as path from 'path';
import { ExtensionContext, workspace } from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions
} from 'vscode-languageclient/node';
let client: LanguageClient;
export function activate(context: ExtensionContext) {
const exeName = process.platform === 'win32' ? 'Fig-LSP.exe' : 'Fig-LSP';
// 获取插件安装后的绝对沙箱路径
const serverCommand = context.asAbsolutePath(path.join('bin', exeName));
const serverOptions: ServerOptions = {
run: { command: serverCommand, args: [] },
debug: { command: serverCommand, args: [] }
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'fig' }],
synchronize: {
fileEvents: workspace.createFileSystemWatcher('**/*.fig')
}
};
client = new LanguageClient(
'figLanguageServer',
'Fig Language Server',
serverOptions,
clientOptions
);
client.start();
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
}
@@ -0,0 +1,25 @@
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"" },
{ "open": "'", "close": "'" }
],
"surroundingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"" },
{ "open": "'", "close": "'" }
]
}
+15
View File
@@ -0,0 +1,15 @@
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});
+114
View File
@@ -0,0 +1,114 @@
{
"name": "Fig",
"scopeName": "source.fig",
"patterns": [
{ "include": "#comments" },
{ "include": "#strings" },
{ "include": "#numbers" },
{ "include": "#keywords" },
{ "include": "#operators" },
{ "include": "#functions" },
{ "include": "#identifiers" }
],
"repository": {
"comments": {
"patterns": [
{ "name": "comment.line.double-slash.fig", "match": "//.*$" },
{ "name": "comment.block.fig", "begin": "/\\*", "end": "\\*/" }
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.fig",
"begin": "\"\"\"",
"end": "\"\"\"",
"patterns": [{ "match": ".", "name": "string.content.fig" }]
},
{
"name": "string.quoted.double.fig",
"begin": "\"",
"end": "\"",
"patterns": [
{ "match": "\\\\.", "name": "constant.character.escape.fig" }
]
},
{
"name": "string.quoted.raw.fig",
"begin": "r\"",
"end": "\"",
"patterns": [{ "match": ".", "name": "string.content.fig" }]
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.float.fig",
"match": "\\d*\\.\\d+([eE][+-]?\\d+)?"
},
{
"name": "constant.numeric.integer.fig",
"match": "\\d+([eE][+-]?\\d+)?"
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.fig",
"match": "\\b(and|or|not|import|func|var|const|final|while|for|if|else|new|struct|interface|impl|public|return|break|continue|try|catch|throw|is|as)\\b"
},
{ "name": "constant.language.fig", "match": "\\b(true|false|null)\\b" }
]
},
"operators": {
"patterns": [
{
"name": "keyword.operator.arithmetic.fig",
"match": "(\\+|\\-|\\*|/|%|\\*\\*)"
},
{
"name": "keyword.operator.assignment.fig",
"match": "(=|\\+=|\\-=|\\*=|/=|%=|\\^=|:=)"
},
{
"name": "keyword.operator.logical.fig",
"match": "(&&|\\|\\||\\b(and|or|not)\\b)"
},
{
"name": "keyword.operator.comparison.fig",
"match": "(==|!=|<=|>=|<|>)"
},
{
"name": "punctuation.separator.fig",
"match": "[\\(\\)\\[\\]\\{\\},;:.]"
},
{
"name": "keyword.operator.other.fig",
"match": "(\\+\\+|--|->|=>|<<|>>|\\^|&|\\||~)"
}
]
},
"functions": {
"patterns": [
{
"name": "entity.name.function.fig",
"begin": "\\bfunc\\s+([a-zA-Z_][a-zA-Z0-9_]*)",
"beginCaptures": {
"1": { "name": "entity.name.function.fig" }
},
"end": "(?=;)"
}
]
},
"identifiers": {
"patterns": [
{
"name": "variable.other.fig",
"match": "(?!\\bfunc\\b)[a-zA-Z_][a-zA-Z0-9_]*"
}
]
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"outDir": "out",
"lib": [
"ES2022"
],
"sourceMap": true,
"rootDir": "src",
"strict": true, /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
}
+44
View File
@@ -0,0 +1,44 @@
# Welcome to your VS Code Extension
## What's in the folder
* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your extension and command.
* The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesnt yet need to load the plugin.
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
* The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
## Get up and running straight away
* Press `F5` to open a new window with your extension loaded.
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
* Find output from your extension in the debug console.
## Make changes
* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
## Explore the API
* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
## Run tests
* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner)
* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered.
* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A`
* See the output of the test result in the Test Results view.
* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder.
* The provided test runner will only consider files matching the name pattern `**.test.ts`.
* You can create folders inside the `test` folder to structure your tests any way you want.
## Go further
* [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns.
* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace.
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
* Integrate to the [report issue](https://code.visualstudio.com/api/get-started/wrapping-up#issue-reporting) flow to get issue and feature requests reported by users.
+52 -14
View File
@@ -1,18 +1,56 @@
MIT License MIT License (Fig)
Copyright (c) 2026 PuqiAR Copyright (c) 2026 PuqiAR <im@puqiar.top>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and Permission is hereby granted, free of charge, to any person obtaining a copy
associated documentation files (the "Software"), to deal in the Software without restriction, including of this software and associated documentation files (the "Software"), to deal
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell in the Software without restriction, including without limitation the rights
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
following conditions: copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial 1. The above copyright notice and this permission notice shall be included in all
portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 2. This project includes code from the following projects with their respective licenses:
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO - magic_enum (MIT License)
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER Copyright (c) 2019 - 2024 Daniil Goncharov
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE. Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- json (MIT LICENSE) (for LSP Server JSON-RPC)
Copyright (c) 2013-2026 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" fill="#F28C28"/>
<path d="M135 100 H395 V195 H230 V265 H370 V360 H230 V430 H135 V100 Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 244 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M135 100 H395 V195 H230 V265 H370 V360 H230 V430 H135 V100 Z" fill="#F28C28"/>
</svg>

After

Width:  |  Height:  |  Size: 196 B

+30
View File
@@ -1,2 +1,32 @@
# Fig # Fig
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./Logo/LogoDark.svg">
<img src="./Logo/Logo.svg" alt="Fig Logo" width="200">
</picture>
> **🔔 Main Repository: https://git.fig-lang.cn/PuqiAR/Fig**
> *GitHub is only a mirror. Please submit issues and PRs to the main repository.*
[English](./README.md) | [中文](./README_zh-CN.md)
![License](https://img.shields.io/badge/license-MIT-blue)
![Status](https://img.shields.io/badge/status-0.6.0--alpha-yellow)
![Rust](https://img.shields.io/badge/Rust-100%25-orange)
![cargo](https://img.shields.io/badge/cargo-build-green)
![Platform](https://img.shields.io/badge/Windows%20·%20Linux%20·%20macOS-lightgrey)
**Fig** is a programming language that blends dynamic typing with optional static type annotations, built on reference-based value semantics.
> **v0.6.0 — Rust rewrite in progress.**
## Progress
```
Lexer ████████████████████ 100% (43 tests, all pass)
Parser ░░░░░░░░░░░░░░░░░░░░ 0%
Analyzer ░░░░░░░░░░░░░░░░░░░░ 0%
Compiler ░░░░░░░░░░░░░░░░░░░░ 0%
VM ░░░░░░░░░░░░░░░░░░░░ 0%
LSP ░░░░░░░░░░░░░░░░░░░░ 0%
```
+32
View File
@@ -0,0 +1,32 @@
# Fig
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./Logo/LogoDark.svg">
<img src="./Logo/Logo.svg" alt="Fig Logo" width="200">
</picture>
> **🔔 主仓库:https://git.fig-lang.cn/PuqiAR/Fig**
> *GitHub 仅为镜像,Issue 与 PR 请提交至主仓库*
[English](./README.md) | [中文](./README_zh-CN.md)
![License](https://img.shields.io/badge/license-MIT-blue)
![Status](https://img.shields.io/badge/status-0.6.0--alpha-yellow)
![Rust](https://img.shields.io/badge/Rust-100%25-orange)
![cargo](https://img.shields.io/badge/cargo-构建-green)
![Platform](https://img.shields.io/badge/Windows%20·%20Linux%20·%20macOS-lightgrey)
**Fig** 是一门动态类型与静态类型注解混合的编程语言,采用基于引用的值语义。
> **v0.6.0 — Rust 重写进行中。**
## 进度
```
Lexer ████████████████████ 100% (43 测试, 全部通过)
Parser ░░░░░░░░░░░░░░░░░░░░ 0%
Analyzer ░░░░░░░░░░░░░░░░░░░░ 0%
Compiler ░░░░░░░░░░░░░░░░░░░░ 0%
VM ░░░░░░░░░░░░░░░░░░░░ 0%
LSP ░░░░░░░░░░░░░░░░░░░░ 0%
```
+63
View File
@@ -0,0 +1,63 @@
use std::process::Command;
fn main() {
let hash = Command::new("git")
.args(["rev-parse", "--short=7", "HEAD"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.unwrap_or_default()
.trim()
.to_string();
println!("cargo:rustc-env=GIT_HASH={}", hash);
println!("cargo:rustc-env=BUILD_TIME={}", chrono_build_time());
// 编译器 IDmacOS 上用 clang/LLVMLinux 上用 GCCWindows 上用 MSVC
#[cfg(target_os = "macos")]
{ println!("cargo:rustc-env=FIG_COMPILER_ID=LLVM"); }
#[cfg(target_os = "linux")]
{ println!("cargo:rustc-env=FIG_COMPILER_ID=GCC"); }
#[cfg(target_os = "windows")]
{ println!("cargo:rustc-env=FIG_COMPILER_ID=MSVC"); }
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{ println!("cargo:rustc-env=FIG_COMPILER_ID=unknown"); }
}
// chrono 还没引入,先用简单方式
fn chrono_build_time() -> String {
// RFC 3339 without nanoseconds: "2026-07-23T13:14:00+08:00"
// 但纯 std 拿不到时区,用环境变量 SOURCE_DATE_EPOCH 或 UTC
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let secs = now % 86400;
let days = now / 86400;
// 简单计算 UTC 时间,避免引入 chrono
let hour = (secs / 3600) % 24;
let min = (secs / 60) % 60;
let sec = secs % 60;
// 粗略日期计算(从 1970-01-01 开始)
let (y, mo, d) = civil_from_days(days as i64);
format!("{y:04}-{mo:02}-{d:02} {hour:02}:{min:02}:{sec:02} UTC")
}
// 从 Unix epoch 天数反算年月日
fn civil_from_days(days: i64) -> (i64, u32, u32) {
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
(y, m, d)
}
-3
View File
@@ -1,3 +0,0 @@
-std=c++2b
-static
-stdlib=libc++

Before

Width:  |  Height:  |  Size: 633 KiB

After

Width:  |  Height:  |  Size: 633 KiB

-33
View File
@@ -1,33 +0,0 @@
FROM ubuntu:24.04
# 1. 设置镜像源(可选,用于加速)
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
# 2. 一次性安装所有依赖(工具链、库、CI工具)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
wget gnupg ca-certificates \
clang-19 lld-19 libc++-19-dev libc++abi-19-dev \
mingw-w64 g++-mingw-w64 \
git tar curl \
python3 python3-pip python3.12-venv libpython3.12 \
&& rm -rf /var/lib/apt/lists/* && \
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-19 100 && \
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-19 100 && \
ln -sf /usr/bin/ld.lld-19 /usr/bin/ld.lld
# 3. 安装xmake
RUN wget -O /usr/local/bin/xmake \
https://git.fig-lang.cn/PuqiAR/xmake-binary-copy/raw/commit/989d1f2dabb0bc8d5981a5f900c2cf7c2ac78ee4/xmake-bundle-v3.0.5.linux.x86_64 && \
chmod +x /usr/local/bin/xmake
# 4. 创建非root用户
RUN useradd -m -s /bin/bash builder
USER builder
WORKDIR /home/builder
RUN xmake --version | head -1 && \
clang++ --version | head -1 && \
git --version && \
echo "✅ 核心工具就绪"
-365
View File
@@ -1,365 +0,0 @@
name: Release Build
on:
push:
tags: ["*"]
workflow_dispatch:
inputs:
version:
description: "版本号 (例如: v1.0.0)"
required: true
default: "dev-build"
jobs:
build-linux-x64:
runs-on: ubuntu
container:
image: git.fig-lang.cn/puqiar/fig-ci:base-latest
options: --network=host
steps:
- name: 验证构建环境
run: |
echo "=== 环境验证开始 ==="
xmake --version
clang++ --version | head -1
echo "=== 环境验证通过 ==="
- name: 检出代码
run: |
git clone https://git.fig-lang.cn/${{ github.repository }} .
git checkout ${{ github.ref }}
- name: 设置版本和提交信息
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
else
VERSION="${{ github.ref_name }}"
fi
echo "构建版本: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV
# 拿提交消息
COMMIT_MSG=$(git log -3 --pretty=%B)
echo "COMMIT_MSG<<EOF" >> $GITHUB_ENV
echo "$COMMIT_MSG" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: 构建项目 (Linux)
run: |
echo "开始构建Linux版本..."
xmake f -p linux -a x86_64 -m release -y
xmake build -j$(nproc) Fig
echo "Linux构建成功。"
# 🔧 新增:构建Linux平台安装器
- name: 构建Linux安装器
run: |
echo "开始构建Linux安装器..."
cd Installer/ConsoleInstaller
python3 -m venv venv
. venv/bin/activate
pip config set global.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
# 安装依赖并构建
pip install --upgrade pip
pip install -r requirements.txt
python3 -m PyInstaller -F -n FigSetup-Linux --distpath ./dist/linux main.py
echo "Linux安装器构建完成"
- name: 打包Linux发布文件
run: |
VERSION="${{ env.VERSION }}"
PACKAGE_NAME="Fig-${VERSION}-linux-x86_64"
mkdir -p "${PACKAGE_NAME}"
cp build/linux/x86_64/release/Fig "${PACKAGE_NAME}/"
if [ -d "src/Module/Library" ]; then
cp -r src/Module/Library "${PACKAGE_NAME}/"
echo "已包含Library目录。"
fi
tar -czf "${PACKAGE_NAME}.tar.gz" "${PACKAGE_NAME}"
sha256sum "${PACKAGE_NAME}.tar.gz" > "${PACKAGE_NAME}.sha256"
echo "Linux打包完成: ${PACKAGE_NAME}.tar.gz"
- name: 发布Linux版本到Gitea
env:
GITEA_TOKEN: ${{ secrets.CI_TOKEN }}
run: |
VERSION="${{ env.VERSION }}"
if [ -z "$VERSION" ]; then
VERSION="${{ github.ref_name }}"
fi
COMMIT_MSG="${{ env.COMMIT_MSG }}"
API="https://git.fig-lang.cn/api/v1/repos/${{ github.repository }}"
echo "正在检查版本 $VERSION 的发布状态..."
# 准备 JSON 数据
VERSION="$VERSION" COMMIT_MSG="$COMMIT_MSG" python3 -c "import json, os; print(json.dumps({'tag_name': os.environ.get('VERSION', ''), 'name': 'Fig ' + os.environ.get('VERSION', ''), 'body': os.environ.get('COMMIT_MSG', ''), 'draft': False, 'prerelease': False}))" > release_body.json
# 1. 尝试获取已有发布
RESPONSE_TAG=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/releases/tags/$VERSION" 2>/dev/null || echo '{"id":0}')
RELEASE_ID=$(echo "$RESPONSE_TAG" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "0" ]; then
echo "✅ 找到已有发布 (ID: $RELEASE_ID),正在更新说明..."
curl -sS -X PATCH -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d @release_body.json \
"$API/releases/$RELEASE_ID" > /dev/null
else
echo "未找到已有发布,准备创建新发布..."
RESPONSE=$(curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d @release_body.json \
"$API/releases" 2>/dev/null || echo '{"id":0}')
RELEASE_ID=$(echo "$RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "0" ]; then
# 再次尝试获取,防止并发冲突
RESPONSE_TAG=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/releases/tags/$VERSION" 2>/dev/null || echo '{"id":0}')
RELEASE_ID=$(echo "$RESPONSE_TAG" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
fi
fi
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "0" ]; then
echo "❌ 错误:无法获取或创建发布 ID"
exit 1
fi
echo "✅ 使用发布 ID: $RELEASE_ID 进行上传"
# 上传资产
PACKAGE_ZIP="Fig-$VERSION-linux-x86_64.tar.gz"
PACKAGE_SHA="Fig-$VERSION-linux-x86_64.sha256"
echo "正在上传 $PACKAGE_ZIP ..."
curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$PACKAGE_ZIP" \
"$API/releases/$RELEASE_ID/assets?name=$PACKAGE_ZIP" > /dev/null
echo "正在上传 $PACKAGE_SHA ..."
curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: text/plain" \
--data-binary "@$PACKAGE_SHA" \
"$API/releases/$RELEASE_ID/assets?name=$PACKAGE_SHA" > /dev/null
# 🔧 上传Linux安装器
INSTALLER="Installer/ConsoleInstaller/dist/linux/FigSetup-Linux"
if [ -f "$INSTALLER" ]; then
echo "正在上传Linux安装器..."
curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$INSTALLER" \
"$API/releases/$RELEASE_ID/assets?name=FigSetup-Linux" > /dev/null
fi
echo "✅ Linux版本发布完成!"
build-windows-x64:
runs-on: windows
steps:
- name: 验证Windows工具链
run: |
Set-ExecutionPolicy Bypass -Scope Process -Force
# 检查 xmake
if (Get-Command xmake -ErrorAction SilentlyContinue) {
Write-Host '✅ xmake 已安装'
xmake --version
} else { Write-Host '警告: xmake 未找到' }
# 检查 clang++
if (Get-Command clang++ -ErrorAction SilentlyContinue) {
Write-Host '✅ clang++ 已安装'
clang++ --version
} else { Write-Host '警告: clang++ 未找到' }
- name: 检出代码
run: |
$env:Path = "C:\Program Files\Git\cmd;$env:Path"
git clone https://git.fig-lang.cn/$env:GITHUB_REPOSITORY .
git checkout ${{ github.ref }}
- name: 设置版本和提交信息
run: |
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
if ($env:GITHUB_EVENT_NAME -eq 'workflow_dispatch') {
$VERSION = $env:INPUT_VERSION
if (-not $VERSION) { $VERSION = $env:VERSION_INPUT }
if (-not $VERSION) { $VERSION = "dev-build" }
} else {
$VERSION = "${{ github.ref_name }}"
}
Write-Host "构建版本: $VERSION"
# 确保无 BOM 的 UTF8
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "VERSION=$VERSION`n")
# 提交消息
$COMMIT_MSG = git log -3 --pretty=%B
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "COMMIT_MSG<<EOF`n$COMMIT_MSG`nEOF`n")
- name: 构建项目 (Windows Native)
run: |
xmake f -p windows -a x86_64 -m release -y
xmake build -j $env:NUMBER_OF_PROCESSORS Fig
Write-Host 'Windows构建成功。'
# 🔧 新增:构建Windows平台安装器
- name: 构建Windows安装器
run: |
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
Write-Host "开始构建Windows安装器..."
cd Installer\ConsoleInstaller
pip install -r requirements.txt
pyinstaller -F -i logo.ico -n FigSetup --distpath .\dist\windows main.py
Write-Host "Windows安装器构建完成"
- name: 打包Windows发布文件
run: |
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$VERSION = $env:VERSION
if (-not $VERSION) {
$VERSION = "${{ github.ref_name }}"
Write-Host "⚠️ 警告:从环境变量获取 VERSION 失败,回退到 github.ref_name: $VERSION"
}
Write-Host "打包版本: $VERSION"
$PACKAGE_NAME = "Fig-${VERSION}-windows-x86_64"
Write-Host "打包名称: $PACKAGE_NAME"
New-Item -ItemType Directory -Force -Path $PACKAGE_NAME
# 查找可执行文件
if (Test-Path 'build\windows\x86_64\release\Fig.exe') {
Copy-Item 'build\windows\x86_64\release\Fig.exe' $PACKAGE_NAME\
} elseif (Test-Path 'build\windows\x86_64\release\Fig') {
Copy-Item 'build\windows\x86_64\release\Fig' $PACKAGE_NAME\Fig.exe
} else {
Write-Host '错误:未找到构建输出文件'
exit 1
}
if (Test-Path 'src\Module\Library') {
Copy-Item -Recurse 'src\Module\Library' $PACKAGE_NAME\
}
# 压缩文件
$ZIP_NAME = "$PACKAGE_NAME.zip"
Compress-Archive -Path $PACKAGE_NAME -DestinationPath $ZIP_NAME
# 生成校验文件
$Hash = Get-FileHash $ZIP_NAME -Algorithm SHA256
"$($Hash.Hash) $ZIP_NAME" | Out-File "$PACKAGE_NAME.sha256" -Encoding UTF8
Write-Host "Windows打包完成: $ZIP_NAME"
- name: 发布Windows版本到Gitea
env:
GITEA_TOKEN: ${{ secrets.CI_TOKEN }}
run: |
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$VERSION = $env:VERSION
$COMMIT_MSG = $env:COMMIT_MSG
if (-not $VERSION) {
$VERSION = "${{ github.ref_name }}"
Write-Host "⚠️ 警告:从环境变量获取 VERSION 失败,回退到 github.ref_name: $VERSION"
}
if (-not $VERSION) {
Write-Host "❌ 错误:版本号仍然为空,无法创建发布。"
exit 1
}
$REPO = $env:GITHUB_REPOSITORY
$API = "https://git.fig-lang.cn/api/v1/repos/$REPO"
$TOKEN = $env:GITEA_TOKEN
$HEADERS = @{
Authorization = "token $TOKEN"
'Content-Type' = 'application/json'
}
$ZIP_FILE = "Fig-$VERSION-windows-x86_64.zip"
$HASH_FILE = "Fig-$VERSION-windows-x86_64.sha256"
$INSTALLER_PATH = "Installer\ConsoleInstaller\dist\windows\FigSetup.exe"
if (-not (Test-Path $ZIP_FILE)) {
Write-Host "❌ 错误:找不到ZIP文件 $ZIP_FILE"
exit 1
}
$CREATE_BODY = @{
tag_name = $VERSION
name = "Fig $VERSION"
body = $COMMIT_MSG
draft = $false
prerelease = $false
} | ConvertTo-Json -Compress
Write-Host "正在检查版本 $VERSION 的发布状态..."
$RELEASE_ID = $null
try {
$EXISTING = Invoke-RestMethod -Uri "$API/releases/tags/$VERSION" -Headers $HEADERS -ErrorAction Stop
if ($EXISTING -and $EXISTING.id) {
$RELEASE_ID = $EXISTING.id
Write-Host "✅ 找到已有发布 (ID: $RELEASE_ID),正在更新..."
Invoke-RestMethod -Method Patch -Uri "$API/releases/$RELEASE_ID" -Headers $HEADERS -Body $CREATE_BODY -ErrorAction Stop | Out-Null
}
} catch {
Write-Host "未找到已有发布,准备创建新发布..."
}
if (-not $RELEASE_ID) {
try {
Write-Host "正在创建新发布: $VERSION ..."
$RESPONSE = Invoke-RestMethod -Method Post -Uri "$API/releases" -Headers $HEADERS -Body $CREATE_BODY -ErrorAction Stop
$RELEASE_ID = $RESPONSE.id
Write-Host "✅ 发布创建成功 (ID: $RELEASE_ID)"
} catch {
$err = $_.Exception.Message
Write-Host "❌ 创建发布失败: $err"
# 最后一次尝试:再次尝试按标签获取,防止由于并发导致的冲突
try {
$RETRY = Invoke-RestMethod -Uri "$API/releases/tags/$VERSION" -Headers $HEADERS -ErrorAction Stop
$RELEASE_ID = $RETRY.id
Write-Host "✅ 重试获取发布成功 (ID: $RELEASE_ID)"
} catch {
Write-Host "❌ 无法获取或创建发布 ID"
exit 1
}
}
}
# 上传资产
Write-Host "正在上传文件..."
$ASSETS = @(
@{ Name = $ZIP_FILE; Path = $ZIP_FILE; ContentType = "application/octet-stream" },
@{ Name = $HASH_FILE; Path = $HASH_FILE; ContentType = "text/plain" }
)
if (Test-Path $INSTALLER_PATH) {
$ASSETS += @{ Name = "FigSetup.exe"; Path = $INSTALLER_PATH; ContentType = "application/octet-stream" }
}
foreach ($asset in $ASSETS) {
Write-Host "正在上传 $($asset.Name) ..."
try {
# 如果资产已存在,Gitea 可能会报错,这里简单处理
Invoke-RestMethod -Method Post -Uri "$API/releases/$RELEASE_ID/assets?name=$($asset.Name)" `
-Headers @{ Authorization = "token $TOKEN"; 'Content-Type' = $asset.ContentType } `
-InFile $asset.Path -ErrorAction SilentlyContinue | Out-Null
} catch {
Write-Host "⚠️ 上传 $($asset.Name) 失败,可能已存在。"
}
}
Write-Host "✅ Windows版本发布完成!"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-44
View File
@@ -1,44 +0,0 @@
// __ __ _ ______ _____
// | \/ | (_) | ____| / ____|_ _
// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
// __/ | https://github.com/Neargye/magic_enum
// |___/ version 0.9.7
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2019 - 2024 Daniil Goncharov <neargye@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef NEARGYE_MAGIC_ENUM_ALL_HPP
#define NEARGYE_MAGIC_ENUM_ALL_HPP
#include "magic_enum.hpp"
#include "magic_enum_containers.hpp"
#include "magic_enum_flags.hpp"
#include "magic_enum_format.hpp"
#include "magic_enum_fuse.hpp"
#include "magic_enum_iostream.hpp"
#include "magic_enum_switch.hpp"
#include "magic_enum_utility.hpp"
#endif // NEARGYE_MAGIC_ENUM_ALL_HPP
File diff suppressed because it is too large Load Diff
-222
View File
@@ -1,222 +0,0 @@
// __ __ _ ______ _____
// | \/ | (_) | ____| / ____|_ _
// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
// __/ | https://github.com/Neargye/magic_enum
// |___/ version 0.9.7
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2019 - 2024 Daniil Goncharov <neargye@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef NEARGYE_MAGIC_ENUM_FLAGS_HPP
#define NEARGYE_MAGIC_ENUM_FLAGS_HPP
#include "magic_enum.hpp"
#if defined(__clang__)
# pragma clang diagnostic push
#elif defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'.
#elif defined(_MSC_VER)
# pragma warning(push)
#endif
namespace magic_enum {
namespace detail {
template <typename E, enum_subtype S, typename U = std::underlying_type_t<E>>
constexpr U values_ors() noexcept {
static_assert(S == enum_subtype::flags, "magic_enum::detail::values_ors requires valid subtype.");
auto ors = U{0};
for (std::size_t i = 0; i < count_v<E, S>; ++i) {
ors |= static_cast<U>(values_v<E, S>[i]);
}
return ors;
}
} // namespace magic_enum::detail
// Returns name from enum-flags value.
// If enum-flags value does not have name or value out of range, returns empty string.
template <typename E>
[[nodiscard]] auto enum_flags_name(E value, char_type sep = static_cast<char_type>('|')) -> detail::enable_if_t<E, string> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
constexpr auto S = detail::enum_subtype::flags;
static_assert(detail::is_reflected_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
string name;
auto check_value = U{0};
for (std::size_t i = 0; i < detail::count_v<D, S>; ++i) {
if (const auto v = static_cast<U>(enum_value<D, S>(i)); (static_cast<U>(value) & v) != 0) {
if (const auto n = detail::names_v<D, S>[i]; !n.empty()) {
check_value |= v;
if (!name.empty()) {
name.append(1, sep);
}
name.append(n.data(), n.size());
} else {
return {}; // Value out of range.
}
}
}
if (check_value != 0 && check_value == static_cast<U>(value)) {
return name;
}
return {}; // Invalid value or out of range.
}
// Obtains enum-flags value from integer value.
// Returns optional with enum-flags value.
template <typename E>
[[nodiscard]] constexpr auto enum_flags_cast(underlying_type_t<E> value) noexcept -> detail::enable_if_t<E, optional<std::decay_t<E>>> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
constexpr auto S = detail::enum_subtype::flags;
static_assert(detail::is_reflected_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
if constexpr (detail::count_v<D, S> == 0) {
static_cast<void>(value);
return {}; // Empty enum.
} else {
if constexpr (detail::is_sparse_v<D, S>) {
auto check_value = U{0};
for (std::size_t i = 0; i < detail::count_v<D, S>; ++i) {
if (const auto v = static_cast<U>(enum_value<D, S>(i)); (value & v) != 0) {
check_value |= v;
}
}
if (check_value != 0 && check_value == value) {
return static_cast<D>(value);
}
} else {
constexpr auto min = detail::min_v<D, S>;
constexpr auto max = detail::values_ors<D, S>();
if (value >= min && value <= max) {
return static_cast<D>(value);
}
}
return {}; // Invalid value or out of range.
}
}
// Obtains enum-flags value from name.
// Returns optional with enum-flags value.
template <typename E, typename BinaryPredicate = std::equal_to<>>
[[nodiscard]] constexpr auto enum_flags_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable<BinaryPredicate>()) -> detail::enable_if_t<E, optional<std::decay_t<E>>, BinaryPredicate> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
constexpr auto S = detail::enum_subtype::flags;
static_assert(detail::is_reflected_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
if constexpr (detail::count_v<D, S> == 0) {
static_cast<void>(value);
return {}; // Empty enum.
} else {
auto result = U{0};
while (!value.empty()) {
const auto d = detail::find(value, '|');
const auto s = (d == string_view::npos) ? value : value.substr(0, d);
auto f = U{0};
for (std::size_t i = 0; i < detail::count_v<D, S>; ++i) {
if (detail::cmp_equal(s, detail::names_v<D, S>[i], p)) {
f = static_cast<U>(enum_value<D, S>(i));
result |= f;
break;
}
}
if (f == U{0}) {
return {}; // Invalid value or out of range.
}
value.remove_prefix((d == string_view::npos) ? value.size() : d + 1);
}
if (result != U{0}) {
return static_cast<D>(result);
}
return {}; // Invalid value or out of range.
}
}
// Checks whether enum-flags contains value with such value.
template <typename E>
[[nodiscard]] constexpr auto enum_flags_contains(E value) noexcept -> detail::enable_if_t<E, bool> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
return static_cast<bool>(enum_flags_cast<D>(static_cast<U>(value)));
}
// Checks whether enum-flags contains value with such integer value.
template <typename E>
[[nodiscard]] constexpr auto enum_flags_contains(underlying_type_t<E> value) noexcept -> detail::enable_if_t<E, bool> {
using D = std::decay_t<E>;
return static_cast<bool>(enum_flags_cast<D>(value));
}
// Checks whether enum-flags contains enumerator with such name.
template <typename E, typename BinaryPredicate = std::equal_to<>>
[[nodiscard]] constexpr auto enum_flags_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable<BinaryPredicate>()) -> detail::enable_if_t<E, bool, BinaryPredicate> {
using D = std::decay_t<E>;
return static_cast<bool>(enum_flags_cast<D>(value, std::move(p)));
}
// Checks whether `flags set` contains `flag`.
// Note: If `flag` equals 0, it returns false, as 0 is not a flag.
template <typename E>
constexpr auto enum_flags_test(E flags, E flag) noexcept -> detail::enable_if_t<E, bool> {
using U = underlying_type_t<E>;
return static_cast<U>(flag) && ((static_cast<U>(flags) & static_cast<U>(flag)) == static_cast<U>(flag));
}
// Checks whether `lhs flags set` and `rhs flags set` have common flags.
// Note: If `lhs flags set` or `rhs flags set` equals 0, it returns false, as 0 is not a flag, and therfore cannot have any matching flag.
template <typename E>
constexpr auto enum_flags_test_any(E lhs, E rhs) noexcept -> detail::enable_if_t<E, bool> {
using U = underlying_type_t<E>;
return (static_cast<U>(lhs) & static_cast<U>(rhs)) != 0;
}
} // namespace magic_enum
#if defined(__clang__)
# pragma clang diagnostic pop
#elif defined(__GNUC__)
# pragma GCC diagnostic pop
#elif defined(_MSC_VER)
# pragma warning(pop)
#endif
#endif // NEARGYE_MAGIC_ENUM_FLAGS_HPP
-114
View File
@@ -1,114 +0,0 @@
// __ __ _ ______ _____
// | \/ | (_) | ____| / ____|_ _
// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
// __/ | https://github.com/Neargye/magic_enum
// |___/ version 0.9.7
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2019 - 2024 Daniil Goncharov <neargye@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef NEARGYE_MAGIC_ENUM_FORMAT_HPP
#define NEARGYE_MAGIC_ENUM_FORMAT_HPP
#include "magic_enum.hpp"
#include "magic_enum_flags.hpp"
#if !defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT)
# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT 1
# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE
#endif
namespace magic_enum::customize {
// customize enum to enable/disable automatic std::format
template <typename E>
constexpr bool enum_format_enabled() noexcept {
return MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT;
}
} // magic_enum::customize
#if defined(__cpp_lib_format)
#ifndef MAGIC_ENUM_USE_STD_MODULE
#include <format>
#endif
template <typename E>
struct std::formatter<E, std::enable_if_t<std::is_enum_v<std::decay_t<E>> && magic_enum::customize::enum_format_enabled<E>(), char>> : std::formatter<std::string_view, char> {
template <class FormatContext>
auto format(E e, FormatContext& ctx) const {
static_assert(std::is_same_v<char, string_view::value_type>, "formatter requires string_view::value_type type same as char.");
using D = std::decay_t<E>;
if constexpr (magic_enum::detail::supported<D>::value) {
if constexpr (magic_enum::detail::subtype_v<D> == magic_enum::detail::enum_subtype::flags) {
if (const auto name = magic_enum::enum_flags_name<D>(e); !name.empty()) {
return formatter<std::string_view, char>::format(std::string_view{name.data(), name.size()}, ctx);
}
} else {
if (const auto name = magic_enum::enum_name<D>(e); !name.empty()) {
return formatter<std::string_view, char>::format(std::string_view{name.data(), name.size()}, ctx);
}
}
}
return formatter<std::string_view, char>::format(std::to_string(magic_enum::enum_integer<D>(e)), ctx);
}
};
#endif
#if defined(FMT_VERSION)
#include <fmt/format.h>
template <typename E>
struct fmt::formatter<E, std::enable_if_t<std::is_enum_v<std::decay_t<E>> && magic_enum::customize::enum_format_enabled<E>(), char>> : fmt::formatter<std::string_view> {
template <class FormatContext>
auto format(E e, FormatContext& ctx) const {
static_assert(std::is_same_v<char, string_view::value_type>, "formatter requires string_view::value_type type same as char.");
using D = std::decay_t<E>;
if constexpr (magic_enum::detail::supported<D>::value) {
if constexpr (magic_enum::detail::subtype_v<D> == magic_enum::detail::enum_subtype::flags) {
if (const auto name = magic_enum::enum_flags_name<D>(e); !name.empty()) {
return formatter<std::string_view, char>::format(std::string_view{name.data(), name.size()}, ctx);
}
} else {
if (const auto name = magic_enum::enum_name<D>(e); !name.empty()) {
return formatter<std::string_view, char>::format(std::string_view{name.data(), name.size()}, ctx);
}
}
}
return formatter<std::string_view, char>::format(std::to_string(magic_enum::enum_integer<D>(e)), ctx);
}
};
#endif
#if defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE)
# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT
# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE
#endif
#endif // NEARGYE_MAGIC_ENUM_FORMAT_HPP
-89
View File
@@ -1,89 +0,0 @@
// __ __ _ ______ _____
// | \/ | (_) | ____| / ____|_ _
// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
// __/ | https://github.com/Neargye/magic_enum
// |___/ version 0.9.7
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2019 - 2024 Daniil Goncharov <neargye@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef NEARGYE_MAGIC_ENUM_FUSE_HPP
#define NEARGYE_MAGIC_ENUM_FUSE_HPP
#include "magic_enum.hpp"
namespace magic_enum {
namespace detail {
template <typename E>
constexpr optional<std::uintmax_t> fuse_one_enum(optional<std::uintmax_t> hash, E value) noexcept {
if (hash) {
if (const auto index = enum_index(value)) {
return (*hash << log2((enum_count<E>() << 1) - 1)) | *index;
}
}
return {};
}
template <typename E>
constexpr optional<std::uintmax_t> fuse_enum(E value) noexcept {
return fuse_one_enum(0, value);
}
template <typename E, typename... Es>
constexpr optional<std::uintmax_t> fuse_enum(E head, Es... tail) noexcept {
return fuse_one_enum(fuse_enum(tail...), head);
}
template <typename... Es>
constexpr auto typesafe_fuse_enum(Es... values) noexcept {
enum class enum_fuse_t : std::uintmax_t;
const auto fuse = fuse_enum(values...);
if (fuse) {
return optional<enum_fuse_t>{static_cast<enum_fuse_t>(*fuse)};
}
return optional<enum_fuse_t>{};
}
} // namespace magic_enum::detail
// Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements.
template <typename... Es>
[[nodiscard]] constexpr auto enum_fuse(Es... values) noexcept {
static_assert((std::is_enum_v<std::decay_t<Es>> && ...), "magic_enum::enum_fuse requires enum type.");
static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 values.");
static_assert((detail::log2(enum_count<std::decay_t<Es>>() + 1) + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums");
#if defined(MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE)
const auto fuse = detail::fuse_enum<std::decay_t<Es>...>(values...);
#else
const auto fuse = detail::typesafe_fuse_enum<std::decay_t<Es>...>(values...);
#endif
return MAGIC_ENUM_ASSERT(fuse), fuse;
}
} // namespace magic_enum
#endif // NEARGYE_MAGIC_ENUM_FUSE_HPP
@@ -1,117 +0,0 @@
// __ __ _ ______ _____
// | \/ | (_) | ____| / ____|_ _
// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
// __/ | https://github.com/Neargye/magic_enum
// |___/ version 0.9.7
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2019 - 2024 Daniil Goncharov <neargye@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef NEARGYE_MAGIC_ENUM_IOSTREAM_HPP
#define NEARGYE_MAGIC_ENUM_IOSTREAM_HPP
#include "magic_enum.hpp"
#include "magic_enum_flags.hpp"
#ifndef MAGIC_ENUM_USE_STD_MODULE
#include <iosfwd>
#endif
namespace magic_enum {
namespace ostream_operators {
template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, E value) {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
if constexpr (detail::supported<D>::value) {
if constexpr (detail::subtype_v<D> == detail::enum_subtype::flags) {
if (const auto name = enum_flags_name<D>(value); !name.empty()) {
for (const auto c : name) {
os.put(c);
}
return os;
}
} else {
if (const auto name = enum_name<D>(value); !name.empty()) {
for (const auto c : name) {
os.put(c);
}
return os;
}
}
}
return (os << static_cast<U>(value));
}
template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, optional<E> value) {
return value ? (os << *value) : os;
}
} // namespace magic_enum::ostream_operators
namespace istream_operators {
template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
std::basic_istream<Char, Traits>& operator>>(std::basic_istream<Char, Traits>& is, E& value) {
using D = std::decay_t<E>;
std::basic_string<Char, Traits> s;
is >> s;
if constexpr (detail::supported<D>::value) {
if constexpr (detail::subtype_v<D> == detail::enum_subtype::flags) {
if (const auto v = enum_flags_cast<D>(s)) {
value = *v;
} else {
is.setstate(std::basic_ios<Char>::failbit);
}
} else {
if (const auto v = enum_cast<D>(s)) {
value = *v;
} else {
is.setstate(std::basic_ios<Char>::failbit);
}
}
} else {
is.setstate(std::basic_ios<Char>::failbit);
}
return is;
}
} // namespace magic_enum::istream_operators
namespace iostream_operators {
using magic_enum::ostream_operators::operator<<;
using magic_enum::istream_operators::operator>>;
} // namespace magic_enum::iostream_operators
} // namespace magic_enum
#endif // NEARGYE_MAGIC_ENUM_IOSTREAM_HPP
-195
View File
@@ -1,195 +0,0 @@
// __ __ _ ______ _____
// | \/ | (_) | ____| / ____|_ _
// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
// __/ | https://github.com/Neargye/magic_enum
// |___/ version 0.9.7
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2019 - 2024 Daniil Goncharov <neargye@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef NEARGYE_MAGIC_ENUM_SWITCH_HPP
#define NEARGYE_MAGIC_ENUM_SWITCH_HPP
#include "magic_enum.hpp"
namespace magic_enum {
namespace detail {
struct default_result_type {};
template <typename T>
struct identity {
using type = T;
};
struct nonesuch {};
template <typename F, typename V, bool = std::is_invocable_v<F, V>>
struct invoke_result : identity<nonesuch> {};
template <typename F, typename V>
struct invoke_result<F, V, true> : std::invoke_result<F, V> {};
template <typename F, typename V>
using invoke_result_t = typename invoke_result<F, V>::type;
template <typename E, enum_subtype S, typename F, std::size_t... I>
constexpr auto common_invocable(std::index_sequence<I...>) noexcept {
static_assert(std::is_enum_v<E>, "magic_enum::detail::invocable_index requires enum type.");
if constexpr (count_v<E, S> == 0) {
return identity<nonesuch>{};
} else {
return std::common_type<invoke_result_t<F, enum_constant<values_v<E, S>[I]>>...>{};
}
}
template <typename E, enum_subtype S, typename Result, typename F>
constexpr auto result_type() noexcept {
static_assert(std::is_enum_v<E>, "magic_enum::detail::result_type requires enum type.");
constexpr auto seq = std::make_index_sequence<count_v<E, S>>{};
using R = typename decltype(common_invocable<E, S, F>(seq))::type;
if constexpr (std::is_same_v<Result, default_result_type>) {
if constexpr (std::is_same_v<R, nonesuch>) {
return identity<void>{};
} else {
return identity<R>{};
}
} else {
if constexpr (std::is_convertible_v<R, Result>) {
return identity<Result>{};
} else if constexpr (std::is_convertible_v<Result, R>) {
return identity<R>{};
} else {
return identity<nonesuch>{};
}
}
}
template <typename E, enum_subtype S, typename Result, typename F, typename D = std::decay_t<E>, typename R = typename decltype(result_type<D, S, Result, F>())::type>
using result_t = std::enable_if_t<std::is_enum_v<D> && !std::is_same_v<R, nonesuch>, R>;
#if !defined(MAGIC_ENUM_ENABLE_HASH) && !defined(MAGIC_ENUM_ENABLE_HASH_SWITCH)
template <typename T = void>
inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v<T>) { return T{}; };
template <>
inline constexpr auto default_result_type_lambda<void> = []() noexcept {};
template <std::size_t I, std::size_t End, typename R, typename E, enum_subtype S, typename F, typename Def>
constexpr decltype(auto) constexpr_switch_impl(F&& f, E value, Def&& def) {
if constexpr(I < End) {
constexpr auto v = enum_constant<enum_value<E, I, S>()>{};
if (value == v) {
if constexpr (std::is_invocable_r_v<R, F, decltype(v)>) {
return static_cast<R>(std::forward<F>(f)(v));
} else {
return def();
}
} else {
return constexpr_switch_impl<I + 1, End, R, E, S>(std::forward<F>(f), value, std::forward<Def>(def));
}
} else {
return def();
}
}
template <typename R, typename E, enum_subtype S, typename F, typename Def>
constexpr decltype(auto) constexpr_switch(F&& f, E value, Def&& def) {
static_assert(is_enum_v<E>, "magic_enum::detail::constexpr_switch requires enum type.");
if constexpr (count_v<E, S> == 0) {
return def();
} else {
return constexpr_switch_impl<0, count_v<E, S>, R, E, S>(std::forward<F>(f), value, std::forward<Def>(def));
}
}
#endif
} // namespace magic_enum::detail
template <typename Result = detail::default_result_type, typename E, detail::enum_subtype S = detail::subtype_v<E>, typename F, typename R = detail::result_t<E, S, Result, F>>
constexpr decltype(auto) enum_switch(F&& f, E value) {
using D = std::decay_t<E>;
static_assert(std::is_enum_v<D>, "magic_enum::enum_switch requires enum type.");
static_assert(detail::is_reflected_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH)
return detail::constexpr_switch<&detail::values_v<D, S>, detail::case_call_t::value>(
std::forward<F>(f),
value,
detail::default_result_type_lambda<R>);
#else
return detail::constexpr_switch<R, D, S>(
std::forward<F>(f),
value,
detail::default_result_type_lambda<R>);
#endif
}
template <typename Result = detail::default_result_type, detail::enum_subtype S, typename E, typename F, typename R = detail::result_t<E, S, Result, F>>
constexpr decltype(auto) enum_switch(F&& f, E value) {
return enum_switch<Result, E, S>(std::forward<F>(f), value);
}
template <typename Result, typename E, detail::enum_subtype S = detail::subtype_v<E>, typename F, typename R = detail::result_t<E, S, Result, F>>
constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) {
using D = std::decay_t<E>;
static_assert(std::is_enum_v<D>, "magic_enum::enum_switch requires enum type.");
static_assert(detail::is_reflected_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH)
return detail::constexpr_switch<&detail::values_v<D, S>, detail::case_call_t::value>(
std::forward<F>(f),
value,
[&result]() -> R { return std::forward<Result>(result); });
#else
return detail::constexpr_switch<R, D, S>(
std::forward<F>(f),
value,
[&result]() -> R { return std::forward<Result>(result); });
#endif
}
template <typename Result, detail::enum_subtype S, typename E, typename F, typename R = detail::result_t<E, S, Result, F>>
constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) {
return enum_switch<Result, E, S>(std::forward<F>(f), value, std::forward<Result>(result));
}
} // namespace magic_enum
template <>
struct std::common_type<magic_enum::detail::nonesuch, magic_enum::detail::nonesuch> : magic_enum::detail::identity<magic_enum::detail::nonesuch> {};
template <typename T>
struct std::common_type<T, magic_enum::detail::nonesuch> : magic_enum::detail::identity<T> {};
template <typename T>
struct std::common_type<magic_enum::detail::nonesuch, T> : magic_enum::detail::identity<T> {};
#endif // NEARGYE_MAGIC_ENUM_SWITCH_HPP
-138
View File
@@ -1,138 +0,0 @@
// __ __ _ ______ _____
// | \/ | (_) | ____| / ____|_ _
// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
// __/ | https://github.com/Neargye/magic_enum
// |___/ version 0.9.7
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2019 - 2024 Daniil Goncharov <neargye@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef NEARGYE_MAGIC_ENUM_UTILITY_HPP
#define NEARGYE_MAGIC_ENUM_UTILITY_HPP
#include "magic_enum.hpp"
namespace magic_enum {
namespace detail {
template <typename E, enum_subtype S, typename F, std::size_t... I>
constexpr auto for_each(F&& f, std::index_sequence<I...>) {
constexpr bool has_void_return = (std::is_void_v<std::invoke_result_t<F, enum_constant<values_v<E, S>[I]>>> || ...);
constexpr bool all_same_return = (std::is_same_v<std::invoke_result_t<F, enum_constant<values_v<E, S>[0]>>, std::invoke_result_t<F, enum_constant<values_v<E, S>[I]>>> && ...);
if constexpr (has_void_return) {
(f(enum_constant<values_v<E, S>[I]>{}), ...);
} else if constexpr (all_same_return) {
return std::array{f(enum_constant<values_v<E, S>[I]>{})...};
} else {
return std::tuple{f(enum_constant<values_v<E, S>[I]>{})...};
}
}
template <typename E, enum_subtype S, typename F,std::size_t... I>
constexpr bool all_invocable(std::index_sequence<I...>) {
if constexpr (count_v<E, S> == 0) {
return false;
} else {
return (std::is_invocable_v<F, enum_constant<values_v<E, S>[I]>> && ...);
}
}
} // namespace magic_enum::detail
template <typename E, detail::enum_subtype S = detail::subtype_v<E>, typename F, detail::enable_if_t<E, int> = 0>
constexpr auto enum_for_each(F&& f) {
using D = std::decay_t<E>;
static_assert(std::is_enum_v<D>, "magic_enum::enum_for_each requires enum type.");
static_assert(detail::is_reflected_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
constexpr auto sep = std::make_index_sequence<detail::count_v<D, S>>{};
if constexpr (detail::all_invocable<D, S, F>(sep)) {
return detail::for_each<D, S>(std::forward<F>(f), sep);
} else {
static_assert(detail::always_false_v<D>, "magic_enum::enum_for_each requires invocable of all enum value.");
}
}
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_next_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t<E, optional<std::decay_t<E>>> {
using D = std::decay_t<E>;
constexpr std::ptrdiff_t count = detail::count_v<D, S>;
if (const auto i = enum_index<D, S>(value)) {
const std::ptrdiff_t index = (static_cast<std::ptrdiff_t>(*i) + n);
if (index >= 0 && index < count) {
return enum_value<D, S>(static_cast<std::size_t>(index));
}
}
return {};
}
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_next_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t<E, std::decay_t<E>> {
using D = std::decay_t<E>;
constexpr std::ptrdiff_t count = detail::count_v<D, S>;
if (const auto i = enum_index<D, S>(value)) {
const std::ptrdiff_t index = ((((static_cast<std::ptrdiff_t>(*i) + n) % count) + count) % count);
if (index >= 0 && index < count) {
return enum_value<D, S>(static_cast<std::size_t>(index));
}
}
return MAGIC_ENUM_ASSERT(false), value;
}
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_prev_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t<E, optional<std::decay_t<E>>> {
using D = std::decay_t<E>;
constexpr std::ptrdiff_t count = detail::count_v<D, S>;
if (const auto i = enum_index<D, S>(value)) {
const std::ptrdiff_t index = (static_cast<std::ptrdiff_t>(*i) - n);
if (index >= 0 && index < count) {
return enum_value<D, S>(static_cast<std::size_t>(index));
}
}
return {};
}
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_prev_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t<E, std::decay_t<E>> {
using D = std::decay_t<E>;
constexpr std::ptrdiff_t count = detail::count_v<D, S>;
if (const auto i = enum_index<D, S>(value)) {
const std::ptrdiff_t index = ((((static_cast<std::ptrdiff_t>(*i) - n) % count) + count) % count);
if (index >= 0 && index < count) {
return enum_value<D, S>(static_cast<std::size_t>(index));
}
}
return MAGIC_ENUM_ASSERT(false), value;
}
} // namespace magic_enum
#endif // NEARGYE_MAGIC_ENUM_UTILITY_HPP
+48
View File
@@ -0,0 +1,48 @@
/*
src/core/build_info.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use crate::core::colors;
pub struct BuildInfo
{
pub version: &'static str,
pub git_hash: &'static str,
pub build_time: &'static str,
pub platform: String,
}
pub fn get() -> BuildInfo
{
BuildInfo
{
version: env!("CARGO_PKG_VERSION"),
git_hash: env!("GIT_HASH"),
build_time: env!("BUILD_TIME"),
platform: format!(
"{} [{} | {}]",
std::env::consts::OS,
env!("FIG_COMPILER_ID"),
std::env::consts::ARCH
),
}
}
pub fn print_header(sub_system: &str)
{
let info = get();
print!(
"{}{} v{} {}(Build {} {} {}){}\n\n",
colors::BOLD,
sub_system,
info.version,
colors::DIM,
info.git_hash,
info.build_time,
info.platform,
colors::RESET,
);
}
+32
View File
@@ -0,0 +1,32 @@
/*
src/core/colors.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
pub const RESET: &str = "\x1b[0m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const RED: &str = "\x1b[31m";
pub const GREEN: &str = "\x1b[32m";
pub const YELLOW: &str = "\x1b[33m";
pub const CYAN: &str = "\x1b[36m";
pub const BRIGHT_RED: &str = "\x1b[91m";
pub const BRIGHT_CYAN: &str = "\x1b[96m";
pub const GRAY: &str = "\x1b[90m";
pub const LIGHT_GRAY: &str = "\x1b[37m";
pub const ORANGE: &str = "\x1b[38;2;217;119;6m";
pub const PURPLE: &str = "\x1b[38;2;168;85;247m"; // 亮紫色 error 主色
pub const CRITICAL_RED: &str = "\x1b[38;2;239;68;68m"; // critical
pub const ACCENT_BLUE: &str = "\x1b[38;2;59;130;246m";
// 源码行号
pub const LINE_NO: &str = "\x1b[38;2;59;130;246m"; // 蓝,和 error 区分
// 源码文本
pub const LIGHT_BLUE: &str = "\x1b[38;2;96;165;250m";
pub const SOURCE_TEXT: &str = "\x1b[38;2;180;180;180m"; // 浅灰,比 DIM 亮
+18
View File
@@ -0,0 +1,18 @@
/*
src/core/mod.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
pub mod source;
pub mod colors;
pub mod build_info;
#[derive(Debug, Clone, Copy)]
#[allow(non_camel_case_types)]
pub enum Lang
{
en_US,
zh_CN,
}
+124
View File
@@ -0,0 +1,124 @@
/*
src/core/source.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use std::path::PathBuf;
#[derive(Debug, Clone, Copy)]
pub struct SourcePosition
{
pub file_id: usize,
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone, Copy)]
pub struct SourceRange
{
pub file_id: usize,
pub start_line: usize,
pub start_column: usize,
pub end_line: usize,
pub end_column: usize,
}
// SourceFile / SourceManager
pub struct SourceFile
{
pub path: PathBuf,
pub source: String,
line_offsets: Vec<usize>,
}
impl SourceFile
{
pub fn from_string(name: &str, source: String) -> Self
{
let line_offsets = Self::build_line_offsets(&source);
Self { path: PathBuf::from(name), source, line_offsets }
}
pub fn from_path(path: PathBuf) -> Result<Self, std::io::Error>
{
let source = std::fs::read_to_string(&path)?;
let line_offsets = Self::build_line_offsets(&source);
Ok(Self { path, source, line_offsets })
}
pub fn read(path: PathBuf) -> Result<Self, std::io::Error>
{
let source = std::fs::read_to_string(&path)?;
let line_offsets = Self::build_line_offsets(&source);
Ok(Self { path, source, line_offsets })
}
fn build_line_offsets(source: &str) -> Vec<usize>
{
std::iter::once(0)
.chain(
source
.bytes()
.enumerate()
.filter(|(_, b)| *b == b'\n')
.map(|(i, _)| i + 1),
)
.collect()
}
pub fn line_offsets(&self) -> &[usize] { &self.line_offsets }
pub fn offset_to_position(&self, offset: usize) -> (usize, usize)
{
let line = match self.line_offsets.binary_search(&offset) {
Ok(line) => line,
Err(line) => line.saturating_sub(1),
};
let col = offset - self.line_offsets[line] + 1;
(line + 1, col)
}
pub fn get_line(&self, offset: usize) -> &str
{
let (line, _) = self.offset_to_position(offset);
let start = self.line_offsets[line - 1];
let end = self.source[start..]
.find('\n')
.map(|i| start + i)
.unwrap_or(self.source.len());
&self.source[start..end]
}
}
pub struct SourceManager
{
files: Vec<SourceFile>,
}
impl SourceManager
{
pub fn new() -> Self
{
Self { files: Vec::new() }
}
pub fn add_file(&mut self, file: SourceFile) -> usize
{
let id = self.files.len();
self.files.push(file);
id
}
pub fn get(&self, id: usize) -> Option<&SourceFile>
{
self.files.get(id)
}
pub fn source(&self, id: usize) -> Option<&str>
{
self.files.get(id).map(|f| f.source.as_str())
}
}
@@ -0,0 +1,63 @@
/*
src/error/definitions/invalid_escape_sequence.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use crate::error::diagnostic::{Diagnostic, ErrorType, Thrower};
#[derive(Debug)]
pub struct InvalidEscapeSequenceError {
file_id: usize,
start_line: usize,
start_column: usize,
escape_sequence: String,
thrower: Option<Thrower>,
}
impl InvalidEscapeSequenceError {
pub fn new(file_id: usize, start_line: usize, start_column: usize, escape_sequence: String) -> Self {
InvalidEscapeSequenceError {
file_id,
start_line,
start_column,
escape_sequence,
thrower: None,
}
}
pub fn with_thrower(mut self, thrower: Thrower) -> Self {
self.thrower = Some(thrower);
self
}
}
impl Diagnostic for InvalidEscapeSequenceError {
fn error_type(&self) -> ErrorType {
ErrorType::InvalidEscapeSequence
}
fn message(&self, lang: crate::core::Lang) -> String {
match lang {
crate::core::Lang::en_US => format!(
"Invalid escape sequence: {} at line {}, column {}",
self.escape_sequence, self.start_line, self.start_column
),
crate::core::Lang::zh_CN => format!(
"无效的转义序列: {} 在第 {} 行,第 {}",
self.escape_sequence, self.start_line, self.start_column
),
}
}
fn span(&self) -> crate::core::source::SourceRange {
crate::core::source::SourceRange {
file_id: self.file_id,
start_line: self.start_line,
start_column: self.start_column,
end_line: self.start_line,
end_column: self.start_column + self.escape_sequence.len(),
}
}
}
@@ -0,0 +1,72 @@
/*
src/error/definitions/invalid_number_literal.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use crate::core;
use crate::core::source::SourceRange;
use crate::error::diagnostic::{Diagnostic, ErrorType, Thrower};
#[derive(Debug)]
pub struct InvalidNumberLiteralError {
file_id: usize,
start_line: usize,
start_column: usize,
end_column: usize,
thrower: Option<Thrower>,
}
impl InvalidNumberLiteralError {
pub fn new(file_id: usize, start_line: usize, start_column: usize, end_column: usize) -> Self {
InvalidNumberLiteralError {
file_id,
start_line,
start_column,
end_column,
thrower: None,
}
}
pub fn with_thrower(mut self, thrower: Thrower) -> Self {
self.thrower = Some(thrower);
self
}
}
impl Diagnostic for InvalidNumberLiteralError {
fn error_type(&self) -> ErrorType {
ErrorType::InvalidNumberLiteral
}
fn message(&self, lang: core::Lang) -> String {
match lang {
core::Lang::en_US => {
format!(
"Invalid number literal at line {}, column {}",
self.start_line, self.start_column,
)
}
core::Lang::zh_CN => {
format!(
"无效的数字字面量,位于第 {} 行,第 {}",
self.start_line, self.start_column,
)
}
}
}
fn span(&self) -> SourceRange {
SourceRange {
file_id: self.file_id,
start_line: self.start_line,
start_column: self.start_column,
end_line: self.start_line,
end_column: self.end_column,
}
}
fn thrower(&self) -> Option<Thrower> {
self.thrower
}
}
+12
View File
@@ -0,0 +1,12 @@
/*
src/error/definitions/mod.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
pub mod unterminated_string_literal;
pub mod unexpected_character;
pub mod invalid_number_literal;
pub mod invalid_escape_sequence;
pub mod unterminated_comment;
@@ -0,0 +1,90 @@
/*
src/error/definitions/unexpected_character.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use crate::core;
use crate::core::source::SourceRange;
use crate::error::diagnostic::{Diagnostic, ErrorType, Thrower};
#[derive(Debug)]
pub struct UnexpectedCharacterError
{
file_id: usize,
character: char,
line: usize,
column: usize,
thrower: Option<Thrower>,
}
impl UnexpectedCharacterError
{
pub fn new(file_id: usize, character: char, line: usize, column: usize) -> Self
{
UnexpectedCharacterError
{
file_id,
character,
line,
column,
thrower: None,
}
}
pub fn with_thrower(mut self, t: Thrower) -> Self
{
self.thrower = Some(t);
self
}
}
impl Diagnostic for UnexpectedCharacterError
{
fn error_type(&self) -> ErrorType
{
ErrorType::UnexpectedCharacter
}
fn message(&self, lang: crate::core::Lang) -> String
{
let ch = match (self.character, lang) {
(' ', crate::core::Lang::zh_CN) => "<空格>",
(' ', _) => "<space>",
('\n', crate::core::Lang::zh_CN) => "<换行>",
('\n', _) => "<newline>",
('\t', crate::core::Lang::zh_CN) => "<制表符>",
('\t', _) => "<tab>",
('\r', crate::core::Lang::zh_CN) => "<回车>",
('\r', _) => "<carriage return>",
(other, crate::core::Lang::zh_CN) =>
return format!("意外的字符 '{}',位于第 {} 行,第 {}", other, self.line, self.column),
(other, _) =>
return format!("Unexpected character '{}' at line {}, column {}", other, self.line, self.column),
};
match lang {
crate::core::Lang::zh_CN =>
format!("意外的字符 {},位于第 {} 行,第 {}", ch, self.line, self.column),
_ =>
format!("Unexpected character {} at line {}, column {}", ch, self.line, self.column),
}
}
fn span(&self) -> SourceRange
{
SourceRange
{
file_id: self.file_id,
start_line: self.line,
start_column: self.column,
end_line: self.line,
end_column: self.column + 1,
}
}
fn thrower(&self) -> Option<Thrower>
{
self.thrower
}
}
@@ -0,0 +1,83 @@
/*
src/error/definitions/unterminated_comment.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use crate::error::diagnostic::{Diagnostic, ErrorType, Thrower};
use crate::core;
use crate::core::source::SourceRange;
#[derive(Debug)]
pub struct UnterminatedCommentError
{
file_id: usize,
start_line: usize,
start_column: usize,
end_line: usize,
end_column: usize,
thrower: Option<Thrower>,
}
impl UnterminatedCommentError
{
pub fn new(file_id: usize, start_line: usize, start_column: usize, end_line: usize, end_column: usize) -> Self
{
UnterminatedCommentError
{
file_id,
start_line,
start_column,
end_line,
end_column,
thrower: None,
}
}
pub fn with_thrower(mut self, t: Thrower) -> Self
{
self.thrower = Some(t);
self
}
}
impl Diagnostic for UnterminatedCommentError
{
fn error_type(&self) -> ErrorType
{
ErrorType::UnterminatedComment
}
fn message(&self, lang: core::Lang) -> String
{
match lang
{
core::Lang::en_US =>
{
format!("Unterminated block comment starting at line {}, column {}", self.start_line, self.start_column)
}
core::Lang::zh_CN =>
{
format!("未终止的多行注释,起始于第 {} 行,第 {}", self.start_line, self.start_column)
}
}
}
fn span(&self) -> SourceRange
{
SourceRange
{
file_id: self.file_id,
start_line: self.start_line,
start_column: self.start_column,
end_line: self.end_line,
end_column: self.end_column,
}
}
fn thrower(&self) -> Option<Thrower>
{
self.thrower
}
}
@@ -0,0 +1,83 @@
/*
src/error/definitions/unterminated_string_literal.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use crate::error::diagnostic::{Diagnostic, ErrorType, Thrower};
use crate::core;
use crate::core::source::SourceRange;
#[derive(Debug)]
pub struct UnterminatedStringLiteralError
{
file_id: usize,
start_line: usize,
start_column: usize,
end_line: usize,
end_column: usize,
thrower: Option<Thrower>,
}
impl UnterminatedStringLiteralError
{
pub fn new(file_id: usize, start_line: usize, start_column: usize, end_line: usize, end_column: usize) -> Self
{
UnterminatedStringLiteralError
{
file_id,
start_line,
start_column,
end_line,
end_column,
thrower: None,
}
}
pub fn with_thrower(mut self, t: Thrower) -> Self
{
self.thrower = Some(t);
self
}
}
impl Diagnostic for UnterminatedStringLiteralError
{
fn error_type(&self) -> ErrorType
{
ErrorType::UnterminatedStringLiteral
}
fn message(&self, lang: core::Lang) -> String
{
match lang
{
core::Lang::en_US =>
{
format!("Unterminated string literal starting at line {}, column {}", self.start_line, self.start_column)
}
core::Lang::zh_CN =>
{
format!("字符串字面量未终止,起始于第 {} 行,第 {}", self.start_line, self.start_column)
}
}
}
fn span(&self) -> SourceRange
{
SourceRange
{
file_id: self.file_id,
start_line: self.start_line,
start_column: self.start_column,
end_line: self.end_line,
end_column: self.end_column,
}
}
fn thrower(&self) -> Option<Thrower>
{
self.thrower
}
}
+137
View File
@@ -0,0 +1,137 @@
/*
src/error/error.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use crate::core;
use crate::core::source::SourceRange;
use crate::error::hint;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity
{
Warning,
Error,
Critical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorType
{
UnterminatedStringLiteral,
UnexpectedCharacter,
InvalidNumberLiteral,
InvalidEscapeSequence,
UnterminatedComment,
}
impl ErrorType
{
pub fn severity(&self) -> Severity
{
match self
{
ErrorType::UnterminatedStringLiteral => Severity::Error,
ErrorType::UnexpectedCharacter => Severity::Error,
ErrorType::InvalidNumberLiteral => Severity::Error,
ErrorType::InvalidEscapeSequence => Severity::Error,
ErrorType::UnterminatedComment => Severity::Error,
}
}
pub fn title(&self, lang: core::Lang) -> String
{
match self
{
ErrorType::UnterminatedStringLiteral =>
{
match lang
{
core::Lang::en_US => "Unterminated string literal".to_string(),
core::Lang::zh_CN => "字符串字面量未终止".to_string(),
}
}
ErrorType::UnexpectedCharacter =>
{
match lang
{
core::Lang::en_US => "Unexpected character".to_string(),
core::Lang::zh_CN => "意外的字符".to_string(),
}
}
ErrorType::InvalidNumberLiteral =>
{
match lang
{
core::Lang::en_US => "Invalid number literal".to_string(),
core::Lang::zh_CN => "无效的数字字面量".to_string(),
}
}
ErrorType::InvalidEscapeSequence =>
{
match lang
{
core::Lang::en_US => "Invalid escape sequence".to_string(),
core::Lang::zh_CN => "无效的转义序列".to_string(),
}
}
ErrorType::UnterminatedComment =>
{
match lang
{
core::Lang::en_US => "Unterminated comment".to_string(),
core::Lang::zh_CN => "未终止的注释".to_string(),
}
}
}
}
}
pub struct Related
{
pub span: SourceRange,
pub message: String,
}
/// 编译器源码位置,`thrower!()` 宏自动捕获。
#[derive(Debug, Clone, Copy)]
pub struct Thrower
{
pub file: &'static str,
pub line: u32,
}
#[macro_export]
macro_rules! thrower {
() => {
$crate::error::diagnostic::Thrower {
file: file!(),
line: line!(),
}
};
}
pub trait Diagnostic: std::fmt::Debug
{
fn error_type(&self) -> ErrorType;
fn message(&self, lang: core::Lang) -> String;
fn span(&self) -> SourceRange;
fn hints(&self) -> Vec<hint::Hint>
{
vec![]
}
fn related(&self, _lang: core::Lang) -> Vec<Related>
{
vec![]
}
fn thrower(&self) -> Option<Thrower>
{
None
}
}
+13
View File
@@ -0,0 +1,13 @@
/*
src/error/hint.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
pub enum Hint
{
Insertion { line: usize, column: usize, content: String },
Deletion { line: usize, column: usize, length: usize },
Replacement { line: usize, column: usize, length: usize, content: String },
}
+11
View File
@@ -0,0 +1,11 @@
/*
src/error/mod.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
pub mod diagnostic;
pub mod definitions;
pub mod hint;
pub mod report;
+536
View File
@@ -0,0 +1,536 @@
/*
src/error/report.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use std::fmt::Write;
use crate::core;
use crate::core::colors as C;
use crate::core::source::SourceManager;
use crate::error::diagnostic::{Diagnostic, Related, Severity};
use crate::error::hint::Hint;
pub struct Reporter {
lang: core::Lang,
}
impl Reporter {
pub fn new(lang: core::Lang) -> Self {
Self { lang }
}
pub fn report(&self, diag: &dyn Diagnostic, manager: &SourceManager, out: &mut impl Write) {
let et = diag.error_type();
let severity = et.severity();
let span = diag.span();
let file = manager.get(span.file_id);
let (severity_color, severity_label, severity_icon) = match severity {
Severity::Warning => (C::ORANGE, self.txt_warning(), "\u{26A0}"),
Severity::Error => (C::PURPLE, self.txt_error(), "\u{2717}"),
Severity::Critical => (C::CRITICAL_RED, self.txt_critical(), "\u{2620}"),
};
writeln!(
out,
"{} {}{}{}[E{}]{} {}",
severity_icon,
C::BOLD,
severity_color,
severity_label,
et as usize,
C::RESET,
et.title(self.lang),
)
.unwrap();
if let Some(f) = file {
writeln!(
out,
" {}{}-->{} {}:{}:{}",
C::DIM,
C::GRAY,
C::RESET,
f.path.display(),
span.start_line,
span.start_column,
)
.unwrap();
}
self.print_source_context(diag, manager, out);
let hints = diag.hints();
for hint in &hints {
let label = self.fmt_hint(hint);
writeln!(
out,
" {}={}{} {}",
C::DIM,
C::RESET,
self.txt_suggestion(),
label
)
.unwrap();
}
if let Some(t) = diag.thrower() {
writeln!(
out,
" {}@ {}:{}{}",
C::DIM,
t.file, t.line,
C::RESET,
).unwrap();
}
let related = diag.related(self.lang);
if !related.is_empty() {
for rel in &related {
self.print_related(rel, manager, out);
}
}
writeln!(out).unwrap();
}
fn print_source_context(
&self,
diag: &dyn Diagnostic,
manager: &SourceManager,
out: &mut impl Write,
) {
let span = diag.span();
let severity = diag.error_type().severity();
let file = match manager.get(span.file_id) {
Some(f) => f,
None => return,
};
let line_color = match severity {
Severity::Warning => C::ORANGE,
Severity::Error => C::PURPLE,
Severity::Critical => C::CRITICAL_RED,
};
let total_lines = file.line_offsets().len();
let context_lines = 3;
let line_start = span.start_line.saturating_sub(context_lines).max(1);
let line_end = (span.end_line + 2).min(total_lines);
let max_line_width = format!("{}", line_end).len();
writeln!(out, " {}|{}", C::DIM, C::RESET).unwrap();
for line_no in line_start..=line_end {
let offset = file.line_offsets()[line_no - 1];
let line_text = file.get_line(offset);
let highlight = line_no >= span.start_line && line_no <= span.end_line;
if highlight {
writeln!(
out,
" {} {}{}{}{} |{} {}",
padding(max_line_width, line_no),
C::BOLD,
line_color,
line_no,
C::RESET,
C::SOURCE_TEXT,
line_text,
)
.unwrap();
} else {
writeln!(
out,
" {} {}{}{} |{} {}",
padding(max_line_width, line_no),
C::DIM,
line_no,
C::RESET,
C::SOURCE_TEXT,
line_text,
)
.unwrap();
}
if line_no == span.start_line {
let (caret_line, carets_end) =
self.build_caret_line(span, severity, file, line_no, max_line_width);
writeln!(out, "{caret_line}").unwrap();
let msg = diag.message(self.lang);
let msg_color = match severity {
Severity::Warning => C::ORANGE,
Severity::Error => C::PURPLE,
Severity::Critical => C::CRITICAL_RED,
};
let arrow_indent = carets_end.saturating_sub(3);
writeln!(
out,
"{} {}{}╰─{} {} {}",
" ".repeat(arrow_indent),
C::DIM,
C::GRAY,
C::RESET,
msg_color,
msg,
)
.unwrap();
}
if severity == Severity::Critical && line_no == span.end_line {
let text_before = take_chars(line_text, span.end_column.saturating_sub(1));
let dw = display_width(&text_before);
let span_dw = display_width(&take_chars_range(
line_text,
span.start_column.saturating_sub(1),
span.end_column,
));
let ww = char_width('\u{FE4D}').max(1);
let wave_line = "\u{FE4D}".repeat((span_dw + ww - 1) / ww);
let pad = " ".repeat(max_line_width + 5 + dw);
writeln!(
out,
"{} {}{}{}{}",
pad,
C::BRIGHT_RED,
C::BOLD,
wave_line,
C::RESET
)
.unwrap();
}
}
writeln!(out, " {}|{}", C::DIM, C::RESET).unwrap();
}
/// 返回 (caret行字符串, caret结束的显示列位置)
fn print_related(
&self,
rel: &Related,
manager: &SourceManager,
out: &mut impl Write,
)
{
let file = match manager.get(rel.span.file_id) {
Some(f) => f,
None => return,
};
writeln!(
out,
" {}={} \u{1F4A1} {}{}{}: {}",
C::DIM,
C::RESET,
C::LIGHT_BLUE, self.txt_note(), C::RESET,
rel.message,
).unwrap();
writeln!(
out,
" {}{}-->{} {}:{}:{}",
C::DIM,
C::GRAY,
C::RESET,
file.path.display(),
rel.span.start_line,
rel.span.start_column,
).unwrap();
let line_no = rel.span.start_line;
let offset = file.line_offsets()[line_no - 1];
let line_text = file.get_line(offset);
let max_line_width = format!("{}", line_no).len();
writeln!(out, " {}|{}", C::DIM, C::RESET).unwrap();
writeln!(
out,
" {} {}{}{} |{} {}",
padding(max_line_width, line_no),
C::DIM, line_no,
C::RESET,
C::SOURCE_TEXT,
line_text,
).unwrap();
let col_start = rel.span.start_column;
let col_end = rel.span.end_column.min(chars_count(line_text) + 1);
let len = col_end.saturating_sub(col_start);
if len > 0 {
let dw_before = display_width(&take_chars(line_text, col_start.saturating_sub(1)));
let indent = " ".repeat(max_line_width + 5 + dw_before);
let dw_error = display_width(&take_chars_range(line_text, col_start.saturating_sub(1), col_start.saturating_sub(1) + len));
let wave_w = char_width('\u{FE4D}').max(1);
let n_wave = (dw_error + wave_w - 1) / wave_w;
let wave = "\u{FE4D}".repeat(n_wave);
writeln!(out, "{}{}{}{}{}", indent, C::PURPLE, C::BOLD, wave, C::RESET).unwrap();
}
writeln!(out, " {}|{}", C::DIM, C::RESET).unwrap();
writeln!(out).unwrap();
}
fn build_caret_line(
&self,
span: core::source::SourceRange,
severity: Severity,
file: &core::source::SourceFile,
line_no: usize,
max_line_width: usize,
) -> (String, usize) {
let line_offset = file.line_offsets()[line_no - 1];
let line_text = file.get_line(line_offset);
let col_start = if line_no == span.start_line {
span.start_column
} else {
1
};
let col_end = if line_no == span.end_line {
span.end_column.min(chars_count(line_text) + 1)
} else {
chars_count(line_text) + 1
};
let len = col_end.saturating_sub(col_start);
if len == 0 {
return (String::new(), 0);
}
let text_before_caret = take_chars(line_text, col_start.saturating_sub(1));
let dw_before = display_width(&text_before_caret);
let indent_width = max_line_width + 5 + dw_before;
let indent = " ".repeat(indent_width);
let error_text = take_chars_range(
line_text,
col_start.saturating_sub(1),
col_start.saturating_sub(1) + len,
);
let dw_error = display_width(&error_text);
let wave_w = char_width('\u{FE4D}').max(1);
let n_wave = (dw_error + wave_w - 1) / wave_w;
let wave = "\u{FE4D}".repeat(n_wave);
let color = match severity {
Severity::Warning => C::ORANGE,
Severity::Error => C::PURPLE,
Severity::Critical => C::CRITICAL_RED,
};
let line = format!("{}{}{}{}{}", indent, color, C::BOLD, wave, C::RESET);
let end_col = indent_width + n_wave * wave_w;
(line, end_col)
}
pub fn report_all(
&self,
diags: &[&dyn Diagnostic],
manager: &SourceManager,
out: &mut impl Write,
) {
let mut warnings = 0u32;
let mut errors = 0u32;
for diag in diags {
match diag.error_type().severity() {
Severity::Warning => warnings += 1,
_ => errors += 1,
}
self.report(*diag, manager, out);
}
writeln!(
out,
"{}{}{}",
C::BOLD,
self.fmt_summary(errors, warnings),
C::RESET,
)
.unwrap();
}
// i18n
fn txt_warning(&self) -> &str {
match self.lang {
core::Lang::zh_CN => "警告",
core::Lang::en_US => "warning",
}
}
fn txt_error(&self) -> &str {
match self.lang {
core::Lang::zh_CN => "错误",
core::Lang::en_US => "error",
}
}
fn txt_critical(&self) -> &str {
match self.lang {
core::Lang::zh_CN => "严重错误",
core::Lang::en_US => "critical error",
}
}
fn txt_suggestion(&self) -> &str {
match self.lang {
core::Lang::zh_CN => "建议",
core::Lang::en_US => "suggestion",
}
}
fn txt_note(&self) -> &str {
match self.lang {
core::Lang::zh_CN => "提示",
core::Lang::en_US => "note",
}
}
fn fmt_hint(&self, hint: &Hint) -> String {
match hint {
Hint::Insertion { content, .. } => match self.lang {
core::Lang::zh_CN => format!(": 插入 `{}`", content),
core::Lang::en_US => format!(": insert `{}`", content),
},
Hint::Deletion { length, .. } => match self.lang {
core::Lang::zh_CN => format!(": 删除 {} 个字符", length),
core::Lang::en_US => format!(": delete {} character(s)", length),
},
Hint::Replacement { content, .. } => match self.lang {
core::Lang::zh_CN => format!(": 替换为 `{}`", content),
core::Lang::en_US => format!(": replace with `{}`", content),
},
}
}
fn fmt_summary(&self, errors: u32, warnings: u32) -> String {
match self.lang {
core::Lang::zh_CN => format!("{} 个错误,{} 个警告", errors, warnings),
core::Lang::en_US => format!("{} error(s), {} warning(s)", errors, warnings),
}
}
}
// 工具函数
fn padding(max: usize, n: usize) -> String {
let w = format!("{}", n).len();
" ".repeat(max.saturating_sub(w))
}
fn chars_count(s: &str) -> usize {
s.chars().count()
}
fn take_chars(s: &str, n: usize) -> String {
s.chars().take(n).collect()
}
fn take_chars_range(s: &str, start: usize, end: usize) -> String {
s.chars()
.skip(start)
.take(end.saturating_sub(start))
.collect()
}
/// 终端显示宽度:ASCII = 1CJK 等宽字符 = 2Tab = 4
fn char_width(c: char) -> usize {
if c == '\t' {
return 4;
}
if c.is_ascii() {
return 1;
}
let cp = c as u32;
// 宽字符区段
if (0x1100..=0x115F).contains(&cp) // Hangul Jamo
|| (0x2329..=0x232A).contains(&cp) // <>
|| (0x2E80..=0xA4CF).contains(&cp) // CJK Radicals .. CJK
|| (0xA960..=0xA97C).contains(&cp) // Hangul Jamo Extended-A
|| (0xAC00..=0xD7A3).contains(&cp) // Hangul Syllables
|| (0xF900..=0xFAFF).contains(&cp) // CJK Compatibility
|| (0xFE10..=0xFE6F).contains(&cp) // CJK Compatibility Forms
|| (0xFF01..=0xFF60).contains(&cp) // Fullwidth Forms
|| (0xFFE0..=0xFFE6).contains(&cp) // Fullwidth Signs
|| (0x1F300..=0x1F64F).contains(&cp) // Emoticons
|| (0x20000..=0x2FFFF).contains(&cp) // CJK Extension B+
|| (0x30000..=0x3FFFF).contains(&cp) // CJK Extension G+
|| cp >= 0x1F000
// Emoji / Supplemental
{
return 2;
}
1
}
fn display_width(s: &str) -> usize {
s.chars().map(char_width).sum()
}
// 测试
#[cfg(test)]
mod tests {
use super::*;
use crate::core::source::SourceFile;
use crate::error::definitions::unterminated_string_literal::UnterminatedStringLiteralError;
#[test]
fn display_width_ascii() {
assert_eq!(display_width("hello"), 5);
}
#[test]
fn display_width_cjk() {
assert_eq!(display_width("你好世界"), 8); // 4 chars × 2 width
}
#[test]
fn display_width_mixed() {
assert_eq!(display_width("var 你好"), 3 + 1 + 4); // "var" + " " + "你好" = 3 + 1 + 4 = 8
}
#[test]
fn show_unterminated_string_zh_cn() {
let source = "var x = \"hello\nvar y = \"world\n";
let mut mgr = SourceManager::new();
mgr.add_file(SourceFile::from_string("test.fig", source.to_string()));
let err = UnterminatedStringLiteralError::new(0, 2, 9, 2, 14).with_thrower(crate::thrower!());
let reporter = Reporter::new(core::Lang::zh_CN);
let mut buf = String::new();
reporter.report(&err, &mgr, &mut buf);
println!("{buf}");
}
#[test]
fn show_cjk_source() {
let source = "让 你好 = \"你好世界\n";
let mut mgr = SourceManager::new();
mgr.add_file(SourceFile::from_string("test.fig", source.to_string()));
let err = UnterminatedStringLiteralError::new(0, 1, 8, 1, 15).with_thrower(crate::thrower!());
let reporter = Reporter::new(core::Lang::zh_CN);
let mut buf = String::new();
reporter.report(&err, &mgr, &mut buf);
println!("{buf}");
}
#[test]
fn show_unterminated_string_en_us() {
let source = "var x = \"hello\n";
let mut mgr = SourceManager::new();
mgr.add_file(SourceFile::from_string("test.fig", source.to_string()));
let err = UnterminatedStringLiteralError::new(0, 1, 9, 1, 14).with_thrower(crate::thrower!());
let reporter = Reporter::new(core::Lang::en_US);
let mut buf = String::new();
reporter.report(&err, &mgr, &mut buf);
println!("{buf}");
}
}
+942
View File
@@ -0,0 +1,942 @@
/*
src/lexer/lexer.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
use crate::error::definitions::*;
use crate::error::diagnostic::Diagnostic;
use crate::token::TokenKind;
use crate::token;
pub struct SrcReader<'a> {
source: &'a str,
index: usize,
line: usize,
column: usize,
}
impl<'a> SrcReader<'a> {
pub fn new(source: &'a str) -> Self {
SrcReader {
source,
index: 0,
line: 1,
column: 1,
}
}
pub fn read_char(&mut self) -> Option<char> {
if self.index >= self.source.len() {
return None;
}
let ch = self.source[self.index..].chars().next().unwrap();
self.index += ch.len_utf8();
match ch {
'\n' => {
self.line += 1;
self.column = 1;
}
'\t' => {
self.column += 4;
}
_ => {
self.column += 1;
}
}
Some(ch)
}
pub fn peek_char(&self) -> Option<char> {
if self.index >= self.source.len() {
return None;
}
Some(self.source[self.index..].chars().next().unwrap())
}
pub fn is_eof(&self) -> bool {
self.index >= self.source.len()
}
}
pub struct Lexer<'a> {
file_name: &'a str,
file_id: usize,
reader: SrcReader<'a>,
is_eof: bool,
}
impl<'a> Lexer<'a> {
pub fn new(file_name: &'a str, file_id: usize, source: &'a str) -> Self {
Lexer {
file_name,
file_id,
reader: SrcReader::new(source),
is_eof: false,
}
}
pub fn next_token(&mut self) -> Result<token::Token, Box<dyn Diagnostic>> {
if self.is_eof {
panic!("Lexer has reached EOF, cannot read more tokens.");
} else if self.reader.is_eof() {
self.is_eof = true;
return Ok(self.make_eof_token());
}
let line = self.reader.line;
let column = self.reader.column;
let ch = self.reader.read_char().unwrap();
match ch {
' ' | '\t' | '\n' => self.next_token(),
c if c.is_alphabetic() || c == '_' => self.parse_identifier(c),
'"' => self.parse_string(line, column),
'r' if self.reader.peek_char().is_some_and(|c| c == '"') => self.parse_raw_string(),
/* 二进制、八进制、十六进制数字 严格 0b 0o 0x小写,具体数字部分字母大写 */
'0' if self.reader.peek_char().is_some_and(|c| c == 'b') => self.parse_binint(),
'0' if self.reader.peek_char().is_some_and(|c| c == 'o') => self.parse_octint(),
'0' if self.reader.peek_char().is_some_and(|c| c == 'x') => self.parse_hexint(),
c if c.is_ascii_digit() => self.parse_number(c),
'/' if self.reader.peek_char().is_some_and(|c| c == '/') => {
self.skip_line_comment();
self.next_token()
}
'/' if self.reader.peek_char().is_some_and(|c| c == '*') => {
self.parse_block_comment(line, column)
}
c if token::lookup_operator(&c.to_string()).is_some() => self.parse_operator(c),
_ => Err(Box::new(
unexpected_character::UnexpectedCharacterError::new(self.file_id, ch, line, column),
)),
}
}
fn parse_identifier(&mut self, first: char) -> Result<token::Token, Box<dyn Diagnostic>> {
let mut tok = token::Token::new(
TokenKind::Identifier,
self.reader.index - first.len_utf8(),
1,
);
let mut lexeme = String::from(first);
while self
.reader
.peek_char()
.is_some_and(|c| c.is_alphanumeric() || c == '_')
{
tok.length += 1;
lexeme.push(self.reader.read_char().unwrap());
}
if let Some(keyword_type) = token::lookup_keyword(&lexeme) {
Ok(token::Token::new(keyword_type, tok.index, tok.length))
} else {
Ok(tok)
}
}
fn parse_string(
&mut self,
start_line: usize,
start_column: usize,
) -> Result<token::Token, Box<dyn Diagnostic>> {
let mut tok = token::Token::new(token::TokenKind::String, self.reader.index - 1, 1);
let mut terminated = false;
let mut end_line = start_line;
let mut end_column = start_column + 1;
while let Some(c) = self.reader.peek_char() {
match c {
'"' => {
end_line = self.reader.line;
end_column = self.reader.column + 1;
tok.length += 1;
self.reader.read_char();
terminated = true;
break;
}
'\\' => {
tok.length += 1;
self.reader.read_char();
if let Some(esc) = self.reader.read_char() {
tok.length += 1;
match esc {
'n' | 't' | 'r' | '\\' | '"' => {}
_ => {
return self.diag_newline(Box::new(
invalid_escape_sequence::InvalidEscapeSequenceError::new(
self.file_id,
self.reader.line,
self.reader.column - 2,
esc.to_string(),
),
));
}
}
} else {
break;
}
}
_ => {
end_line = self.reader.line;
end_column = self.reader.column;
tok.length += 1;
self.reader.read_char();
}
}
}
if !terminated {
return self.diag_newline(Box::new(
unterminated_string_literal::UnterminatedStringLiteralError::new(
self.file_id,
start_line,
start_column,
end_line,
end_column,
),
));
}
Ok(tok)
}
fn parse_raw_string(&mut self) -> Result<token::Token, Box<dyn Diagnostic>> {
let line = self.reader.line;
let column = self.reader.column;
self.reader.read_char(); // 跳过 '"'
match self.parse_string(line, column) {
Ok(mut tok) => {
tok.kind = token::TokenKind::RawString;
Ok(tok)
}
Err(e) => Err(e),
}
}
fn parse_number(&mut self, first: char) -> Result<token::Token, Box<dyn Diagnostic>> {
let mut tok = token::Token::new(TokenKind::Number, self.reader.index - first.len_utf8(), 1);
if first == '0' && self.reader.peek_char().is_some_and(|c| c.is_ascii_digit()) {
return self.diag_newline(Box::new(
invalid_number_literal::InvalidNumberLiteralError::new(
self.file_id,
self.reader.line,
self.reader.column - 1,
self.reader.column,
),
));
}
self.read_digits(&mut tok);
if self.reader.peek_char().is_some_and(|c| c == '.') {
tok.length += 1;
self.reader.read_char();
self.read_digits(&mut tok);
}
if self
.reader
.peek_char()
.is_some_and(|c| c == 'e' || c == 'E')
{
tok.length += 1;
self.reader.read_char();
if self
.reader
.peek_char()
.is_some_and(|c| c == '+' || c == '-')
{
tok.length += 1;
self.reader.read_char();
}
match self.reader.peek_char() {
Some('0') => {
return self.diag_newline(Box::new(
invalid_number_literal::InvalidNumberLiteralError::new(
self.file_id,
self.reader.line,
self.reader.column,
self.reader.column + 1,
),
));
}
Some(c) if c.is_ascii_digit() => {
self.read_digits(&mut tok);
}
_ => {
return self.diag_newline(Box::new(
invalid_number_literal::InvalidNumberLiteralError::new(
self.file_id,
self.reader.line,
self.reader.column,
self.reader.column + 1,
),
));
}
}
}
Ok(tok)
}
fn parse_binint(&mut self) -> Result<token::Token, Box<dyn Diagnostic>> {
let start_column = self.reader.column - 1;
let mut tok = token::Token::new(TokenKind::BinInt, self.reader.index - 1, 2);
self.reader.read_char(); // 跳过 b
while let Some(c) = self.reader.peek_char() {
match c {
'0' | '1' => {
tok.length += 1;
self.reader.read_char();
}
'2'..='9' => {
return self.diag_newline(Box::new(
invalid_number_literal::InvalidNumberLiteralError::new(
self.file_id,
self.reader.line,
start_column,
self.reader.column,
),
))
}
_ => {
break;
}
}
}
Ok(tok)
}
fn parse_octint(&mut self) -> Result<token::Token, Box<dyn Diagnostic>> {
let start_column = self.reader.column - 1;
let mut tok = token::Token::new(TokenKind::OctInt, self.reader.index - 1, 2);
self.reader.read_char(); // 跳过 o
while let Some(c) = self.reader.peek_char() {
match c {
'0'..='8' => {
tok.length += 1;
self.reader.read_char();
}
'9' => {
return self.diag_newline(Box::new(
invalid_number_literal::InvalidNumberLiteralError::new(
self.file_id,
self.reader.line,
start_column,
self.reader.column,
),
))
}
_ => {
break;
}
}
}
Ok(tok)
}
fn parse_hexint(&mut self) -> Result<token::Token, Box<dyn Diagnostic>> {
let start_column = self.reader.column - 1;
let mut tok = token::Token::new(TokenKind::HexInt, self.reader.index - 1, 2);
self.reader.read_char(); // 跳过 x
while let Some(c) = self.reader.peek_char() {
match c {
'0'..='9' | 'A'..='F' => {
tok.length += 1;
self.reader.read_char();
}
'G'..='Z' => {
return self.diag_newline(Box::new(
invalid_number_literal::InvalidNumberLiteralError::new(
self.file_id,
self.reader.line,
start_column,
self.reader.column,
),
))
}
_ => {
break;
}
}
}
Ok(tok)
}
fn parse_operator(&mut self, first: char) -> Result<token::Token, Box<dyn Diagnostic>> {
let start = self.reader.index - 1; // first 一定是 ASCII1 字节
let rest = &self.reader.source[start..];
for len in [2, 1] {
if let Some(prefix) = rest.get(..len) {
if let Some((kind, op_len)) = token::lookup_operator(prefix) {
for _ in 1..op_len {
self.reader.read_char();
}
return Ok(token::Token::new(kind, start, op_len as u32));
}
}
}
unreachable!("dispatcher already confirmed first char is an operator")
}
fn read_digits(&mut self, tok: &mut token::Token) {
while self.reader.peek_char().is_some_and(|c| c.is_ascii_digit()) {
tok.length += 1;
self.reader.read_char();
}
}
fn diag_newline(
&mut self,
diagnostic: Box<dyn Diagnostic>,
) -> Result<token::Token, Box<dyn Diagnostic>> {
while !self.reader.is_eof() {
let ch = self.reader.read_char().unwrap();
if ch == '\n' {
break;
}
}
Err(diagnostic)
}
fn skip_line_comment(&mut self) {
self.reader.read_char();
while let Some(c) = self.reader.peek_char() {
if c == '\n' {
break;
}
self.reader.read_char();
}
}
fn parse_block_comment(
&mut self,
start_line: usize,
start_column: usize,
) -> Result<token::Token, Box<dyn Diagnostic>> {
self.reader.read_char();
let mut end_line = start_line;
let mut end_column = start_column;
while let Some(c) = self.reader.peek_char() {
end_line = self.reader.line;
end_column = self.reader.column;
if c == '*' {
self.reader.read_char();
if self.reader.peek_char().is_some_and(|c| c == '/') {
self.reader.read_char();
return self.next_token();
}
} else {
self.reader.read_char();
}
}
self.diag_newline(Box::new(unterminated_comment::UnterminatedCommentError::new(
self.file_id,
start_line,
start_column,
end_line,
end_column,
)))
}
fn make_eof_token(&self) -> token::Token {
token::Token::new(TokenKind::Eof, self.reader.index, 1)
}
fn diag_semicolon(
&mut self,
diagnostic: Box<dyn Diagnostic>,
) -> Result<token::Token, Box<dyn Diagnostic>> {
while !self.reader.is_eof() {
let ch = self.reader.read_char().unwrap();
if ch == ';' {
break;
}
}
Err(diagnostic)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core;
use crate::core::source::{SourceFile, SourceManager};
use crate::error::report::Reporter;
use crate::token::TokenKind::*;
use std::string::String as StdString;
fn setup(src: &str) -> (SourceManager, Reporter) {
let mut mgr = SourceManager::new();
mgr.add_file(SourceFile::from_string("test", src.to_string()));
(mgr, Reporter::new(core::Lang::zh_CN))
}
fn lex(source: &str) -> Vec<token::Token> {
let (mgr, reporter) = setup(source);
let mut lexer = Lexer::new("test", 0, source);
let mut tokens = vec![];
loop {
match lexer.next_token() {
Ok(tok) => {
let is_eof = tok.kind == Eof;
tokens.push(tok);
if is_eof {
break;
}
}
Err(e) => {
let mut buf = StdString::new();
reporter.report(e.as_ref(), &mgr, &mut buf);
println!("{buf}");
panic!("Lexer error in test");
}
}
}
tokens
}
fn kinds(tokens: &[token::Token]) -> Vec<token::TokenKind> {
tokens.iter().map(|t| t.kind).collect()
}
#[test]
fn keywords() {
let tokens = lex("var const func if else return true false null");
assert_eq!(
kinds(&tokens),
[Var, Const, Func, If, Else, Return, True, False, Null, Eof]
);
}
#[test]
fn identifiers() {
let tokens = lex("foo _bar 你好 a1 b2_c3");
let ks = kinds(&tokens);
assert_eq!(ks[0], Identifier);
assert_eq!(ks[1], Identifier);
assert_eq!(ks[2], Identifier);
assert_eq!(ks[3], Identifier);
assert_eq!(ks[4], Identifier);
assert_eq!(ks[5], Eof);
}
#[test]
fn numbers() {
let tokens = lex("42 3.14 5e2 0b1010 0o77 0xFF");
assert_eq!(
kinds(&tokens),
[Number, Number, Number, BinInt, OctInt, HexInt, Eof]
);
}
#[test]
fn binint() {
let tokens = lex("0b1010 0b0 0b11111111");
assert_eq!(kinds(&tokens), [BinInt, BinInt, BinInt, Eof]);
}
#[test]
fn octint() {
let tokens = lex("0o777 0o0 0o1234567");
assert_eq!(kinds(&tokens), [OctInt, OctInt, OctInt, Eof]);
}
#[test]
fn hexint() {
let tokens = lex("0xFF 0xDEAD 0xBEEF 0xA0");
assert_eq!(kinds(&tokens), [HexInt, HexInt, HexInt, HexInt, Eof]);
}
#[test]
fn binint_invalid_digit() {
let (mgr, reporter) = setup("0b102");
let mut lexer = Lexer::new("test", 0, "0b102");
let mut buf = StdString::new();
match lexer.next_token() {
Err(e) => reporter.report(e.as_ref(), &mgr, &mut buf),
_ => {}
}
println!("{buf}");
}
#[test]
fn strings() {
let tokens = lex(r#""hello" "world\n""#);
assert_eq!(kinds(&tokens), [String, String, Eof]);
}
#[test]
fn operators_single() {
let tokens = lex("+ - * / % = < > & | ^ ~");
assert_eq!(
kinds(&tokens),
[Plus, Minus, Star, Slash, Percent, Assign, Lt, Gt, Amp, Pipe, Caret, Tilde, Eof]
);
}
#[test]
fn operators_multi() {
let tokens = lex("== != <= >= && || << >> ++ -- **");
assert_eq!(
kinds(&tokens),
[EqEq, NotEq, LtEq, GtEq, AndAnd, OrOr, LtLt, GtGt, PlusPlus, MinusMinus, StarStar, Eof]
);
}
#[test]
fn operators_assign() {
let tokens = lex("+= -= *= /= %= ^=");
assert_eq!(
kinds(&tokens),
[PlusAssign, MinusAssign, StarAssign, SlashAssign, PercentAssign, CaretAssign, Eof]
);
}
#[test]
fn operators_longest_match() {
// (x = 42) 中 = 不能和 == 混淆
let tokens = lex("x = 42 x == 42");
assert_eq!(
kinds(&tokens),
[Identifier, Assign, Number, Identifier, EqEq, Number, Eof]
);
}
#[test]
fn operators_pair() {
let tokens = lex("() {} []");
assert_eq!(
kinds(&tokens),
[LParen, RParen, LBrace, RBrace, LBracket, RBracket, Eof]
);
}
#[test]
fn operators_closing_only() {
let tokens = lex(") } ]");
assert_eq!(kinds(&tokens), [RParen, RBrace, RBracket, Eof]);
}
#[test]
fn operators_delimiters() {
let tokens = lex("() {} [] , ; : . -> => ? @ # $");
assert_eq!(
kinds(&tokens),
[
LParen, RParen,
LBrace, RBrace,
LBracket, RBracket,
Comma, Semicolon, Colon, Dot,
Arrow, FatArrow,
Question, At, Hash, Dollar,
Eof,
]
);
}
#[test]
fn unterminated_string_errors() {
let source = "\"hello\n";
let (mgr, reporter) = setup(source);
let mut lexer = Lexer::new("test", 0, source);
let mut buf = StdString::new();
match lexer.next_token() {
Err(e) => reporter.report(e.as_ref(), &mgr, &mut buf),
_ => {}
}
println!("{buf}");
let tok = lexer.next_token().unwrap();
assert_eq!(tok.kind, Eof);
}
#[test]
fn line_comment() {
let tokens = lex("42 // this is a comment\n99");
assert_eq!(kinds(&tokens), [Number, Number, Eof]);
}
#[test]
fn block_comment() {
let tokens = lex("42 /* block */ 99");
assert_eq!(kinds(&tokens), [Number, Number, Eof]);
}
#[test]
fn block_comment_multiline() {
let tokens = lex("42 /* line 1\nline 2\n*/ 99");
assert_eq!(kinds(&tokens), [Number, Number, Eof]);
}
#[test]
fn block_comment_unterminated() {
let source = "42 /* never closed";
let (mgr, reporter) = setup(source);
let mut lexer = Lexer::new("test", 0, source);
let mut buf = StdString::new();
while let Ok(tok) = lexer.next_token() {
if tok.kind == Eof { break; }
}
// error was reported via diag_newline, which recovers to newline
// verify the error system captured it (we don't assert output, just no panic)
}
#[test]
fn comment_inside_code() {
let source = r#"
var x = 1 // inline comment
/* multi
line */
var y = 2
"#;
let (mgr, reporter) = setup(source);
let mut lexer = Lexer::new("test", 0, source);
loop {
match lexer.next_token() {
Ok(tok) => {
if tok.kind == Eof { break; }
}
Err(_) => {}
}
}
}
#[test]
fn edge_empty() {
// 空源码 → 只有 Eof
let tokens = lex("");
assert_eq!(kinds(&tokens), [Eof]);
}
#[test]
fn edge_whitespace_only() {
// 纯空白
let tokens = lex(" \n \t \n ");
assert_eq!(kinds(&tokens), [Eof]);
}
#[test]
fn edge_single_chars() {
let tokens = lex("+ - * / % = < > & | ^ ~ ! . , ; : ? @ # $ ( ) { } [ ]");
assert_eq!(kinds(&tokens).len(), 28); // 27 tokens + Eof
}
#[test]
fn edge_eof_after_token_start() {
let (_, reporter) = setup("\"eof");
let mut lexer = Lexer::new("test", 0, "\"eof");
assert!(lexer.next_token().is_err());
assert_eq!(lexer.next_token().unwrap().kind, Eof);
}
#[test]
fn edge_consecutive_operators() {
let tokens = lex("+-*/%&&||==!=<><=>=->=>::++--**+=-=*=/=%=^=");
assert!(kinds(&tokens).len() > 20);
}
#[test]
fn edge_operator_at_eof() {
let tokens = lex("42+");
assert_eq!(kinds(&tokens), [Number, Plus, Eof]);
}
#[test]
fn edge_line_comment_at_eof() {
let tokens = lex("42// no newline");
assert_eq!(kinds(&tokens), [Number, Eof]);
}
#[test]
fn edge_block_comment_at_eof() {
let mut lexer = Lexer::new("test", 0, "42/* no close");
let mut kinds = vec![];
loop {
match lexer.next_token() {
Ok(tok) => {
kinds.push(tok.kind);
if tok.kind == Eof { break; }
}
Err(_) => {}
}
}
assert_eq!(&kinds, &[Number, Eof]);
}
#[test]
fn edge_zero_at_eof() {
let tokens = lex("x >= 0");
assert_eq!(kinds(&tokens), [Identifier, GtEq, Number, Eof]);
}
#[test]
fn edge_empty_prefix_number() {
let tokens = lex("0b 0x 0o");
assert_eq!(kinds(&tokens), [BinInt, HexInt, OctInt, Eof]);
}
#[test]
fn edge_empty_raw_string() {
let mut lexer = Lexer::new("test", 0, r###"r"""###);
let tok = lexer.next_token().unwrap();
println!("{:?}", tok.kind);
}
#[test]
fn edge_star_not_comment() {
let tokens = lex("2 * 3");
assert_eq!(kinds(&tokens), [Number, Star, Number, Eof]);
}
#[test]
fn edge_slash_not_comment() {
let tokens = lex("2 / 3");
assert_eq!(kinds(&tokens), [Number, Slash, Number, Eof]);
}
#[test]
fn edge_block_comment_star_only() {
let source = "42 /* * * *";
let mut lexer = Lexer::new("test", 0, source);
let mut tokens = vec![];
loop {
match lexer.next_token() {
Ok(tok) => {
let is_eof = tok.kind == Eof;
tokens.push(tok.kind);
if is_eof { break; }
}
Err(_) => {} // unterminated comment, recovered
}
}
assert_eq!(&tokens, &[Number, Eof]);
}
#[test]
fn stress_1000_lines() {
let mut source = StdString::new();
// 生成约 1000 行覆盖全 token 类型的源码
for i in 0..60 {
let hex = format!("0x{:X}", i * 7);
let oct = format!("0o{:o}", i);
let bin = format!("0b{:b}", i % 256);
source.push_str(&format!(
"var v{i} = {i}\n\
const c{i} = {i}.{i}\n\
let s{i} = \"str {i} 你好\"\n\
// line comment {i}\n\
func f{i}(a{i}, b{i}) {{\n\
/* block comment {i} */\n\
if a{i} != {i} {{\n\
return a{i} + b{i} * {i} - {hex} | {oct}\n\
}} else {{\n\
return c{i} >= {i} && v{i} <= {i} || f{i}(a{i}, b{i})\n\
}}\n\
}}\n\
v{i} += {i}\n\
v{i} **= 2\n\
let t{i} = true && !false\n\
let n{i} = !{i}.0 + 0xFF\n\
let arr{i} = [? # $ @ -> =>]\n\
"
));
}
// 添加故意错误行(错误恢复验证)
source.push_str("var bad = \"never close this\n");
source.push_str("/* unterminated block comment\n");
source.push_str("var after_comment = 42\n");
let (mgr, reporter) = setup(&source);
let mut lexer = Lexer::new("stress", 0, &source);
let mut tokens = 0usize;
let mut errors = 0usize;
loop {
match lexer.next_token() {
Ok(tok) => {
tokens += 1;
if tok.kind == Eof { break; }
}
Err(e) => {
errors += 1;
let mut buf = StdString::new();
reporter.report(e.as_ref(), &mgr, &mut buf);
println!("{buf}");
}
}
}
let lines = source.lines().count();
println!(
"stress: {} lines => {} tokens, {} errors recovered",
lines, tokens, errors
);
assert!(tokens > 2000, "expected >2000 tokens, got {tokens}");
assert!(lines > 800, "expected >800 lines, got {lines}");
}
#[test]
fn stress_test() {
let source = r#"
var 你好 = "hello 世界"
const MAX = 0xFF
func fib(n) {
if n <= 1 {
return n
}
return fib(n - 1) + fib(n - 2)
}
let x = 42 + 3.14
let y = x * 2 - 1
let flag = true && !false
let bits = 0b1010 | 0o77
let result = (x >= 0) && (x != 42)
x += 1
y -= 1
x **= 2
-> => ? @ # $
"#;
let (mgr, reporter) = setup(source);
let mut lexer = Lexer::new("test", 0, source);
let mut count = 0usize;
loop {
match lexer.next_token() {
Ok(tok) => {
let text: StdString = source[tok.index..]
.chars()
.take(tok.length as usize)
.collect();
count += 1;
println!("{:>3} {:?} '{}'", count, tok.kind, text);
if tok.kind == Eof {
break;
}
}
Err(e) => {
let mut buf = StdString::new();
reporter.report(e.as_ref(), &mgr, &mut buf);
println!("{buf}");
}
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
/*
src/lexer/mod.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
pub mod lexer;
pub use lexer::Lexer as Lexer;
+14
View File
@@ -0,0 +1,14 @@
/*
src/main.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
mod core;
mod error;
mod token;
mod lexer;
fn main() {
println!("Fig Language Compiler v0.6.0");
}
+231
View File
@@ -0,0 +1,231 @@
/*
src/token/mod.rs
Part of The Fig Project, under the MIT License.
Copyright (c) 2026, PuqiAR (im@puqiar.top) All rights reserved.
See LICENSE for details.
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenKind {
Eof,
// 标识符
Identifier,
// 字面量
String,
RawString, // r"raw string"
Number, // 123, 3.1415, 100e10 (等同 100e+10) ...
BinInt, // 0b1010
OctInt, // 0o77
HexInt, // 0xFF
// 关键字
Var,
Const,
Func,
Struct,
Interface,
Impl,
Enum,
If,
Else,
For,
While,
Return,
Break,
Continue,
Import,
New,
As,
Is,
True,
False,
Null,
And,
Or,
Not,
Public,
// 分隔符
LParen, // (
RParen, // )
LBrace, // {
RBrace, // }
LBracket, // [
RBracket, // ]
Comma, // ,
Dot, // .
Semicolon, // ;
Colon, // :
Arrow, // ->
FatArrow, // =>
Question, // ?
At, // @
Hash, // #
Dollar, // $
// 运算符
Plus, // +
Minus, // -
Star, // *
Slash, // /
Percent, // %
StarStar, // **
PlusPlus, // ++
MinusMinus, // --
// 赋值运算符
Assign, // =
PlusAssign, // +=
MinusAssign, // -=
StarAssign, // *=
SlashAssign, // /=
PercentAssign, // %=
CaretAssign, // ^=
// 比较运算符
EqEq, // ==
NotEq, // !=
Lt, // <
Gt, // >
LtEq, // <=
GtEq, // >=
// 逻辑运算符
AndAnd, // &&
OrOr, // ||
// 位运算符
Amp, // &
Pipe, // |
Caret, // ^
Tilde, // ~
Bang, // !
LtLt, // <<
GtGt, // >>
}
/// Token 实例
#[derive(Debug, Clone)]
pub struct Token {
pub kind: TokenKind,
pub index: usize,
pub length: u32,
}
impl Token
{
pub fn new(kind: TokenKind, index: usize, length: u32) -> Self
{
Token
{
kind,
index,
length
}
}
}
/// 运算符打表,按字符串长度降序保证最长匹配优先。
pub static OPERATORS: &[(&str, TokenKind)] = &[
("->", TokenKind::Arrow),
("=>", TokenKind::FatArrow),
("**", TokenKind::StarStar),
("*=", TokenKind::StarAssign),
("++", TokenKind::PlusPlus),
("+=", TokenKind::PlusAssign),
("--", TokenKind::MinusMinus),
("-=", TokenKind::MinusAssign),
("/=", TokenKind::SlashAssign),
("%=", TokenKind::PercentAssign),
("^=", TokenKind::CaretAssign),
("==", TokenKind::EqEq),
("!=", TokenKind::NotEq),
("<=", TokenKind::LtEq),
(">=", TokenKind::GtEq),
("&&", TokenKind::AndAnd),
("||", TokenKind::OrOr),
("<<", TokenKind::LtLt),
(">>", TokenKind::GtGt),
("+", TokenKind::Plus),
("-", TokenKind::Minus),
("*", TokenKind::Star),
("/", TokenKind::Slash),
("%", TokenKind::Percent),
("=", TokenKind::Assign),
("<", TokenKind::Lt),
(">", TokenKind::Gt),
("&", TokenKind::Amp),
("|", TokenKind::Pipe),
("^", TokenKind::Caret),
("~", TokenKind::Tilde),
("!", TokenKind::Bang),
("(", TokenKind::LParen),
(")", TokenKind::RParen),
("{", TokenKind::LBrace),
("}", TokenKind::RBrace),
("[", TokenKind::LBracket),
("]", TokenKind::RBracket),
(",", TokenKind::Comma),
(".", TokenKind::Dot),
(";", TokenKind::Semicolon),
(":", TokenKind::Colon),
("?", TokenKind::Question),
("@", TokenKind::At),
("#", TokenKind::Hash),
("$", TokenKind::Dollar),
];
/// 遍历 OPERATORS,返回第一个匹配的 (TokenKind, 长度)。
pub fn lookup_operator(prefix: &str) -> Option<(TokenKind, usize)> {
for (s, kind) in OPERATORS {
if prefix.starts_with(s) {
return Some((*kind, s.len()));
}
}
None
}
pub fn lookup_keyword(word: &str) -> Option<TokenKind> {
match word {
"var" => Some(TokenKind::Var),
"const" => Some(TokenKind::Const),
"func" => Some(TokenKind::Func),
"struct" => Some(TokenKind::Struct),
"interface" => Some(TokenKind::Interface),
"impl" => Some(TokenKind::Impl),
"enum" => Some(TokenKind::Enum),
"if" => Some(TokenKind::If),
"else" => Some(TokenKind::Else),
"for" => Some(TokenKind::For),
"while" => Some(TokenKind::While),
"return" => Some(TokenKind::Return),
"break" => Some(TokenKind::Break),
"continue" => Some(TokenKind::Continue),
"import" => Some(TokenKind::Import),
"new" => Some(TokenKind::New),
"as" => Some(TokenKind::As),
"is" => Some(TokenKind::Is),
"true" => Some(TokenKind::True),
"false" => Some(TokenKind::False),
"null" => Some(TokenKind::Null),
"and" => Some(TokenKind::And),
"or" => Some(TokenKind::Or),
"not" => Some(TokenKind::Not),
"public" => Some(TokenKind::Public),
_ => None,
}
}