Compare commits

...

35 Commits

Author SHA1 Message Date
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
4f87078a87 Plan 2026-06-01 18:52:06 +08:00
9338c21449 *(无用) feat: 在VM.cpp中添加likely属性以优化分支预测
refact: 在xmake.lua中优化构建设置
2026-05-10 21:25:05 +08:00
98de782760 feat: 增加repl入口,-r/--repl。 添加计时选项。 -- 我发现一个问题,analyzer没法保存环境。完了。 2026-04-30 21:24:11 +08:00
fafa2b4946 feat: 在解析器中实现 Lambda 和 new 表达式
- 增加了对 Lambda 表达式的初步解析支持,包括参数处理和返回类型。Lambda闭包尚未支持。
- 引入了用于对象初始化的新的表达式,支持可选的命名参数。
- 改进了表达式语法错误的错误报告。
- 更新了解析器和分析器以处理新的表达式类型并验证其语义。
- 修改了现有测试以涵盖新功能并确保其正确性。
- 改进了各种解析和语义错误的诊断。
2026-04-12 10:07:51 +08:00
570a87c3cd feat: 增加函数类型表达式支持,更新解析器和分析器 2026-03-18 17:30:09 +08:00
e1d9812f92 refact:实现参数解析器和入口点
- 新增了一个名为 ArgumentParser 的类来处理命令行参数,其中包括用于显示帮助、显示版本和显示许可证的标志。
- 更新了 main.cpp 以使用 ArgumentParser 来改进命令行界面。
- 创建了 Entry.cpp 和 Entry.hpp 来封装虚拟机执行逻辑,从而实现更好的关注点分离。
- 调整了 xmake.lua 以包含 ArgumentParser 和 Entry 组件的新源文件。
- 强化了命令行使用的错误处理和用户反馈。
2026-03-14 14:18:21 +08:00
6bcc98bdb3 删除无用的东西233 2026-03-13 20:11:46 +08:00
91b5a0e384 feat: 增加一个很简单的repl(还不能用) 写了readme 2026-03-13 01:28:43 +08:00
c0eacfd236 refactor: 修改Disassembler使用CoreIO的stream 2026-03-11 21:51:58 +08:00
51a939ac45 对编译器和虚拟机进行重构,以支持闭包和垃圾回收功能
- 去除了不再使用的结构,并更新了编译器以处理新的闭包语义。
- 改进了 Compiler,使其能够生成带有源位置跟踪的指令。
- 在 FunctionObject 和 VM 中引入了作用域变量管理,以支持动态闭包。
- 实现了使用标记-扫描(Mark-And-Sweep) (Tri-Color tracing) 算法的垃圾回收机制,包括对作用域变量的处理。
- 在 VM 中增加了函数加载和作用域变量检索的支持。
- 更新了对象模型,包括引入 InstanceObject 并改进内存管理。
- 添加了用于调试的全局变量打印功能。
2026-03-11 16:53:10 +08:00
0f635ccf2b 重构类型系统并改进诊断功能
- 更新了类型系统,新增了类型并优化了结构。
- 引入了基类型和派生类,用于函数、结构体和接口类型。
- 实现了类型上下文,用于管理内置类型和类型解析。
- 添加了诊断类,用于收集和报告警告和错误。
- 通过改进错误处理增强了虚拟机执行,以应对递归限制问题。
- 实现了反汇编器,将字节码转换为代码,以改善调试和分析。
- 添加了新的抽象语法树节点,用于成员表达式、对象初始化、接口和结构体定义。
- 引入了语义错误测试,包括重定义、未声明的变量和无效的结构字段。
2026-03-10 12:33:17 +08:00
90448006ff refactor: 引入 Arena 内存池并优化指令分发,为类型系统重构做准备 2026-03-08 15:59:55 +08:00
91e4eb734e feat: 使用Computed Goto优化指令分发机制和算术运算处理 2026-03-07 21:33:55 +08:00
6dbecbbdc0 feat: 重构编译器以支持函数定义和调用,添加新的字节码以支持函数调用
另外,我很高兴地宣布,fib(40) 递归法 在我的平台, i5-13490f,只需要 6600ms, fib(30) 56ms
这是历史性的一刻!
2026-03-07 00:34:52 +08:00
1fe9ccf7ea feat: 实现控制流语句并优化类型解析
- 新增了 ReturnStmt、BreakStmt 和 ContinueStmt 结构,以支持 AST 中的控制流。
- 引入了 TypeInfo 和 TypeContext 以实现更好的类型管理和解析。
- 对分析器进行了增强,能够处理函数定义和返回语句,包括类型检查和错误处理。
- 更新了编译器和解析器以适应新的控制流语句和类型解析逻辑。
- 重构了现有代码以提高清晰度和可维护性,包括对表达式和语句中的类型处理的更改。
- 删除了过时的 String 和 Struct 定义,代之以 StringObject 和 StructObject 以实现更好的对象表示。
2026-02-28 20:42:15 +08:00
bb23ddf9fa feat: 添加函数定义和类型表达式支持,重构解析器以处理新语法(函数定义) 2026-02-26 14:41:41 +08:00
12dc31a6c0 fix: 修复了在解析字面量时的条件逻辑,确保整数转换在浮点数转换之后进行 2026-02-25 19:15:25 +08:00
a0fb8cdffb feat: 添加“while 语句”支持,并对解析器进行重构以处理控制流相关内容
- 引入了 WhileStmt 结构来表示 while 循环语句。
- 在解析器中实现了对 while 语句的解析逻辑。
- 在分析器中为 while 语句添加了语义分析。
- 重构了现有的解析器方法,以利用 StateProtector 进行状态管理。
- 更新了对各种表达式和语句的错误处理。
- 移除了未使用的终止符管理方法,并简化了表达式解析。
- 将 FigLSPServer.cpp 重命名为 LSPServer.cpp,并调整了构建配置。
- 增强了重复声明和类型错误的错误报告。
- 在多个文件中改进了代码格式和一致性。
2026-02-25 17:31:00 +08:00
b7bb889676 feat: 增加了analyzer, compiler不再分析并且报错, 只生产 bytecode, analyzer作为前端结束最后一道防线检查代码,同时引入一个简单的LSP 2026-02-23 19:57:28 +08:00
852dd27836 feat: 优化表达式解析器,添加终止符重置功能及代码格式调整 2026-02-23 15:03:53 +08:00
abdb1d2fb0 feat: 添加跳转指令支持及条件语句编译实现 2026-02-20 17:02:13 +08:00
eb20993e27 feat: 添加If语句及块语句解析支持 2026-02-20 15:46:33 +08:00
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
f2e899c7a7 更换新Logo! 然后为拓展增加Logo。 2026-02-18 14:28:19 +08:00
c81da16dfb 修复EOF飘逸(去除末尾\n)以及其他修复... 2026-02-18 00:16:59 +08:00
663fe39070 添加缺失的 doxy 2026-02-17 14:13:57 +08:00
6b75e028ff 修复右结合绑定力错误 2026-02-17 14:03:48 +08:00
878157c2fc 完成Parser定义以及表达式解析 2026-02-14 23:03:46 +08:00
35e479fd05 完成表达式Ast定义。修改了format文件 2026-02-14 18:00:46 +08:00
51e831cc6a 添加了注释文档 2026-02-14 15:32:11 +08:00
35b98c4d7f 完成Lexer实现,100%可靠 2026-02-14 14:54:44 +08:00
877253cbbc 完成 Error定义和ErrorLog. 以及一些相关的东西 2026-02-13 23:11:37 +08:00
cfcdfde170 结构调整2 2026-02-12 14:55:48 +08:00
5e75402b43 项目结构调整 2026-02-12 14:55:34 +08:00
148 changed files with 39230 additions and 2614 deletions

View File

@@ -6,13 +6,13 @@ Language: Cpp
AccessModifierOffset: -4
# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行)
AlignAfterOpenBracket: Align
AlignAfterOpenBracket: AlwaysBreak
# 连续赋值时,对齐所有等号
AlignConsecutiveAssignments: false
AlignConsecutiveAssignments: true
# 连续声明时,对齐所有声明的变量名
AlignConsecutiveDeclarations: false
AlignConsecutiveDeclarations: true
# 右对齐逃脱换行(使用反斜杠换行)的反斜杠
AlignEscapedNewlines: Right
@@ -33,13 +33,13 @@ AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All
AllowShortFunctionsOnASingleLine: Inline
AllowShortFunctionsOnASingleLine: Empty
# 允许短的if语句保持在同一行
AllowShortIfStatementsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: false
# 允许短的循环保持在同一行
AllowShortLoopsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数),
# AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义)
@@ -108,8 +108,7 @@ BreakConstructorInitializers: AfterColon
BreakStringLiterals: false
# 每行字符的限制0表示没有限制
ColumnLimit: 120
ColumnLimit: 100
CompactNamespaces: true
# 构造函数的初始化列表要么都在同一行,要么都各自一行
@@ -157,7 +156,7 @@ PointerAlignment: Right
ReflowComments: true
# 允许排序#include
SortIncludes: false
SortIncludes: true
# 允许排序 using 声明
SortUsingDeclarations: false

11
.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
# Xmake cache
.xmake/
build/
# MacOS Cache
.DS_Store
.vscode
.VSCodeCounter
/test.fig

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")

View File

@@ -1,11 +1,11 @@
from time import time as tt
def fib(x:int) -> int:
if x <= 1: return x;
if x <= 1: return x
return fib(x-1) + fib(x-2)
if __name__ == '__main__':
t0 = tt()
result = fib(30)
result = fib(35)
t1 = tt()
print('cost: ',t1-t0, 'result:', result)

5
Fig-VSCode/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
out
dist
node_modules
.vscode-test/
*.vsix

View File

@@ -0,0 +1,5 @@
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'out/test/**/*.test.js',
});

11
Fig-VSCode/.vscodeignore Normal file
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
Fig-VSCode/CHANGELOG.md Normal file
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
Fig-VSCode/LICENSE Normal file
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
Fig-VSCode/README.md Normal file
View File

@@ -0,0 +1,3 @@
# fig-vscode README
Fuck.

BIN
Fig-VSCode/bin/Fig-LSP.exe Normal file

Binary file not shown.

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",
},
}];

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

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
Fig-VSCode/package-lock.json generated Normal file
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
Fig-VSCode/package.json Normal file
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"
}
}

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();
}

View File

@@ -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": "'" }
]
}

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));
});
});

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
Fig-VSCode/tsconfig.json Normal file
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. */
}
}

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.

66
LICENSE
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
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:
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.
1. 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.
2. This project includes code from the following projects with their respective licenses:
- 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.
- 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
Logo/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

4
Logo/Logo.svg Normal file
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
Logo/LogoDark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

3
Logo/LogoDark.svg Normal file
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

193
README.md
View File

@@ -1,2 +1,195 @@
# Fig
## tmd赶工代码质量太差了暑假我要重构
<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.5.0--alpha-yellow)
![C++](https://img.shields.io/badge/C++-99.5%25-blue)
![xmake](https://img.shields.io/badge/xmake-build-green)
![LLVM](https://img.shields.io/badge/LLVM-clang%2021.1.8-purple)
![Platform](https://img.shields.io/badge/Windows%20·%20Linux%20·%20macOS-lightgrey)
![Performance](https://img.shields.io/badge/fib(30)-~28ms%20(i5--13490f)-brightgreen)
**Fig** is a programming language that blends dynamic typing with optional static type annotations, built on reference-based value semantics. It offers the flexibility of scripting languages while providing optional type constraints for more robust code.
> ⚠️ **Fig is currently at version 0.5.0-alpha, and its syntax and APIs are subject to change.** Feel free to try it out and provide feedback, but do not use it in production environments.
```fig
// Get a feel for Fig
var flexible = 10 // dynamic type, can be any value
flexible = "hello" // works
var fixed := 20 // fixed to Int type
// fixed = 3.14 // error! type mismatch
func greet(name) => "Hello, " + name + "!"
println(greet("Fig")) // output: Hello, Fig!
```
## ✨ Key Features
### 🎭 Dynamic & Static Blending
Variables are dynamic by default (`Any` type), but you can lock their type using `:=` or `: Type` for static safety.
```fig
var any; // type is Any
any = 42; // now points to an Int
any = [1,2,3]; // now points to a List
var safe := 100; // inferred as Int, can only be assigned Int values
safe = 200; // ✅
// safe = "oops" // ❌ compile error
```
### 🔗 Reference-Based Value Semantics
All objects are immutable; variables are just "names" that refer to objects. Multiple variables can share the same object, and modifications to complex types (like lists) are visible to all references—this is the essence of references.
```fig
var a := [1, 2, 3];
var b := a; // b and a refer to the same list
a[0] = 99; // modify the list content
println(b) // output: [99, 2, 3] — b sees the change
// Need a true copy? Use deep copy (syntax may change)
var c := new List{a}; // deep copy of a
c[0] = 0;
println(a) // still [99, 2, 3]
```
### 🏗 Object-Oriented Features (OOP)
Fig provides lightweight OOP support based on structs, with polymorphism via interfaces.
- **Structs as classes**: defined with `struct`, can have fields and methods.
- **Access control**: `public` keyword; fields are private by default.
- **Concise construction**:
```fig
var p1 := new Point{1, 2}; // positional
var p2 := new Point{x: 2, y: 3}; // named
var x := 5, y := 10;
var p3 := new Point{y, x}; // shorthand, auto field matching
```
- **Interfaces and implementations**:
```fig
interface Drawable { draw() -> String; }
struct Circle { radius: Double }
impl Drawable for Circle {
draw() => "⚪ radius: " + radius;
}
```
- **No inheritance, polymorphism via interfaces** (currently implicit `this`, may change to explicit `self.xxx` in the future).
### 🧩 Functional Features
Functions are first-class citizens, with support for closures and concise arrow syntax.
```fig
func multiplier(factor) {
return func (n) => n * factor; // closure (upvalue)
}
var double = multiplier(2);
println(double(5)) // output: 10
```
### ⚡ High Performance
**Recursive fib(30) takes only 28ms** (i5-13490f, Windows 11, DDR4 2667, clang 21.1.8, libc++, C++23) — excellent performance among dynamic languages.
Fig achieves high performance through a series of low-level optimizations:
- **Register-based VM**: replaces the tree-walk interpreter, dramatically improving execution speed.
- **FastCall optimization**: global ordinary functions can be called directly as prototypes, avoiding runtime unwrapping overhead (traditional calls require checking if an object is a function and unwrapping it).
- **Window Slicing** (originated from Lua): a stack sliding window technique that efficiently manages function calls and closures (upvalues), reducing memory allocations.
- **Upvalue mechanism**: lightweight closure implementation, making functional programming performant.
- **Computed Goto**: leverages GCC/Clang extensions to speed up bytecode dispatch.
- **NaN-Boxing**: efficient storage and type tagging to boost dynamic typing performance.
#### Performance Comparison
Rough comparison of recursive fib(30) execution times (environment may vary, for reference only):
| Language Type | Language | Time (ms) | Notes |
|---------------|----------------|-----------|-------|
| AOT compiled | C (clang -O2) | ~0.05 | Nanoseconds, as a speed reference |
| AOT compiled | Rust (--release) | ~0.06 | Same as above |
| **Dynamic** | **Fig** | **~28** | **Register VM + optimizations** |
| Dynamic | Lua 5.4 | ~35 | Classic dynamic language |
| Dynamic | Python 3.11 | ~450 | CPython |
| Dynamic | Node.js 20 | ~60 | V8 engine |
| Dynamic | Ruby 3.2 | ~800 | CRuby |
*Fig performs admirably among dynamic languages, approaching Lua and Node.js, and significantly outperforming Python and Ruby.*
## 🚀 Quick Start
### Installation
#### Option 1: Download Pre-built Binaries
Download the binary for your platform from the [main repository Releases](https://git.fig-lang.cn/PuqiAR/Fig/releases) or the [GitHub mirror](https://github.com/PuqiAR/Fig/releases), extract it, and add it to your PATH.
#### Option 2: Build from Source with xmake
```bash
# Clone the main repository (GitHub is a mirror)
git clone https://git.fig-lang.cn/PuqiAR/Fig.git
cd Fig
# Install xmake if you haven't: https://xmake.io
# Build (must use clang because computed goto requires compiler support)
xmake f --toolchain=clang
xmake
# After building, the binary is in the build/ directory
```
### Run Your First Fig Program
Create a file `hello.fig`:
```fig
import std.io; // io module must be imported; println and others reside here
println("Hello, Fig!");
```
Run it:
```bash
./build/fig hello.fig
```
Or if you added `fig` to your PATH:
```bash
fig hello.fig
```
## 📊 Current Status & Roadmap
| Status | Module | Description |
|--------|----------------------------|-------------|
| ✅ | Frontend | Lexer, parser, AST, semantic analysis |
| ✅ | Core semantics | Variables, functions, closures, structs, interfaces, type system |
| ✅ | Built-in types | 13 built-in types (Any, Null, Int, String, Bool, Double, Function, StructType, StructInstance, List, Map, Module, InterfaceType) |
| 🚧 | Register VM | Original tree-walk interpreter deprecated; VM in development |
| 🚧 | Garbage Collection | Basic GC implemented, optimizing |
| 📝 | Standard Library | IO preliminarily available; more libraries to come |
| 📝 | FFI | Foreign function interface (C ABI) |
| 📝 | LSP | Language Server Protocol support |
## 📚 Documentation & Community
- **Documentation**: Check the [`/docs`](./docs) directory (continuously updated)
- **Example Code**: [`/ExampleCodes`](./ExampleCodes) contains various usage demonstrations
- **Contributing**: Issues and PRs are welcome at the main repository
- Report bugs
- Discuss features
- Improve documentation
- **License**: MIT © PuqiAR
- **Author**: PuqiAR · [im@puqiar.top](mailto:im@puqiar.top)
- **Links**:
- [Main Repository](https://git.fig-lang.cn/PuqiAR/Fig)
- [GitHub Mirror](https://github.com/PuqiAR/Fig)
---
**Fig** is evolving rapidly. If you're intrigued, give it a try or join us in shaping its future!

195
README_zh-CN.md Normal file
View File

@@ -0,0 +1,195 @@
# Fig
## tmd赶工代码质量太差了暑假我要重构
<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.5.0--alpha-yellow)
![C++](https://img.shields.io/badge/C++-99.5%25-blue)
![xmake](https://img.shields.io/badge/xmake-构建-green)
![LLVM](https://img.shields.io/badge/LLVM-clang%2021.1.8-purple)
![Platform](https://img.shields.io/badge/Windows%20·%20Linux%20·%20macOS-lightgrey)
![Performance](https://img.shields.io/badge/fib(30)-~28ms%20(i5--13490f)-brightgreen)
**Fig** 是一门动态类型与静态类型注解混合的编程语言,采用基于引用的值语义。它既保留了脚本语言的灵活性,又提供了可选的类型约束,让代码更健壮。
> ⚠️ **Fig 当前为 0.5.0-alpha 版本,语法和 API 仍可能发生变动。** 欢迎试用和反馈,但请勿用于生产环境。
```fig
// 试试 Fig 的感觉
var flexible = 10 // 动态类型,可以是任意值
flexible = "hello" // 没问题
var fixed := 20 // 固定为 Int 类型
// fixed = 3.14 // 错误!类型不匹配
func greet(name) => "Hello, " + name + "!"
println(greet("Fig")) // 输出: Hello, Fig!
```
## ✨ 核心特性
### 🎭 动态与静态的融合
变量默认是动态的(`Any` 类型),但你可以随时用 `:=``: Type` 锁定类型,享受静态检查的安全感。
```fig
var any; // 类型为 Any
any = 42; // 现在指向 Int
any = [1,2,3]; // 现在指向 List
var safe := 100; // 推断为 Int之后只能赋 Int 值
safe = 200; // ✅
// safe = "oops" // ❌ 编译错误
```
### 🔗 基于引用的值语义
所有对象都是不可变的,变量只是指向对象的“名字”。多个变量可以共享同一个对象,修改操作会创建新对象,但复杂类型(如列表)的修改会反映在所有引用上——这正是引用的含义。
```fig
var a := [1, 2, 3];
var b := a; // b 和 a 指向同一个列表
a[0] = 99; // 修改列表内容
println(b) // 输出: [99, 2, 3] —— 因为 b 也看到了变化
// 需要真正的副本?用深拷贝 (语法可能变动)
var c := new List{a}; // 深拷贝 a
c[0] = 0;
println(a) // 仍是 [99, 2, 3]
```
### 🏗 面向对象特性 (OOP)
Fig 提供基于结构体的轻量面向对象支持,通过接口实现多态。
- **结构体即类**:用 `struct` 定义,可包含字段和方法
- **访问控制**`public` 关键字,默认为私有
- **简洁构造**
```fig
var p1 := new Point{1, 2}; // 位置参数
var p2 := new Point{x: 2, y: 3}; // 命名参数
var x := 5, y := 10;
var p3 := new Point{y, x}; // 变量名简写,自动匹配字段
```
- **接口与实现**
```fig
interface Drawable { draw() -> String; }
struct Circle { radius: Double }
impl Drawable for Circle {
draw() => "⚪ 半径: " + radius;
}
```
- **无继承,用接口实现多态**(当前为隐式 `this`,未来可能改为显式 `self.xxx`
### 🧩 函数式特性
函数是一等公民,支持闭包和简洁的箭头语法。
```fig
func multiplier(factor) {
return func (n) => n * factor; // 闭包upvalue
}
var double = multiplier(2);
println(double(5)) // 输出: 10
```
### ⚡ 高性能实现
**递归 fib(30) 仅需 28ms** (i5-13490f, Windows 11, DDR4 2667, clang 21.1.8, libc++, C++23) —— 在动态语言中表现优异。
Fig 通过一系列底层优化实现高性能:
- **寄存器虚拟机**:替代树遍历解释器,执行效率大幅提升。
- **FastCall 优化**全局普通函数直接调用原型proto避免运行时解包开销传统调用需检查是否为函数对象并解包
- **Window Slicing**(源自 Lua栈滑动窗口技术高效管理函数调用和闭包upvalue减少内存分配。
- **Upvalue 机制**:轻量闭包实现,让函数式编程无性能负担。
- **Computed Goto**:利用 GCC/Clang 扩展,加速字节码分发。
- **NaN-Boxing**:高效存储和类型判断,提升动态类型性能。
#### 性能对比
以下为递归计算 fib(30) 的粗略对比(不同环境可能存在差异,仅供参考):
| 语言类型 | 语言 | 时间 (ms) | 备注 |
|--------|------|----------|------|
| AOT 编译 | C (clang -O2) | ~0.05 | 纳秒级,作为速度参照 |
| AOT 编译 | Rust (--release) | ~0.06 | 同上 |
| **动态语言** | **Fig** | **~28** | **寄存器 VM + 上述优化** |
| 动态语言 | Lua 5.4 | ~35 | 经典的动态语言 |
| 动态语言 | Python 3.11 | ~450 | CPython |
| 动态语言 | Node.js 20 | ~60 | V8 引擎 |
| 动态语言 | Ruby 3.2 | ~800 | CRuby |
*Fig 在动态语言阵营中表现出色,接近 Lua 和 Node.js 的水平,远超 Python 和 Ruby。*
## 🚀 快速上手
### 安装
#### 方法一:下载预编译二进制
从 [主仓库 Releases](https://git.fig-lang.cn/PuqiAR/Fig/releases) 或 [GitHub 镜像](https://github.com/PuqiAR/Fig/releases) 下载对应平台的二进制,解压后加入 PATH。
#### 方法二:使用 xmake 从源码编译
```bash
# 克隆主仓库GitHub 为镜像)
git clone https://git.fig-lang.cn/PuqiAR/Fig.git
cd Fig
# 安装 xmake如未安装https://xmake.io
# 编译(必须用 clang因为 computed goto 需要编译器支持)
xmake f --toolchain=clang
xmake
# 编译完成后,二进制位于 build/ 目录下
```
### 运行第一个 Fig 程序
创建文件 `hello.fig`
```fig
import std.io; // io 模块必须导入println 等函数位于其中
println("Hello, Fig!");
```
运行:
```bash
./build/fig hello.fig
```
或已加入 PATH
```bash
fig hello.fig
```
## 📊 当前状态与路线图
| 状态 | 模块 | 说明 |
|------|------|------|
| ✅ | 前端 | 词法/语法分析、AST、语义分析 |
| ✅ | 核心语义 | 变量、函数、闭包、结构体、接口、类型系统 |
| ✅ | 内置类型 | 13 种内置类型Any, Null, Int, String, Bool, Double, Function, StructType, StructInstance, List, Map, Module, InterfaceType |
| 🚧 | 寄存器虚拟机 | 原树遍历解释器已废弃VM 开发中 |
| 🚧 | 垃圾回收 | 基础 GC 已实现,正在优化 |
| 📝 | 标准库 | IO 已初步可用,更多库待完善 |
| 📝 | FFI | 外部函数接口C ABI |
| 📝 | LSP | 语言服务器协议支持 |
## 📚 文档与社区
- **文档**:查看 [`/docs`](./docs) 目录(持续更新)
- **示例代码**[`/ExampleCodes`](./ExampleCodes) 包含各种用法演示
- **贡献**:欢迎提交 Issue 或 PR主仓库
- 报告 bug
- 讨论特性
- 改进文档
- **许可证**MIT © PuqiAR
- **作者**PuqiAR · [im@puqiar.top](mailto:im@puqiar.top)
- **相关链接**
- [主仓库](https://git.fig-lang.cn/PuqiAR/Fig)
- [GitHub 镜像](https://github.com/PuqiAR/Fig)
---
**Fig** 还在快速迭代中,如果你对它感兴趣,不妨试一试,或者加入我们一起塑造它的未来!

View File

Before

Width:  |  Height:  |  Size: 633 KiB

After

Width:  |  Height:  |  Size: 633 KiB

33
src/Ast/Ast.hpp Normal file
View File

@@ -0,0 +1,33 @@
/*!
@file src/Ast/Ast.hpp
@brief Ast总链接
@author PuqiAR (im@puqiar.top)
@date 2026-02-17
*/
#pragma once
#include <Ast/Expr/CallExpr.hpp>
#include <Ast/Expr/IdentiExpr.hpp>
#include <Ast/Expr/IndexExpr.hpp>
#include <Ast/Expr/InfixExpr.hpp>
#include <Ast/Expr/LambdaExpr.hpp>
#include <Ast/Expr/LiteralExpr.hpp>
#include <Ast/Expr/MemberExpr.hpp>
#include <Ast/Expr/NewExpr.hpp>
#include <Ast/Expr/PrefixExpr.hpp>
#include <Ast/Expr/TernaryExpr.hpp>
#include <Ast/Expr/PostfixExpr.hpp>
#include <Ast/Stmt/ControlFlowStmts.hpp>
#include <Ast/Stmt/ExprStmt.hpp>
#include <Ast/Stmt/FnDefStmt.hpp>
#include <Ast/Stmt/IfStmt.hpp>
#include <Ast/Stmt/ImplStmt.hpp>
#include <Ast/Stmt/InterfaceDefStmt.hpp>
#include <Ast/Stmt/StructDefStmt.hpp>
#include <Ast/Stmt/ForStmt.hpp>
#include <Ast/Stmt/ImportStmt.hpp>
#include <Ast/Stmt/VarDecl.hpp>
#include <Ast/Stmt/WhileStmt.hpp>
#include <Ast/TypeExpr.hpp>

167
src/Ast/Base.hpp Normal file
View File

@@ -0,0 +1,167 @@
/*!
@file src/Ast/Base.hpp
@brief AstNode基类定义
@author PuqiAR (im@puqiar.top)
@date 2026-03-08
*/
#pragma once
#include <Core/SourceLocations.hpp>
#include <Deps/Deps.hpp>
#include <Sema/Type.hpp>
#include <cstdint>
namespace Fig
{
enum class AstType : std::uint8_t
{
AstNode,
Program,
Expr,
Stmt,
BlockStmt,
/* Expressions */
IdentiExpr,
LiteralExpr,
PrefixExpr,
InfixExpr,
IndexExpr,
CallExpr,
MemberExpr, // obj.prop
NewExpr, // new Point{}
LambdaExpr,
TernaryExpr, // cond ? then : else
PostfixExpr, // expr++ / expr--
/* Statements */
ExprStmt,
VarDecl,
IfStmt,
ElseIfStmt,
WhileStmt,
FnDefStmt,
StructDefStmt,
InterfaceDefStmt,
ImplStmt, // impl Document for File {}
ReturnStmt,
BreakStmt,
ContinueStmt,
ForStmt, // for loop
ImportStmt, // import
/* Type Expressions */
TypeExpr,
NamedTypeExpr, // 废弃,用 IdentiExpr/MemberExpr/ApplyExpr 替代
NullableTypeExpr, // 废弃,用 NullableExpr 替代
FnTypeExpr,
ApplyExpr, // 泛型实例化: List<Int>
NullableExpr, // 可空后缀: Int?
};
struct AstNode
{
AstType type = AstType::AstNode;
SourceLocation location;
virtual String toString() const = 0;
virtual ~AstNode() {};
};
struct Expr : public AstNode
{
// 语义分析后填充
Type resolvedType;
Expr()
{
type = AstType::Expr;
}
};
struct Stmt : public AstNode
{
bool isPublic = false;
Stmt()
{
type = AstType::Stmt;
}
};
struct Program final : public AstNode
{
DynArray<Stmt *> nodes;
Program()
{
type = AstType::Program;
}
virtual String toString() const override
{
return "<Program>";
}
};
struct BlockStmt final : public Stmt
{
DynArray<Stmt *> nodes;
BlockStmt()
{
type = AstType::BlockStmt;
}
virtual String toString() const override
{
return "<BlockStmt>";
}
};
// --- Type Expressions (inherit Expr — 类型即值) ---
struct TypeExpr : public Expr
{
TypeExpr() { type = AstType::TypeExpr; }
virtual ~TypeExpr() = default;
};
// ApplyExpr: 泛型实例化List<Int> → ApplyExpr(base, [Int])
struct ApplyExpr final : public Expr
{
Expr *base; // 基础类型表达式
DynArray<Expr *> args; // 泛型参数
ApplyExpr() { type = AstType::ApplyExpr; }
ApplyExpr(Expr *_base, DynArray<Expr *> _args, SourceLocation _loc) :
base(_base), args(std::move(_args))
{
type = AstType::ApplyExpr;
location = std::move(_loc);
}
virtual String toString() const override
{
String s = base->toString() + "<";
for (size_t i = 0; i < args.size(); ++i)
{
if (i) s += ", ";
s += args[i]->toString();
}
s += ">";
return s;
}
};
// NullableExpr: 可空后缀 Int? → NullableExpr(Int)
struct NullableExpr final : public Expr
{
Expr *inner;
NullableExpr() { type = AstType::NullableExpr; }
NullableExpr(Expr *_inner, SourceLocation _loc) : inner(_inner)
{
type = AstType::NullableExpr;
location = std::move(_loc);
}
virtual String toString() const override
{
return inner->toString() + "?";
}
};
} // namespace Fig

61
src/Ast/Expr/CallExpr.hpp Normal file
View File

@@ -0,0 +1,61 @@
/*!
@file src/Ast/Expr/CallExpr.hpp
@brief CallExpr等定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-17
*/
#pragma once
#include <Ast/Base.hpp>
#include <Deps/Deps.hpp>
namespace Fig
{
struct FnCallArgs
{
DynArray<Expr *> args;
size_t size() const
{
return args.size();
}
String toString() const
{
String str = "(";
for (const Expr *expr : args)
{
if (expr != args.front())
{
str += ", ";
}
str += expr->toString();
}
str += ")";
return str;
}
};
struct CallExpr final : public Expr
{
Expr *callee;
FnCallArgs args;
CallExpr()
{
type = AstType::CallExpr;
}
CallExpr(Expr *_callee, FnCallArgs _args, SourceLocation _location) : callee(_callee), args(std::move(_args))
{
type = AstType::CallExpr;
location = std::move(_location);
}
virtual String toString() const override
{
return std::format("<CallExpr: '{}{}'>", callee->toString(), args.toString());
}
};
} // namespace Fig

View File

@@ -0,0 +1,32 @@
/*!
@file src/Ast/Expr/IdentiExpr.hpp
@brief 标识符表达式定义
*/
#pragma once
#include <Ast/Base.hpp>
#include <Sema/Environment.hpp>
namespace Fig
{
struct IdentiExpr final : public Expr
{
String name;
Symbol *resolvedSymbol = nullptr; // 语义分析后填充Compiler 唯一的依赖
IdentiExpr()
{
type = AstType::IdentiExpr;
}
IdentiExpr(String _name, SourceLocation _location) : name(std::move(_name))
{
type = AstType::IdentiExpr;
location = std::move(_location);
}
virtual String toString() const override
{
return std::format("<IdentiExpr '{}'>", name);
}
};
} // namespace Fig

View File

@@ -0,0 +1,35 @@
/*!
@file src/Ast/Expr/IndexExpr.hpp
@brief IndexExpr定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-17
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct IndexExpr final : public Expr
{
Expr *base;
Expr *index;
IndexExpr()
{
type = AstType::IndexExpr;
}
IndexExpr(Expr *_base, Expr *_index) : base(_base), index(_index)
{
type = AstType::IndexExpr;
location = base->location;
}
virtual String toString() const override
{
return std::format("<IndexExpr: '{}[{}]'>", base->toString(), index->toString());
}
};
}; // namespace Fig

View File

@@ -0,0 +1,39 @@
/*!
@file src/Ast/Expr/InfixExpr.hpp
@brief 中缀表达式定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#pragma once
#include <Ast/Base.hpp>
#include <Ast/Operator.hpp>
#include <Deps/Deps.hpp>
namespace Fig
{
struct InfixExpr final : Expr
{
Expr *left;
BinaryOperator op;
Expr *right;
InfixExpr()
{
type = AstType::InfixExpr;
}
InfixExpr(Expr *_left, BinaryOperator _op, Expr *_right) :
left(_left), op(_op), right(_right)
{
type = AstType::InfixExpr;
location = _left->location;
}
virtual String toString() const override
{
return std::format("<InfixExpr: '{}' {} '{}'>", left->toString(), magic_enum::enum_name(op), right->toString());
}
};
}; // namespace Fig

View File

@@ -0,0 +1,70 @@
/*!
@file src/Ast/Expr/LambdaExpr.hpp
@brief Lambda表达式定义
*/
#pragma once
#include <Ast/Base.hpp>
#include <Ast/Stmt/FnDefStmt.hpp>
namespace Fig
{
struct LambdaExpr final : public Expr
{
// func (params) [-> return type] ([=> expr] / [ {stmt} ])
DynArray<Param *> params;
Expr *returnType;
AstNode *body; // expr/blockstmt
bool isExprBody;
DynArray<UpvalueInfo> upvalues;
LambdaExpr()
{
type = AstType::LambdaExpr;
}
LambdaExpr(
DynArray<Param *> _params,
Expr *_returnType,
AstNode *_body,
bool _isExprBody,
SourceLocation _location) :
params(std::move(_params)),
returnType(_returnType),
body(_body),
isExprBody(_isExprBody)
{
type = AstType::LambdaExpr;
location = std::move(_location);
}
virtual String toString() const override
{
String specifying = "<LambdaExpr 'func (";
for (auto &p : params)
{
if (p != params.front())
{
specifying += ", ";
}
specifying += p->toString();
}
if (isExprBody)
{
specifying += ") => ";
specifying += body->toString();
}
else
{
specifying += ") {";
specifying += body->toString();
specifying.push_back(U'}');
}
specifying += "'>";
return specifying;
}
};
}; // namespace Fig

View File

@@ -0,0 +1,27 @@
/*!
@file src/Ast/Expr/LiteralExpr.hpp
@brief 字面量表达式定义
*/
#pragma once
#include <Ast/Base.hpp>
#include <Token/Token.hpp>
namespace Fig
{
struct LiteralExpr final : public Expr
{
Token literal;
LiteralExpr() { type = AstType::LiteralExpr; }
LiteralExpr(const Token &_literal, SourceLocation _location) : literal(_literal)
{
type = AstType::LiteralExpr;
location = std::move(_location);
}
virtual String toString() const override {
return std::format("<LiteralExpr: {}>", magic_enum::enum_name(literal.type));
}
};
}

View File

@@ -0,0 +1,35 @@
/*!
@file src/Ast/Expr/MemberExpr.hpp
@brief 成员访问表达式定义obj.member
@author PuqiAR (im@puqiar.top)
@date 2026-03-08
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct MemberExpr final : public Expr
{
Expr *target; // 访问对象
String name; // 成员名字
MemberExpr()
{
type = AstType::MemberExpr;
}
MemberExpr(Expr *_t, String _n, SourceLocation _loc) : target(_t), name(std::move(_n))
{
type = AstType::MemberExpr;
location = std::move(_loc);
}
virtual String toString() const override
{
return std::format("<MemberExpr {}.{}>", target->toString(), name);
}
};
} // namespace Fig

50
src/Ast/Expr/NewExpr.hpp Normal file
View File

@@ -0,0 +1,50 @@
/*!
@file src/Ast/Expr/ObjectInitExpr.hpp
@brief 对象初始化表达式 AST 定义
@author PuqiAR (im@puqiar.top)
@date 2026-03-08
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct NewExpr final : public Expr
{
struct Arg
{
String name;
Expr *value;
};
Expr *typeExpr;
DynArray<Arg> args;
NewExpr()
{
type = AstType::NewExpr;
}
NewExpr(Expr *_te, DynArray<Arg> _args, SourceLocation _loc) :
typeExpr(_te), args(std::move(_args))
{
type = AstType::NewExpr;
location = std::move(_loc);
}
virtual String toString() const override
{
String res = "<NewExpr 'new " + typeExpr->toString() + "{";
for (size_t i = 0; i < args.size(); ++i)
{
if (!args[i].name.empty())
res += args[i].name + ": ";
res += args[i].value->toString();
if (i < args.size() - 1)
res += ", ";
}
res += "}'>";
return res;
}
};
} // namespace Fig

View File

@@ -0,0 +1,35 @@
/*!
@file src/Ast/Expr/PostfixExpr.hpp
@brief expr++ / expr--
@author PuqiAR (im@puqiar.top)
@date 2026-06-06
*/
#pragma once
#include <Ast/Base.hpp>
#include <Ast/Operator.hpp>
namespace Fig
{
struct PostfixExpr final : public Expr
{
UnaryOperator op;
Expr *operand;
PostfixExpr() { type = AstType::PostfixExpr; }
PostfixExpr(UnaryOperator _op, Expr *_operand) :
op(_op), operand(_operand)
{
type = AstType::PostfixExpr;
location = _operand->location;
}
virtual String toString() const override
{
return std::format("<PostfixExpr: {} '{}'>",
operand->toString(), magic_enum::enum_name(op));
}
};
} // namespace Fig

View File

@@ -0,0 +1,39 @@
/*!
@file src/Ast/Expr/PrefixExpr.hpp
@brief 前缀表达式定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#pragma once
#include <Ast/Operator.hpp>
#include <Ast/Base.hpp>
#include <Deps/Deps.hpp>
namespace Fig
{
struct PrefixExpr final : Expr
{
UnaryOperator op;
Expr *operand;
PrefixExpr()
{
type = AstType::PrefixExpr;
}
PrefixExpr(UnaryOperator _op, Expr *_operand) :
op(_op), operand(_operand)
{
type = AstType::PrefixExpr;
location = _operand->location;
}
virtual String toString() const override
{
return std::format("<PrefixExpr: {} '{}'>", magic_enum::enum_name(op), operand->toString());
}
};
};

View File

@@ -0,0 +1,35 @@
/*!
@file src/Ast/Expr/TernaryExpr.hpp
@brief cond ? then : else
@author PuqiAR (im@puqiar.top)
@date 2026-06-06
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct TernaryExpr final : public Expr
{
Expr *cond;
Expr *thenExpr;
Expr *elseExpr;
TernaryExpr() { type = AstType::TernaryExpr; }
TernaryExpr(Expr *_cond, Expr *_then, Expr *_else, SourceLocation _loc) :
cond(_cond), thenExpr(_then), elseExpr(_else)
{
type = AstType::TernaryExpr;
location = std::move(_loc);
}
virtual String toString() const override
{
return std::format("<TernaryExpr: {} ? {} : {}>",
cond->toString(), thenExpr->toString(), elseExpr->toString());
}
};
} // namespace Fig

193
src/Ast/Operator.cpp Normal file
View File

@@ -0,0 +1,193 @@
/*!
@file src/Ast/Operator.cpp
@brief 运算符定义内函数实现
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#include <Ast/Operator.hpp>
namespace Fig
{
HashMap<TokenType, UnaryOperator> &GetUnaryOpMap()
{
static HashMap<TokenType, UnaryOperator> unaryOpMap{
{TokenType::Tilde, UnaryOperator::BitNot},
{TokenType::Minus, UnaryOperator::Negate},
{TokenType::Not, UnaryOperator::Not},
{TokenType::Ampersand, UnaryOperator::AddressOf},
{TokenType::DoublePlus, UnaryOperator::Increment},
{TokenType::DoubleMinus, UnaryOperator::Decrement},
};
return unaryOpMap;
}
HashMap<TokenType, BinaryOperator> &GetBinaryOpMap()
{
static HashMap<TokenType, BinaryOperator> binaryOpMap{
{TokenType::Plus, BinaryOperator::Add},
{TokenType::Minus, BinaryOperator::Subtract},
{TokenType::Asterisk, BinaryOperator::Multiply},
{TokenType::Slash, BinaryOperator::Divide},
{TokenType::Percent, BinaryOperator::Modulo},
{TokenType::Equal, BinaryOperator::Equal},
{TokenType::NotEqual, BinaryOperator::NotEqual},
{TokenType::Less, BinaryOperator::Less},
{TokenType::Greater, BinaryOperator::Greater},
{TokenType::LessEqual, BinaryOperator::LessEqual},
{TokenType::GreaterEqual, BinaryOperator::GreaterEqual},
{TokenType::Is, BinaryOperator::Is},
{TokenType::And, BinaryOperator::LogicalAnd},
{TokenType::Or, BinaryOperator::LogicalOr},
{TokenType::DoubleAmpersand, BinaryOperator::LogicalAnd},
{TokenType::DoublePipe, BinaryOperator::LogicalOr},
{TokenType::Power, BinaryOperator::Power},
{TokenType::Assign, BinaryOperator::Assign},
{TokenType::PlusEqual, BinaryOperator::AddAssign},
{TokenType::MinusEqual, BinaryOperator::SubAssign},
{TokenType::AsteriskEqual, BinaryOperator::MultiplyAssign},
{TokenType::SlashEqual, BinaryOperator::DivideAssign},
{TokenType::PercentEqual, BinaryOperator::ModuloAssign},
{TokenType::Caret, BinaryOperator::BitXor},
{TokenType::CaretEqual, BinaryOperator::BitXorAssign},
{TokenType::Pipe, BinaryOperator::BitOr},
{TokenType::Ampersand, BinaryOperator::BitAnd},
{TokenType::ShiftLeft, BinaryOperator::ShiftLeft},
{TokenType::ShiftRight, BinaryOperator::ShiftRight},
{TokenType::As, BinaryOperator::As},
};
return binaryOpMap;
}
// 赋值 < 三元 < 逻辑或 < 逻辑与 < 位运算 < 比较 < 位移 < 加减 < 乘除 < 幂 < 一元 < 成员访问 < (后缀)
/*
暂划分:
二元运算符0 - 20000
一元运算符20001 - 40000
后缀/成员/其他40001 - 60001
*/
HashMap<UnaryOperator, BindingPower> &GetUnaryOpBindingPowerMap()
{
static HashMap<UnaryOperator, BindingPower> unbpm{
{UnaryOperator::BitNot, 20001},
{UnaryOperator::Negate, 20001},
{UnaryOperator::Not, 20001},
{UnaryOperator::AddressOf, 20001},
{UnaryOperator::Increment, 20001},
{UnaryOperator::Decrement, 20001},
};
return unbpm;
}
HashMap<BinaryOperator, BindingPower> &GetBinaryOpBindingPowerMap()
{
static HashMap<BinaryOperator, BindingPower> bnbpm{
{BinaryOperator::Assign, 100},
{BinaryOperator::AddAssign, 100},
{BinaryOperator::SubAssign, 100},
{BinaryOperator::MultiplyAssign, 100},
{BinaryOperator::DivideAssign, 100},
{BinaryOperator::ModuloAssign, 100},
{BinaryOperator::BitXorAssign, 100},
{BinaryOperator::LogicalOr, 500},
{BinaryOperator::LogicalAnd, 550},
{BinaryOperator::BitOr, 1000},
{BinaryOperator::BitXor, 1100},
{BinaryOperator::BitAnd, 1200},
{BinaryOperator::Equal, 2000},
{BinaryOperator::NotEqual, 2000},
{BinaryOperator::Less, 2100},
{BinaryOperator::LessEqual, 2100},
{BinaryOperator::Greater, 2100},
{BinaryOperator::GreaterEqual, 2100},
{BinaryOperator::Is, 2100},
{BinaryOperator::As, 2100},
{BinaryOperator::ShiftLeft, 3000},
{BinaryOperator::ShiftRight, 3000},
{BinaryOperator::Add, 4000},
{BinaryOperator::Subtract, 4000},
{BinaryOperator::Multiply, 4500},
{BinaryOperator::Divide, 4500},
{BinaryOperator::Modulo, 4500},
{BinaryOperator::Power, 5000},
};
return bnbpm;
}
BindingPower GetUnaryOpRBp(UnaryOperator op)
{
return GetUnaryOpBindingPowerMap().at(op);
}
BindingPower GetBinaryOpLBp(BinaryOperator op)
{
return GetBinaryOpBindingPowerMap().at(op);
}
BindingPower GetBinaryOpRBp(BinaryOperator op)
{
switch (op)
{
/*
右结合,左绑定力 >= 右
a = b = c
a = (b = c)
*/
case BinaryOperator::Assign:
case BinaryOperator::AddAssign:
case BinaryOperator::SubAssign:
case BinaryOperator::MultiplyAssign:
case BinaryOperator::DivideAssign:
case BinaryOperator::ModuloAssign:
case BinaryOperator::BitXorAssign:
case BinaryOperator::Power: return GetBinaryOpLBp(op) - 1;
case BinaryOperator::As: return GetBinaryOpLBp(op) + 1;
default:
/*
左结合, 左绑定力 < 右
a * b * c
(a * b) * c
*/
return GetBinaryOpLBp(op) + 1;
}
}
bool IsTokenOp(TokenType type, bool binary /* = true*/)
{
if (binary)
{
return GetBinaryOpMap().contains(type);
}
return GetUnaryOpMap().contains(type);
}
UnaryOperator TokenToUnaryOp(const Token &token)
{
return GetUnaryOpMap().at(token.type);
}
BinaryOperator TokenToBinaryOp(const Token &token)
{
return GetBinaryOpMap().at(token.type);
}
}; // namespace Fig

96
src/Ast/Operator.hpp Normal file
View File

@@ -0,0 +1,96 @@
/*!
@file src/Ast/Operator.hpp
@brief 运算符定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#pragma once
#include <cstdint>
#include <Deps/Deps.hpp>
#include <Token/Token.hpp>
namespace Fig
{
enum class UnaryOperator : std::uint8_t
{
BitNot, // 位运算取反 ~
Negate, // 取反 -
Not, // 逻辑非 ! / not
AddressOf, // 取引用 &
Increment, // ++
Decrement, // --
Count // 哨兵,(int) Count 获得运算符数量注意enum必须从 0 开始且不中断)
};
enum class BinaryOperator : std::uint8_t
{
Add, // 加 +
Subtract, // 减 -
Multiply, // 乘 *
Divide, // 除 /
Modulo, // 取模 %
Equal, // 等于 ==
NotEqual, // 不等于 !=
Less, // 小于 <
Greater, // 大于 >
LessEqual, // 小于等于 <=
GreaterEqual, // 大于等于 >=
Is, // is操作符
LogicalAnd, // 逻辑与 && / and
LogicalOr, // 逻辑或 || / or
Power, // 幂运算 **
Assign, // 赋值(修改) =
AddAssign, // +=
SubAssign, // -=
MultiplyAssign, // *=
DivideAssign, // /=
ModuloAssign, // %=
BitXorAssign, // ^=
// 位运算
BitAnd, // 按位与 &
BitOr, // 按位或 |
BitXor, // 异或 ^
ShiftLeft, // 左移
ShiftRight, // 右移
As, // as
// 成员访问
MemberAccess, // .
Count // 哨兵,(int) Count 获得运算符数量注意enum必须从 0 开始且不中断)
};
constexpr unsigned int GetOperatorsSize()
{
// 获取全部运算符的数量
return static_cast<std::uint8_t>(UnaryOperator::Count) + static_cast<std::uint8_t>(BinaryOperator::Count);
}
using BindingPower = unsigned int;
HashMap<TokenType, UnaryOperator> &GetUnaryOpMap();
HashMap<TokenType, BinaryOperator> &GetBinaryOpMap();
HashMap<UnaryOperator, BindingPower> &GetUnaryOpBindingPowerMap();
HashMap<BinaryOperator, BindingPower> &GetBinaryOpBindingPowerMap();
BindingPower GetUnaryOpRBp(UnaryOperator);
BindingPower GetBinaryOpLBp(BinaryOperator);
BindingPower GetBinaryOpRBp(BinaryOperator);
bool IsTokenOp(TokenType type, bool binary = true);
UnaryOperator TokenToUnaryOp(const Token &);
BinaryOperator TokenToBinaryOp(const Token &);
}; // namespace Fig

View File

@@ -0,0 +1,72 @@
/*!
@file src/Ast/Stmt/ControlFlowStmts
@brief 控制流语句 return, break, continue 定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-27
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct ReturnStmt final : public Stmt
{
Expr *value;
ReturnStmt()
{
type = AstType::ReturnStmt;
}
ReturnStmt(Expr *_value, SourceLocation _location) : value(_value)
{
type = AstType::ReturnStmt;
location = std::move(_location);
}
virtual String toString() const override
{
return std::format("<ReturnStmt '{}'>", value->toString());
}
};
struct BreakStmt final : public Stmt
{
BreakStmt()
{
type = AstType::BreakStmt;
}
BreakStmt(SourceLocation _location)
{
type = AstType::BreakStmt;
location = std::move(_location);
}
virtual String toString() const override
{
return "<BreakStmt>";
}
};
struct ContinueStmt final : public Stmt
{
ContinueStmt()
{
type = AstType::ContinueStmt;
}
ContinueStmt(SourceLocation _location)
{
type = AstType::ContinueStmt;
location = std::move(_location);
}
virtual String toString() const override
{
return "<ContinueStmt>";
}
};
}; // namespace Fig

35
src/Ast/Stmt/ExprStmt.hpp Normal file
View File

@@ -0,0 +1,35 @@
/*!
@file src/Ast/Stmt/ExprStmt.hpp
@brief ExprStmt定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-19
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct ExprStmt final : public Stmt
{
Expr *expr;
ExprStmt()
{
type = AstType::ExprStmt;
}
ExprStmt(Expr *_expr) :
expr(_expr)
{
type = AstType::ExprStmt;
location = _expr->location;
}
virtual String toString() const override
{
return std::format("<ExprStmt: {}>", expr->toString());
}
};
}

View File

@@ -0,0 +1,51 @@
/*!
@file src/Ast/Stmt/FnDefStmt.hpp
@brief 函数定义 AST 节点
*/
#pragma once
#include <Ast/Base.hpp>
#include <Sema/Environment.hpp>
#include <Bytecode/Bytecode.hpp>
namespace Fig
{
struct Param : public AstNode {
String name;
Expr *typeSpecifier;
Expr *defaultValue;
Type resolvedType;
Param() { type = AstType::AstNode; }
virtual ~Param() = default;
};
struct PosParam final : public Param {
PosParam(String _n, Expr *_ts, Expr *_dv, SourceLocation _loc) {
name = std::move(_n); typeSpecifier = _ts; defaultValue = _dv; location = std::move(_loc);
}
virtual String toString() const override { return name; }
};
struct FnDefStmt final : public Stmt {
String name;
DynArray<Param *> params;
Expr *returnTypeSpecifier;
BlockStmt *body;
Type resolvedReturnType;
Symbol *resolvedSymbol = nullptr; // 连接物理符号
int protoIndex = -1; // 在CompiledModule扁平化protos的下标
DynArray<UpvalueInfo> upvalues;
FnDefStmt() { type = AstType::FnDefStmt; }
FnDefStmt(bool _p, String _n, DynArray<Param *> _pa, Expr *_rt, BlockStmt *_b, SourceLocation _loc)
: name(std::move(_n)), params(std::move(_pa)), returnTypeSpecifier(_rt), body(_b)
{
type = AstType::FnDefStmt; isPublic = _p; location = std::move(_loc);
}
virtual String toString() const override {
return std::format("<FnDefStmt '{}'>", name);
}
};
}

32
src/Ast/Stmt/ForStmt.hpp Normal file
View File

@@ -0,0 +1,32 @@
/*!
@file src/Ast/Stmt/ForStmt.hpp
@brief for loop
@author PuqiAR (im@puqiar.top)
@date 2026-06-06
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct ForStmt final : public Stmt
{
Stmt *init;
Expr *cond;
Expr *step;
BlockStmt *body;
ForStmt() { type = AstType::ForStmt; }
ForStmt(Stmt *_init, Expr *_cond, Expr *_step, BlockStmt *_body, SourceLocation _loc) :
init(_init), cond(_cond), step(_step), body(_body)
{
type = AstType::ForStmt;
location = std::move(_loc);
}
virtual String toString() const override { return "<ForStmt>"; }
};
} // namespace Fig

69
src/Ast/Stmt/IfStmt.hpp Normal file
View File

@@ -0,0 +1,69 @@
/*!
@file src/Ast/Stmt/IfStmt.hpp
@brief IfStmt定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-20
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct ElseIfStmt final : public Stmt
{
Expr *cond;
BlockStmt *consequent;
ElseIfStmt()
{
type = AstType::ElseIfStmt;
}
ElseIfStmt(Expr *_cond, BlockStmt *_consequent, SourceLocation _location) :
cond(_cond), consequent(_consequent)
{
type = AstType::ElseIfStmt;
location = std::move(_location);
}
virtual String toString() const override
{
return std::format("<ElseIf ({}) then {}>", cond->toString(), consequent->toString());
}
};
struct IfStmt final : public Stmt
{
Expr *cond;
BlockStmt *consequent;
DynArray<ElseIfStmt *> elifs;
BlockStmt *alternate;
IfStmt()
{
type = AstType::IfStmt;
}
IfStmt(Expr *_cond,
BlockStmt *_consequent,
DynArray<ElseIfStmt *> _elifs,
BlockStmt *_alternate,
SourceLocation _location) :
cond(_cond), consequent(_consequent), elifs(std::move(_elifs)), alternate(_alternate)
{
type = AstType::IfStmt;
location = std::move(_location);
}
virtual String toString() const override
{
return std::format("<If ({}) then {}, else if * {}, else {}>",
cond->toString(),
consequent->toString(),
elifs.size(),
alternate->toString());
}
};
}; // namespace Fig

32
src/Ast/Stmt/ImplStmt.hpp Normal file
View File

@@ -0,0 +1,32 @@
/*!
@file src/Ast/Stmt/ImplStmt.hpp
@brief 实现块 AST 节点
*/
#pragma once
#include <Ast/Base.hpp>
#include <Ast/Stmt/FnDefStmt.hpp>
namespace Fig
{
struct ImplStmt final : public Stmt
{
Expr *interfaceType;
Expr *structType;
DynArray<FnDefStmt *> methods;
ImplStmt(Expr *_it, Expr *_st, DynArray<FnDefStmt *> _m, SourceLocation _loc) :
interfaceType(_it), structType(_st), methods(std::move(_m))
{
type = AstType::ImplStmt;
location = std::move(_loc);
}
virtual String toString() const override
{
String detail =
(interfaceType ? interfaceType->toString() + " for " : "") + structType->toString();
return std::format("<ImplStmt '{}'>", detail);
}
};
} // namespace Fig

View File

@@ -0,0 +1,33 @@
/*!
@file src/Ast/Stmt/ImportStmt.hpp
@brief import
@author PuqiAR (im@puqiar.top)
@date 2026-06-06
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct ImportStmt final : public Stmt
{
String path;
bool isFileImport;
ImportStmt() { type = AstType::ImportStmt; }
ImportStmt(String _path, bool _isFileImport, SourceLocation _loc) :
path(std::move(_path)), isFileImport(_isFileImport)
{
type = AstType::ImportStmt;
location = std::move(_loc);
}
virtual String toString() const override
{
return std::format("<ImportStmt '{}'{}>", path, isFileImport ? " [file]" : "");
}
};
} // namespace Fig

View File

@@ -0,0 +1,39 @@
/*!
@file src/Ast/Stmt/InterfaceDefStmt.hpp
@brief 接口定义 AST 节点
@author PuqiAR (im@puqiar.top)
@date 2026-03-08
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct InterfaceDefStmt final : public Stmt
{
struct Method
{
String name;
DynArray<Expr*> params;
Expr *retType;
SourceLocation location;
};
bool isPublic;
String name;
DynArray<Method> methods;
InterfaceDefStmt() { type = AstType::InterfaceDefStmt; }
InterfaceDefStmt(bool _p, String _n, DynArray<Method> _m, SourceLocation _loc)
: isPublic(_p), name(std::move(_n)), methods(std::move(_m))
{
type = AstType::InterfaceDefStmt;
location = std::move(_loc);
}
virtual String toString() const override { return "<InterfaceDefStmt>"; }
};
} // namespace Fig

View File

@@ -0,0 +1,59 @@
/*!
@file src/Ast/Stmt/StructDefStmt.hpp
@brief 结构体定义 AST 节点
*/
#pragma once
#include <Ast/Base.hpp>
#include <Ast/Stmt/FnDefStmt.hpp>
namespace Fig
{
struct StructDefStmt final : public Stmt
{
struct Field
{
bool isPublic;
bool typeInfer;
String name;
Expr *type;
Expr *initExpr;
};
bool isPublic;
String name;
DynArray<String> typeParameters;
DynArray<Field> fields;
DynArray<FnDefStmt *> methods;
StructDefStmt()
{
type = AstType::StructDefStmt;
}
StructDefStmt(bool _p,
String _n,
DynArray<String> _tp,
DynArray<Field> _f,
DynArray<FnDefStmt *> _m,
SourceLocation _loc) :
isPublic(_p),
name(std::move(_n)),
typeParameters(std::move(_tp)),
fields(std::move(_f)),
methods(std::move(_m))
{
type = AstType::StructDefStmt;
location = std::move(_loc);
}
virtual String toString() const override
{
String detail = name;
if (!typeParameters.empty())
{
detail += "<...>";
}
return std::format("<StructDefStmt '{}'>", detail);
}
};
} // namespace Fig

47
src/Ast/Stmt/VarDecl.hpp Normal file
View File

@@ -0,0 +1,47 @@
/*!
@file src/Ast/Stmt/VarDecl.hpp
@brief VarDecl定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-17
*/
#pragma once
#include <Ast/Base.hpp>
#include <Deps/Deps.hpp>
namespace Fig
{
struct VarDecl final : public Stmt
{
String name;
Expr *typeSpecifier;
bool isInfer; // 是否用了 := 类型推断
Expr *initExpr;
int localId = -1;
VarDecl()
{
type = AstType::VarDecl;
}
VarDecl(bool _isPublic, String _name, Expr *_typeSpecifier, bool _isInfer, Expr *_initExpr, SourceLocation _location) :
name(std::move(_name)),
typeSpecifier(_typeSpecifier),
isInfer(_isInfer),
initExpr(_initExpr) // location 指向关键字 var/const位置
{
type = AstType::VarDecl;
isPublic = _isPublic;
location = std::move(_location);
}
virtual String toString() const override
{
const String &typeSpecifierString = (typeSpecifier ? typeSpecifier->toString() : "Any");
const String &initExprString = (initExpr ? initExpr->toString() : "(disprovided)");
return std::format("<VarDecl {}: {} = {}>", name, typeSpecifierString, initExprString);
}
};
}; // namespace Fig

View File

@@ -0,0 +1,37 @@
/*!
@file src/Ast/Stmt/WhileStmt.hpp
@brief WhileStmt定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-24
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct WhileStmt final : public Stmt
{
Expr *cond;
BlockStmt *body;
WhileStmt()
{
type = AstType::WhileStmt;
}
WhileStmt(Expr *_cond, BlockStmt *_body, SourceLocation _location) :
cond(_cond),
body(_body)
{
type = AstType::WhileStmt;
location = std::move(_location);
}
virtual String toString() const override
{
return std::format("<WhileStmt ({}) {{{}}}>", cond->toString(), body->toString());
}
};
}; // namespace Fig

100
src/Ast/TypeExpr.hpp Normal file
View File

@@ -0,0 +1,100 @@
/*!
@file src/Ast/TypeExpr.hpp
@brief 类型表达式 AST 定义:支持泛型与空安全
*/
#pragma once
#include <Ast/Base.hpp>
namespace Fig
{
struct NamedTypeExpr final : public TypeExpr
{
DynArray<String> path;
DynArray<Expr *> arguments;
NamedTypeExpr()
{
type = AstType::NamedTypeExpr;
}
NamedTypeExpr(DynArray<String> _p, DynArray<Expr *> _args, SourceLocation _loc) :
path(std::move(_p)), arguments(std::move(_args))
{
type = AstType::NamedTypeExpr;
location = std::move(_loc);
}
virtual String toString() const override
{
String detail = "";
for (size_t i = 0; i < path.size(); ++i)
{
detail += path[i];
if (i < path.size() - 1)
detail += ".";
}
if (!arguments.empty())
{
detail += "<";
for (size_t i = 0; i < arguments.size(); ++i)
{
detail += arguments[i]->toString();
if (i < arguments.size() - 1)
detail += ", ";
}
detail += ">";
}
return std::format("<NamedTypeExpr '{}'>", detail);
}
};
struct NullableTypeExpr final : public TypeExpr
{
Expr *inner;
NullableTypeExpr(Expr *_inner, SourceLocation _loc) : inner(_inner)
{
type = AstType::NullableTypeExpr;
location = std::move(_loc);
}
virtual String toString() const override
{
return std::format("<NullableTypeExpr '{}?'>", inner->toString());
}
};
struct FnTypeExpr final : public TypeExpr
{
// func (paratypes...) -> return_type
DynArray<Expr *> paraTypes;
Expr *returnType;
FnTypeExpr(DynArray<Expr *> _paraTypes, Expr *_returnType) :
paraTypes(std::move(_paraTypes)), returnType(_returnType)
{
type = AstType::FnTypeExpr;
}
virtual String toString() const override
{
String detail = "<FnTypeExpr 'func (";
for (auto &pt : paraTypes)
{
if (pt != paraTypes.front())
{
detail += ", ";
}
detail += pt->toString();
}
detail += ") -> ";
detail += returnType->toString();
detail += "'>";
return detail;
}
};
} // namespace Fig

111
src/Bytecode/Bytecode.hpp Normal file
View File

@@ -0,0 +1,111 @@
/*!
@file src/Bytecode/Bytecode.hpp
@brief 字节码Bytecode定义
*/
#pragma once
#include <Deps/Deps.hpp>
#include <Object/ObjectBase.hpp>
#include <Core/SourceLocations.hpp>
#include <cstdint>
namespace Fig
{
using Instruction = std::uint32_t;
enum class OpCode : std::uint8_t
{
Exit,
Exit_MaxRecursionDepthExceeded,
LoadK,
LoadTrue,
LoadFalse,
LoadNull,
FastCall,
Call,
Return,
LoadFn,
Jmp,
JmpIfFalse,
Mov,
Add,
Sub,
Mul,
Div,
Mod,
BitXor,
IntFastAdd,
IntFastSub,
IntFastMul,
IntFastDiv,
Equal,
NotEqual,
Greater,
Less,
GreaterEqual,
LessEqual,
GetGlobal,
SetGlobal,
GetUpval,
SetUpval,
Copy,
Count
};
namespace Op
{
[[nodiscard]] inline constexpr Instruction iABx(OpCode op, std::uint8_t a, std::uint16_t bx)
{
return static_cast<std::uint32_t>(op) | (static_cast<std::uint32_t>(a) << 8)
| (static_cast<std::uint32_t>(bx) << 16);
}
[[nodiscard]] inline constexpr Instruction iABC(OpCode op, std::uint8_t a, std::uint8_t b, std::uint8_t c)
{
return static_cast<std::uint32_t>(op) | (static_cast<std::uint32_t>(a) << 8)
| (static_cast<std::uint32_t>(b) << 16) | (static_cast<std::uint32_t>(c) << 24);
}
[[nodiscard]] inline constexpr Instruction iAsBx(OpCode op, std::uint8_t a, std::int16_t sbx)
{
return static_cast<std::uint32_t>(op) | (static_cast<std::uint32_t>(a) << 8)
| (static_cast<std::uint32_t>(static_cast<std::uint16_t>(sbx)) << 16);
}
} // namespace Op
struct UpvalueInfo
{
uint8_t index;
bool isLocal;
};
struct Proto
{
String name;
DynArray<Instruction> code;
DynArray<SourceLocation *> locations;
DynArray<Value> constants;
DynArray<UpvalueInfo> upvalues;
uint8_t maxRegisters = 0;
uint8_t numParams = 0;
};
struct CompiledModule
{
DynArray<Proto *> protos;
};
} // namespace Fig

View File

@@ -0,0 +1,102 @@
/*!
@file src/Bytecode/Disassembler.cpp
@brief 字节码反汇编器实现
*/
#include <Bytecode/Disassembler.hpp>
#include <iostream>
#include <format>
namespace Fig
{
void Disassembler::DisassembleModule(const CompiledModule *module, std::ostream &stream)
{
if (!module) return;
stream << "--- Module Disassembly ---" << std::endl;
for (auto *proto : module->protos)
{
DisassembleProto(proto);
}
}
void Disassembler::DisassembleProto(const Proto *proto, std::ostream &stream)
{
if (!proto) return;
stream << std::format("\n--- Proto: {} (Regs: {}, Params: {}) ---\n",
proto->name, proto->maxRegisters, proto->numParams);
for (size_t i = 0; i < proto->code.size(); ++i)
{
Instruction inst = proto->code[i];
OpCode op = static_cast<OpCode>(inst & 0xFF);
uint8_t a = (inst >> 8) & 0xFF;
stream << std::format("[{:04}] {:<12} ", i, magic_enum::enum_name(op));
Format fmt = GetFormat(op);
if (fmt == Format::ABC)
{
uint8_t b = (inst >> 16) & 0xFF;
uint8_t c = (inst >> 24) & 0xFF;
stream << std::format("A:{:<3} B:{:<3} C:{:<3}", a, b, c);
}
else if (fmt == Format::ABx)
{
uint16_t bx = (inst >> 16) & 0xFFFF;
stream << std::format("A:{:<3} Bx:{:<5}", a, bx);
// 自动关联常量池
if (op == OpCode::LoadK && bx < proto->constants.size())
{
stream << std::format(" ; {}", proto->constants[bx].ToString());
}
}
else if (fmt == Format::AsBx)
{
int16_t sbx = static_cast<int16_t>((inst >> 16) & 0xFFFF);
stream << std::format("A:{:<3} sBx:{:<5}", a, sbx);
// 计算跳转绝对地址
if (op == OpCode::Jmp || op == OpCode::JmpIfFalse)
{
stream << std::format(" ; to [{:04}]", i + sbx + 1);
}
}
stream << "\n";
}
if (!proto->constants.empty())
{
stream << "Constants:\n";
for (size_t i = 0; i < proto->constants.size(); ++i)
{
stream << std::format(" [{}] {}\n", i, proto->constants[i].ToString());
}
}
stream << "--- End Disassembly ---\n";
}
Disassembler::Format Disassembler::GetFormat(OpCode op)
{
switch (op)
{
case OpCode::LoadK:
case OpCode::Mov:
case OpCode::GetGlobal:
case OpCode::SetGlobal:
case OpCode::LoadFn:
return Format::ABx;
case OpCode::Exit:
case OpCode::Exit_MaxRecursionDepthExceeded:
case OpCode::Jmp:
case OpCode::JmpIfFalse:
return Format::AsBx;
default:
return Format::ABC;
}
}
} // namespace Fig

View File

@@ -0,0 +1,35 @@
/*!
@file src/Bytecode/Disassembler.hpp
@brief 字节码反汇编器:物理还原指令语义
@author PuqiAR (im@puqiar.top)
@date 2026-03-08
*/
#pragma once
#include <Bytecode/Bytecode.hpp>
#include <Compiler/Compiler.hpp>
#include <Core/CoreIO.hpp>
namespace Fig
{
class Disassembler
{
public:
// 反汇编整个模块
static void DisassembleModule(const CompiledModule *module, std::ostream & = CoreIO::GetStdOut());
// 反汇编单个函数原型
static void DisassembleProto(const Proto *proto, std::ostream & = CoreIO::GetStdOut());
private:
enum class Format
{
ABC,
ABx,
AsBx
};
static Format GetFormat(OpCode op);
};
} // namespace Fig

View File

@@ -0,0 +1,67 @@
#include <Parser/Parser.hpp>
#include <Sema/Analyzer.hpp>
#include <Compiler/Compiler.hpp>
#include <Bytecode/Disassembler.hpp>
#include <iostream>
#include <filesystem>
int main()
{
using namespace Fig;
String filePath = "T:/Files/Maker/Code/MyCodingLanguage/The Fig Project/Fig/tests/Compiler/test_basic.fig";
if (!std::filesystem::exists(filePath.toStdString()))
{
std::cerr << "CRITICAL: Test file not found at: " << filePath << "\n";
return 1;
}
SourceManager sm{filePath};
String source = sm.Read();
if (!sm.read || source.length() == 0)
{
std::cerr << "CRITICAL: SourceManager failed to read: " << filePath << "\n";
return 1;
}
Lexer lexer(source, filePath);
Diagnostics diagnostics;
Parser parser(lexer, sm, filePath, diagnostics);
diagnostics.EmitAll(sm);
auto pRes = parser.Parse();
if (!pRes)
{
ReportError(pRes.error(), sm);
return 1;
}
Program *program = *pRes;
std::cout << "Successfully parsed nodes: " << program->nodes.size() << "\n";
Analyzer analyzer(sm);
auto aRes = analyzer.Analyze(program);
if (!aRes)
{
ReportError(aRes.error(), sm);
return 1;
}
Diagnostics diag;
Compiler compiler(sm, diag);
auto cRes = compiler.Compile(program);
if (!cRes)
{
ReportError(cRes.error(), sm);
return 1;
}
// 使用正式的 Disassembler
Disassembler::DisassembleModule(*cRes);
return 0;
}

142
src/Compiler/Compiler.cpp Normal file
View File

@@ -0,0 +1,142 @@
/*!
@file src/Compiler/Compiler.cpp
@brief 编译器主逻辑实现:物理 Bootstrapper 与双步扫描
*/
#include <Ast/Stmt/FnDefStmt.hpp>
#include <Compiler/Compiler.hpp>
namespace Fig
{
Result<CompiledModule *, Error> Compiler::Compile(Program *program)
{
module = new CompiledModule();
if (program->nodes.empty())
{
return module;
}
// 预留 Protos[0] 给 Bootstrapper
Proto *bootProto = new Proto();
bootProto->name = "[bootstrapper]";
module->protos.push_back(bootProto);
int initIdx = -1;
int mainIdx = -1;
SourceLocation *mainFnLoc = nullptr;
SourceLocation *initFnLoc = nullptr;
// 预扫描顶层函数
for (auto *stmt : program->nodes)
{
if (stmt->type == AstType::FnDefStmt)
{
auto *f = static_cast<FnDefStmt *>(stmt);
int idx = (int) module->protos.size();
Proto *p = new Proto();
p->name = f->name;
p->numParams = (uint8_t) f->params.size();
p->maxRegisters = p->numParams;
f->protoIndex = idx;
module->protos.push_back(p);
// 连接物理符号到索引
if (f->resolvedSymbol)
{
f->resolvedSymbol->index = idx;
}
if (f->name == "init")
{
initIdx = idx;
initFnLoc = &stmt->location;
}
if (f->name == "main")
{
mainIdx = idx;
mainFnLoc = &stmt->location;
}
}
}
// Bootstrapper 中编译所有语句
FuncState bootState(bootProto, nullptr);
current = &bootState;
for (auto *stmt : program->nodes)
{
auto res = compileStmt(stmt);
if (!res)
{
return std::unexpected(res.error());
}
}
// 发射 Bootstrapper 引导指令
if (initIdx != -1)
{
emit(Op::iABC(OpCode::FastCall, (uint8_t) initIdx, 0, 0), initFnLoc);
}
if (mainIdx != -1)
{
emit(Op::iABC(OpCode::FastCall, (uint8_t) mainIdx, 0, 0), mainFnLoc);
}
emit(Op::iAsBx(OpCode::Exit, 0, 0), &program->nodes.back()->location);
return module;
}
int Compiler::getGlobalID(const String &name)
{
if (globalIDMap.contains(name))
return globalIDMap[name];
int id = (int) globalIDMap.size();
globalIDMap[name] = id;
return id;
}
Result<Register, Error> Compiler::allocateReg(const SourceLocation &loc)
{
if (current->freereg >= MAX_REGISTERS)
{
return std::unexpected(
Error(ErrorType::RegisterOverflow, "too many registers", "", loc));
}
Register reg = current->freereg++;
if (reg >= current->proto->maxRegisters)
{
current->proto->maxRegisters = reg + 1;
}
return reg;
}
void Compiler::freeReg(Register count)
{
if (current->freereg >= count)
{
current->freereg -= count;
}
}
int Compiler::addConstant(Value val)
{
if (current->constantMap.contains(val))
return current->constantMap[val];
int idx = (int) current->proto->constants.size();
current->proto->constants.push_back(val);
current->constantMap[val] = idx;
return idx;
}
void Compiler::emit(Instruction inst, SourceLocation *loc)
{
current->proto->code.push_back(inst);
current->proto->locations.push_back(loc);
}
} // namespace Fig

55
src/Compiler/Compiler.hpp Normal file
View File

@@ -0,0 +1,55 @@
/*!
@file src/Compiler/Compiler.hpp
@brief 编译器定义:物理直连 Bootstrapper
*/
#pragma once
#include <Ast/Ast.hpp>
#include <Bytecode/Bytecode.hpp>
#include <Error/Diagnostics.hpp>
#include <Object/Object.hpp>
namespace Fig
{
using Register = std::uint8_t;
class Compiler
{
private:
static constexpr Register MAX_REGISTERS = 250;
static constexpr Register NO_REG = 255;
struct FuncState
{
Proto *proto;
Register freereg;
FuncState *enclosing;
HashMap<Value, int> constantMap;
FuncState(Proto *p, FuncState *e)
: proto(p), freereg(p->numParams), enclosing(e) {}
};
FuncState *current = nullptr;
CompiledModule *module = nullptr;
SourceManager &manager;
Diagnostics &diag;
HashMap<String, int> globalIDMap;
int getGlobalID(const String& name);
Result<Register, Error> allocateReg(const SourceLocation &loc);
void freeReg(Register count = 1);
int addConstant(Value val);
void emit(Instruction inst, SourceLocation *loc);
Result<void, Error> compileStmt(Stmt *stmt);
Result<Register, Error> compileExpr(Expr *expr, Register target = NO_REG);
public:
Compiler(SourceManager &m, Diagnostics &d) : manager(m), diag(d) {}
Result<CompiledModule *, Error> Compile(Program *program);
};
}

View File

@@ -0,0 +1,477 @@
/*!
@file src/Compiler/ExprCompiler.cpp
@brief 表达式编译
*/
#include <Ast/Expr/CallExpr.hpp>
#include <Ast/Expr/IdentiExpr.hpp>
#include <Ast/Expr/InfixExpr.hpp>
#include <Ast/Expr/LiteralExpr.hpp>
#include <Compiler/Compiler.hpp>
#include <charconv>
#include <limits>
#include <system_error>
namespace Fig
{
static Result<Value, Error> parsePhysicalNumber(const String &raw, const SourceLocation &loc)
{
char buffer[128];
int j = 0;
bool isFloat = false;
for (size_t i = 0; i < raw.length() && j < 127; ++i)
{
char32_t c = raw[i];
if (c == '_')
continue;
// 检查开头的无效字符
if (j == 0 && (c == '.' || c == 'e' || c == 'E'))
return std::unexpected(
Error(ErrorType::SyntaxError, "unexpected leading character", "", loc));
if (c == '.' || c == 'e' || c == 'E')
isFloat = true;
buffer[j++] = (char) c;
}
buffer[j] = '\0';
// 检查16进制/2进制前缀
bool isHexOrBin = false;
int base = 10;
const char *start = buffer;
if (j >= 2 && buffer[0] == '0')
{
if (buffer[1] == 'x' || buffer[1] == 'X')
{
base = 16;
start += 2;
isHexOrBin = true;
}
else if (buffer[1] == 'b' || buffer[1] == 'B')
{
base = 2;
start += 2;
isHexOrBin = true;
}
}
if (isFloat)
{
// 如果既有浮点标记又是0x开头可能是16进制浮点
auto fmt =
(isHexOrBin && base == 16) ? std::chars_format::hex : std::chars_format::general;
double dVal;
auto [ptr, ec] = std::from_chars(start, buffer + j, dVal, fmt);
if (ec != std::errc() || ptr != buffer + j)
return std::unexpected(
Error(ErrorType::SyntaxError, "invalid float literal", "", loc));
return Value::FromDouble(dVal);
}
else if (isHexOrBin)
{
// 16进制或2进制整数
int64_t iVal;
auto [ptr, ec] = std::from_chars(start, buffer + j, iVal, base);
if (ec != std::errc() || ptr != buffer + j)
return std::unexpected(
Error(ErrorType::SyntaxError, "integer overflow or invalid literal", "", loc));
if (iVal >= std::numeric_limits<int32_t>::min()
&& iVal <= std::numeric_limits<int32_t>::max())
{
return Value::FromInt(static_cast<int32_t>(iVal));
}
else
{
return Value::FromDouble(static_cast<double>(iVal));
}
}
else
{
// 10进制数字可能是整数或浮点数
double dVal;
auto [ptr, ec] = std::from_chars(start, buffer + j, dVal, std::chars_format::general);
if (ec != std::errc() || ptr != buffer + j)
return std::unexpected(
Error(ErrorType::SyntaxError, "invalid number literal", "", loc));
// 检查是否是整数(没有小数部分且不超出int32范围)
if (dVal == std::floor(dVal) && dVal >= std::numeric_limits<int32_t>::min()
&& dVal <= std::numeric_limits<int32_t>::max())
{
return Value::FromInt(static_cast<int32_t>(dVal));
}
else
{
return Value::FromDouble(dVal);
}
}
}
Result<Register, Error> Compiler::compileExpr(Expr *expr, Register target)
{
if (expr == nullptr)
{
return std::unexpected(
Error(ErrorType::InternalError, "null expr in compiler", "", {}));
}
switch (expr->type)
{
case AstType::LiteralExpr: {
auto *l = static_cast<LiteralExpr *>(expr);
Register r = (target == NO_REG) ? *allocateReg(l->location) : target;
const Token &tok = l->literal;
if (tok.type == TokenType::LiteralNumber)
{
auto vRes =
parsePhysicalNumber(manager.GetSub(tok.index, tok.length), l->location);
if (!vRes)
return std::unexpected(vRes.error());
emit(
Op::iABx(OpCode::LoadK, r, static_cast<uint16_t>(addConstant(*vRes))),
&l->location);
}
else if (tok.type == TokenType::LiteralString)
{
int kIdx = addConstant(Value::GetNullInstance()); // TODO: String 支持
emit(Op::iABx(OpCode::LoadK, r, static_cast<uint16_t>(kIdx)), &l->location);
}
else if (tok.type == TokenType::LiteralNull)
{
emit(Op::iABC(OpCode::LoadNull, r, 0, 0), &l->location);
}
else if (tok.type == TokenType::LiteralTrue)
{
emit(Op::iABC(OpCode::LoadTrue, r, 0, 0), &l->location);
}
else if (tok.type == TokenType::LiteralFalse)
{
emit(Op::iABC(OpCode::LoadFalse, r, 0, 0), &l->location);
}
return r;
}
case AstType::IdentiExpr: {
auto *i = static_cast<IdentiExpr *>(expr);
Symbol *sym = i->resolvedSymbol;
if (sym->location == SymbolLocation::Local)
{
// no-copy for temp eval
if (target == NO_REG)
return static_cast<Register>(sym->index);
// 仅在被强制指定目标(如参数装填)时发射搬运指令
if (target != sym->index)
{
emit(
Op::iABx(OpCode::Mov, target, static_cast<uint16_t>(sym->index)),
&i->location);
}
return target;
}
Register r = (target == NO_REG) ? *allocateReg(i->location) : target;
if (sym->location == SymbolLocation::Upvalue)
{
emit(
Op::iABC(OpCode::GetUpval, r, static_cast<uint8_t>(sym->index), 0),
&i->location);
}
else if (sym->location == SymbolLocation::Global)
{
int gId = getGlobalID(i->name);
emit(Op::iABx(OpCode::GetGlobal, r, static_cast<uint16_t>(gId)), &i->location);
}
return r;
}
case AstType::CallExpr: {
auto *c = static_cast<CallExpr *>(expr);
Register mark = current->freereg; // 记录调用前的栈顶水位
Register baseReg = current->freereg; // 锁定滑窗基址
// 连续装填参数,占据 baseReg, baseReg+1, baseReg+2...
for (auto *arg : c->args.args)
{
auto allocRes = allocateReg(arg->location);
if (!allocRes)
{
return allocRes;
}
Register argTarget = *allocRes;
auto res = compileExpr(arg, argTarget);
if (!res)
return std::unexpected(res.error());
}
bool isGlobalFastCall = false;
if (c->callee->type == AstType::IdentiExpr)
{
auto *id = static_cast<IdentiExpr *>(c->callee);
// 只有在全局区的函数,才能使用 FastCall
if (id->resolvedSymbol->location == SymbolLocation::Global)
{
isGlobalFastCall = true;
int protoIdx = id->resolvedSymbol->index;
emit(
Op::iABC(
OpCode::FastCall,
static_cast<uint8_t>(protoIdx),
baseReg,
static_cast<uint8_t>(c->args.args.size())),
&c->location);
}
}
if (!isGlobalFastCall)
{
// 动态闭包调用
// 先获取闭包对象所在的物理寄存器
auto r_fn = compileExpr(c->callee);
if (!r_fn)
return std::unexpected(r_fn.error());
// 使用动态 Call 指令RA 是指向堆闭包的寄存器
emit(
Op::iABC(
OpCode::Call,
*r_fn,
baseReg,
static_cast<uint8_t>(c->args.args.size())),
&c->location);
}
// free arg temps
current->freereg = mark;
// 目若 target 未指定allocateReg 将复用 baseReg实现零开销回写
Register r_dest;
if (target == NO_REG)
{
auto res = allocateReg(c->location);
if (!res)
return std::unexpected(res.error());
r_dest = *res;
}
else
{
r_dest = target;
}
if (r_dest != baseReg)
{
emit(Op::iABx(OpCode::Mov, r_dest, baseReg), &c->location);
}
return r_dest;
}
case AstType::InfixExpr: {
auto *in = static_cast<InfixExpr *>(expr);
if (in->op == BinaryOperator::Assign)
{
auto r_val = compileExpr(in->right, target);
if (!r_val)
return std::unexpected(r_val.error());
if (in->left->type == AstType::IdentiExpr)
{
auto *lid = static_cast<IdentiExpr *>(in->left);
Symbol *sym = lid->resolvedSymbol;
if (sym->location == SymbolLocation::Local)
{
emit(
Op::iABx(OpCode::Mov, static_cast<Register>(sym->index), *r_val),
&lid->location);
}
else if (sym->location == SymbolLocation::Upvalue)
{
emit(
Op::iABC(
OpCode::SetUpval, *r_val, static_cast<Register>(sym->index), 0),
&lid->location);
}
else
{
emit(
Op::iABx(
OpCode::SetGlobal,
*r_val,
static_cast<uint16_t>(getGlobalID(lid->name))),
&lid->location);
}
}
return r_val;
}
Register mark = current->freereg; // mark
auto r_l = compileExpr(in->left);
if (!r_l)
return std::unexpected(r_l.error());
auto r_r = compileExpr(in->right);
if (!r_r)
return std::unexpected(r_r.error());
bool isInt = in->left->resolvedType.is(TypeTag::Int)
&& in->right->resolvedType.is(TypeTag::Int);
OpCode op;
switch (in->op)
{
case BinaryOperator::Add: {
op = (isInt ? (OpCode::IntFastAdd) : (OpCode::Add));
break;
}
case BinaryOperator::Subtract: {
op = (isInt ? (OpCode::IntFastSub) : (OpCode::Sub));
break;
}
case BinaryOperator::Multiply: {
op = (isInt ? (OpCode::IntFastMul) : (OpCode::Mul));
break;
}
case BinaryOperator::Divide: {
op = (isInt ? (OpCode::IntFastDiv) : (OpCode::Div));
break;
}
case BinaryOperator::Modulo: {
op = OpCode::Mod;
break;
}
case BinaryOperator::BitXor: {
op = OpCode::BitXor;
break;
}
case BinaryOperator::Equal: {
op = OpCode::Equal;
break;
}
case BinaryOperator::NotEqual: {
op = OpCode::NotEqual;
break;
}
case BinaryOperator::Greater: {
op = OpCode::Greater;
break;
}
case BinaryOperator::Less: {
op = OpCode::Less;
break;
}
case BinaryOperator::GreaterEqual: {
op = OpCode::GreaterEqual;
break;
}
case BinaryOperator::LessEqual: {
op = OpCode::LessEqual;
break;
}
default: {
return std::unexpected(Error(
ErrorType::InternalError,
"unsupported binary operator",
"",
in->location));
}
}
// 释放左右操作数产生的临时寄存器
current->freereg = mark;
// 复用已释放的物理槽位存放计算结果
Register r_d;
if (target == NO_REG)
{
auto res = allocateReg(in->location);
if (!res)
return std::unexpected(res.error());
r_d = *res;
}
else
{
r_d = target;
}
emit(Op::iABC(op, r_d, *r_l, *r_r), &in->location);
return r_d;
}
case AstType::LambdaExpr: {
auto l = static_cast<LambdaExpr *>(expr);
Proto *proto = new Proto;
proto->name = l->toString();
FuncState fs(proto, current);
FuncState *old = current;
current = &fs;
if (l->isExprBody)
{
auto result = compileExpr(static_cast<Expr *>(l->body));
if (!result)
{
return result;
}
emit(Op::iABC(OpCode::Return, *result, 0, 0), &l->location);
}
else
{
auto result = compileStmt(static_cast<BlockStmt *>(l->body));
if (!result)
{
return std::unexpected(result.error());
}
auto _r = allocateReg(l->body->location);
if (!_r)
{
return _r;
}
Register r = *_r;
emit(Op::iABC(OpCode::LoadNull, r, 0, 0), &l->body->location);
emit(Op::iABC(OpCode::Return, r, 0, 0), &l->body->location);
}
int protoIndex = (int) module->protos.size();
module->protos.push_back(proto);
current = old;
if (target == NO_REG)
{
auto _r = allocateReg(expr->location);
if (!_r)
{
return _r;
}
emit(Op::iABx(OpCode::LoadFn, *_r, protoIndex), &l->body->location);
return *_r;
}
else
{
emit(Op::iABx(OpCode::LoadFn, target, protoIndex), &l->body->location);
}
return target;
}
default: break;
}
return std::unexpected(
Error(ErrorType::InternalError, "unsupported expr", "", expr->location));
}
} // namespace Fig

View File

@@ -0,0 +1,269 @@
/*!
@file src/Compiler/StmtCompiler.cpp
@brief 语句编译
*/
#include <Ast/Stmt/FnDefStmt.hpp>
#include <Ast/Stmt/IfStmt.hpp>
#include <Ast/Stmt/VarDecl.hpp>
#include <Ast/Stmt/WhileStmt.hpp>
#include <Compiler/Compiler.hpp>
namespace Fig
{
Result<void, Error> Compiler::compileStmt(Stmt *stmt)
{
if (stmt == nullptr)
{
return {};
}
switch (stmt->type)
{
case AstType::BlockStmt: {
auto *b = static_cast<BlockStmt *>(stmt);
for (auto *n : b->nodes)
{
auto res = compileStmt(n);
if (!res)
return res;
}
break;
}
case AstType::VarDecl: {
auto *v = static_cast<VarDecl *>(stmt);
if (current->enclosing == nullptr) // 处理全局变量
{
Register mark = current->freereg; // mark
auto regRes = compileExpr(v->initExpr);
if (!regRes)
return std::unexpected(regRes.error());
emit(Op::iABx(OpCode::SetGlobal,
*regRes,
static_cast<uint16_t>(getGlobalID(v->name))),
&v->location);
current->freereg = mark; // 释放初始化表达式的临时占用
}
else
{
// 抬升水位,锁死局部变量的物理槽位
Register targetReg = static_cast<Register>(v->localId);
while (current->freereg <= targetReg)
{
auto allocRes = allocateReg(v->location);
if (!allocRes)
{
return std::unexpected(allocRes.error());
}
}
auto regRes = compileExpr(v->initExpr, targetReg);
if (!regRes)
return std::unexpected(regRes.error());
}
break;
}
case AstType::FnDefStmt: {
auto *f = static_cast<FnDefStmt *>(stmt);
if (f->protoIndex == -1) // 闭包环境没有被扫到
{
Proto *newProto = new Proto();
newProto->name = f->name;
newProto->numParams = static_cast<uint8_t>(f->params.size());
newProto->maxRegisters = newProto->numParams; // sync
f->protoIndex = static_cast<int>(module->protos.size());
module->protos.push_back(newProto);
}
// 获取静态原型 (flat protos)
Proto *p = module->protos[f->protoIndex];
p->upvalues = f->upvalues;
FuncState fs(p, current);
FuncState *old = current;
current = &fs;
auto res = compileStmt(f->body);
if (!res)
return res;
if (p->code.empty() || static_cast<OpCode>(p->code.back() & 0xFF) != OpCode::Return)
{
emit(Op::iABC(OpCode::Return, 0, 0, 0), &f->location);
}
current = old;
// 如果是局部闭包,在当前栈帧分配寄存器并生成 LoadFn
if (f->resolvedSymbol->location == SymbolLocation::Local)
{
Register targetReg = static_cast<Register>(f->resolvedSymbol->index);
while (current->freereg <= targetReg)
{
auto allocRes = allocateReg(f->location);
if (!allocRes)
return std::unexpected(allocRes.error());
}
// 生成 LoadFn: RA = 目标寄存器, Bx = Proto 在 module->protos 中的绝对索引
emit(Op::iABx(OpCode::LoadFn, targetReg, static_cast<uint16_t>(f->protoIndex)),
&f->location);
}
else if (f->resolvedSymbol->location == SymbolLocation::Global)
{
auto result = allocateReg(f->location);
if (!result)
{
return std::unexpected(result.error());
}
Register r = *result;
emit(Op::iABx(OpCode::LoadFn, r, static_cast<uint16_t>(f->protoIndex)),
&f->location);
int gId = getGlobalID(f->name);
emit(Op::iABx(OpCode::SetGlobal, r, static_cast<std::uint16_t>(gId)),
&f->location);
freeReg();
}
break;
}
case AstType::IfStmt: {
auto *i = static_cast<IfStmt *>(stmt);
DynArray<int> exitJumps;
Register mark = current->freereg; // mark
auto r_cond = compileExpr(i->cond);
if (!r_cond)
return std::unexpected(r_cond.error());
int jmpToNext = static_cast<int>(current->proto->code.size());
emit(Op::iAsBx(OpCode::JmpIfFalse, *r_cond, 0), &i->location);
current->freereg = mark; // 回收条件表达式临时槽位
if (auto r = compileStmt(i->consequent); !r)
return r;
exitJumps.push_back(static_cast<int>(current->proto->code.size()));
emit(Op::iAsBx(OpCode::Jmp, 0, 0), &i->location);
int targetIdx = static_cast<int>(current->proto->code.size());
current->proto->code[jmpToNext] = Op::iAsBx(
OpCode::JmpIfFalse, *r_cond, static_cast<int16_t>(targetIdx - jmpToNext - 1));
for (auto *elif : i->elifs)
{
Register elifMark = current->freereg;
auto ec = compileExpr(elif->cond);
if (!ec)
return std::unexpected(ec.error());
int nextElif = static_cast<int>(current->proto->code.size());
emit(Op::iAsBx(OpCode::JmpIfFalse, *ec, 0), &elif->location);
current->freereg = elifMark; // 回收 elif 临时槽位
if (auto r = compileStmt(elif->consequent); !r)
return r;
exitJumps.push_back(static_cast<int>(current->proto->code.size()));
emit(Op::iAsBx(OpCode::Jmp, 0, 0), &elif->location);
int target = static_cast<int>(current->proto->code.size());
current->proto->code.resize(nextElif);
current->proto->code[nextElif] = Op::iAsBx(
OpCode::JmpIfFalse, *ec, static_cast<int16_t>(target - nextElif - 1));
current->proto->locations.resize(nextElif);
current->proto->locations[nextElif] = &elif->location;
}
if (i->alternate)
{
if (auto r = compileStmt(i->alternate); !r)
return r;
}
int endIdx = static_cast<int>(current->proto->code.size());
for (int pos : exitJumps)
{
current->proto->code[pos] =
Op::iAsBx(OpCode::Jmp, 0, static_cast<int16_t>(endIdx - pos - 1));
}
break;
}
case AstType::WhileStmt: {
auto *w = static_cast<WhileStmt *>(stmt);
int startIdx = static_cast<int>(current->proto->code.size());
Register mark = current->freereg; // mark
auto r_cond = compileExpr(w->cond);
if (!r_cond)
return std::unexpected(r_cond.error());
int exitJmpIdx = static_cast<int>(current->proto->code.size());
emit(Op::iAsBx(OpCode::JmpIfFalse, *r_cond, 0), &w->location);
current->freereg = mark; // 回收循环条件临时槽位
if (auto r = compileStmt(w->body); !r)
return r;
int backJmpIdx = static_cast<int>(current->proto->code.size());
emit(Op::iAsBx(OpCode::Jmp, 0, static_cast<int16_t>(startIdx - backJmpIdx - 1)),
&w->location);
int endIdx = static_cast<int>(current->proto->code.size());
current->proto->code[exitJmpIdx] = Op::iAsBx(
OpCode::JmpIfFalse, *r_cond, static_cast<int16_t>(endIdx - exitJmpIdx - 1));
break;
}
case AstType::ReturnStmt: {
auto *rs = static_cast<ReturnStmt *>(stmt);
Register mark = current->freereg; // mark
Register retReg;
if (rs->value)
{
auto r = compileExpr(rs->value);
if (!r)
return std::unexpected(r.error());
retReg = *r;
}
else
{
auto r = allocateReg(rs->location);
if (!r)
return std::unexpected(r.error());
emit(Op::iABC(OpCode::LoadNull, *r, 0, 0), &rs->location);
retReg = *r;
}
emit(Op::iABC(OpCode::Return, retReg, 0, 0), &rs->location);
current->freereg = mark; // 回收返回值计算的占用
break;
}
case AstType::ExprStmt: {
Register mark = current->freereg; // mark
auto reg = compileExpr(static_cast<ExprStmt *>(stmt)->expr);
if (!reg)
return std::unexpected(reg.error());
current->freereg = mark; // 彻底抛弃孤立表达式的副作用
break;
}
default: break;
}
return {};
}
} // namespace Fig

13
src/Core/Core.hpp Normal file
View File

@@ -0,0 +1,13 @@
/*!
@file src/Core/Core.hpp
@brief Core总合集
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#pragma once
#include <Core/CoreInfos.hpp>
#include <Core/CoreIO.hpp>
#include <Core/RuntimeTime.hpp>
#include <Core/SourceLocations.hpp>

49
src/Core/CoreIO.cpp Normal file
View File

@@ -0,0 +1,49 @@
/*!
@file src/Core/CoreIO.cpp
@brief 标准输入输出链接
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#include <Core/CoreIO.hpp>
#include <Core/CoreInfos.hpp>
#ifdef _WIN32
#include <windows.h>
#endif
namespace Fig::CoreIO
{
#if defined(_WIN32) || defined(__APPLE__) || defined (__linux__) || defined (__unix__)
std::ostream &GetStdOut()
{
static std::ostream &out = std::cout;
return out;
}
std::ostream &GetStdErr()
{
static std::ostream &err = std::cerr;
return err;
}
std::ostream &GetStdLog()
{
static std::ostream &log = std::clog;
return log;
}
std::istream &GetStdCin()
{
static std::istream &cin = std::cin;
return cin;
}
#else
// link
#endif
void InitConsoleIO()
{
#ifdef _WIN32
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
#endif
}
};

20
src/Core/CoreIO.hpp Normal file
View File

@@ -0,0 +1,20 @@
/*!
@file src/Core/CoreIO.hpp
@brief 标准输入输出链接定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#pragma once
#include <iostream>
namespace Fig::CoreIO
{
std::ostream &GetStdOut();
std::ostream &GetStdErr();
std::ostream &GetStdLog();
std::istream &GetStdCin();
void InitConsoleIO();
}; // namespace Fig::CoreIO

143
src/Core/CoreInfos.hpp Normal file
View File

@@ -0,0 +1,143 @@
/*!
@file src/Core/CoreInfos.hpp
@brief 核心系统信息
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#pragma once
#include <Deps/String/String.hpp>
#include <cstdint>
#include <string_view>
#define __FCORE_VERSION "0.5.0-alpha"
#if defined(_WIN32)
#define __FCORE_PLATFORM "Windows"
#elif defined(__APPLE__)
#define __FCORE_PLATFORM "Apple"
#elif defined(__linux__)
#define __FCORE_PLATFORM "Linux"
#elif defined(__unix__)
#define __FCORE_PLATFORM "Unix"
#else
#define __FCORE_PLATFORM "Unknown"
#endif
#if defined(__GNUC__)
#if defined(_WIN32)
#if defined(__clang__)
#define __FCORE_COMPILER "llvm-mingw"
#else
#define __FCORE_COMPILER "MinGW"
#endif
#else
#define __FCORE_COMPILER "GCC"
#endif
#elif defined(__clang__)
#define __FCORE_COMPILER "Clang"
#elif defined(_MSC_VER)
#define __FCORE_COMPILER "MSVC"
#else
#define __FCORE_COMPILER "Unknown"
#endif
#if SIZE_MAX == 18446744073709551615ull
#define __FCORE_ARCH "64"
#else
#define __FCORE_ARCH "86"
#endif
#define __FCORE_LINK_DEPS
namespace Fig
{
namespace Core
{
inline constexpr std::string_view VERSION = __FCORE_VERSION;
inline constexpr std::string_view LICENSE = "MIT";
inline constexpr std::string_view AUTHOR = "PuqiAR";
inline constexpr std::string_view PLATFORM = __FCORE_PLATFORM;
inline constexpr std::string_view COMPILER = __FCORE_COMPILER;
inline constexpr std::string_view COMPILE_TIME = __FCORE_COMPILE_TIME;
inline constexpr std::string_view ARCH = __FCORE_ARCH;
const std::string LOGO =
R"(
)";
const std::string LICENSE_TEXT =
R"(
MIT License (Fig)
Copyright (c) 2026 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:
- 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.
- 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.
)";
}; // namespace Core
}; // namespace Fig

25
src/Core/RuntimeTime.cpp Normal file
View File

@@ -0,0 +1,25 @@
/*!
@file src/Core/RuntimeTime.cpp
@brief 系统时间库实现(steady_clock)
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#include <Core/RuntimeTime.hpp>
#include <cassert>
namespace Fig::Time
{
Clock::time_point start_time;
void init()
{
static bool flag = false;
if (flag)
{
assert(false);
}
start_time = Clock::now();
flag = true;
}
};

17
src/Core/RuntimeTime.hpp Normal file
View File

@@ -0,0 +1,17 @@
/*!
@file src/Core/RuntimeTime.hpp
@brief 系统时间库定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#pragma once
#include <chrono>
namespace Fig::Time
{
using Clock = std::chrono::steady_clock;
extern Clock::time_point start_time; // since process start
void init();
};

View File

@@ -0,0 +1,59 @@
/*!
@file src/Core/SourceLocations
@brief SourcePosition + SourceLocation定义全局代码定位
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#pragma once
#include <Deps/Deps.hpp>
namespace Fig
{
struct SourcePosition
{
size_t line, column, tok_length;
SourcePosition() { line = column = tok_length = 0; }
SourcePosition(size_t _line, size_t _column, size_t _tok_length)
{
line = _line;
column = _column;
tok_length = _tok_length;
}
};
struct SourceLocation
{
SourcePosition sp;
Deps::String fileName;
Deps::String packageName;
Deps::String functionName;
SourceLocation() {}
SourceLocation(SourcePosition _sp,
Deps::String _fileName,
Deps::String _packageName,
Deps::String _functionName)
{
sp = std::move(_sp);
fileName = std::move(_fileName);
packageName = std::move(_packageName);
functionName = std::move(_functionName);
}
SourceLocation(size_t line,
size_t column,
size_t tok_length,
Deps::String _fileName,
Deps::String _packageName,
Deps::String _functionName)
{
sp = SourcePosition(line, column, tok_length);
fileName = std::move(_fileName);
packageName = std::move(_packageName);
functionName = std::move(_functionName);
}
};
}; // namespace Fig

34
src/Deps/Deps.hpp Normal file
View File

@@ -0,0 +1,34 @@
/*!
@file src/Deps/Deps.hpp
@brief 依赖库集合
@author PuqiAR (im@puqiar.top)
@date 2026-02-13
*/
#pragma once
#include <Core/CoreInfos.hpp>
#include <Deps/DynArray/DynArray.hpp>
#include <Deps/HashMap/HashMap.hpp>
#include <Deps/String/CharUtils.hpp>
#include <Deps/String/String.hpp>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <expected>
#include <format>
namespace Fig
{
#ifdef __FCORE_LINK_DEPS
using Deps::String;
using Deps::HashMap;
using Deps::CharUtils;
using Deps::DynArray;
template <class _Tp, class _Err>
using Result = std::expected<_Tp, _Err>;
#endif
}; // namespace Fig

View File

@@ -0,0 +1,14 @@
/*!
@file src/Deps/DynArray/DynArray
@brief 依赖库DynArray定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#include <vector>
namespace Fig::Deps
{
template<class _Tp, class _Allocator = std::allocator<_Tp>>
using DynArray = std::vector<_Tp, _Allocator>;
};

View File

@@ -0,0 +1,20 @@
/*!
@file src/Deps/HashMap/HashMap.hpp
@brief 依赖库HashMap定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-13
*/
#pragma once
#include <unordered_map>
namespace Fig::Deps
{
template <class _Key,
class _Tp,
class _Hash = std::hash<_Key>,
class _Pred = std::equal_to<_Key>,
class _Alloc = std::allocator<std::pair<const _Key, _Tp> >>
using HashMap = std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>;
};

View File

@@ -0,0 +1,130 @@
/*!
@file src/Deps/String/CharUtils.hpp
@brief char32_t type实现
@author PuqiAR (im@puqiar.top)
@date 2026-02-13
*/
#pragma once
namespace Fig::Deps
{
class CharUtils
{
public:
using U32 = char32_t;
// ===== 基础 =====
static constexpr bool isValidScalar(U32 c) noexcept { return c <= 0x10FFFF && !(c >= 0xD800 && c <= 0xDFFF); }
static constexpr bool isAscii(U32 c) noexcept { return c <= 0x7F; }
static constexpr bool isControl(U32 c) noexcept { return (c <= 0x1F) || (c == 0x7F); }
static constexpr bool isPrintable(U32 c) noexcept { return !isControl(c); }
// ===== ASCII 分类 =====
static constexpr bool isAsciiLower(U32 c) noexcept { return c >= U'a' && c <= U'z'; }
static constexpr bool isAsciiUpper(U32 c) noexcept { return c >= U'A' && c <= U'Z'; }
static constexpr bool isAsciiAlpha(U32 c) noexcept { return isAsciiLower(c) || isAsciiUpper(c); }
static constexpr bool isAsciiDigit(U32 c) noexcept { return c >= U'0' && c <= U'9'; }
static constexpr bool isAsciiHexDigit(U32 c) noexcept
{
return isAsciiDigit(c) || (c >= U'a' && c <= U'f') || (c >= U'A' && c <= U'F');
}
static constexpr bool isAsciiSpace(U32 c) noexcept { return c == U' ' || (c >= 0x09 && c <= 0x0D); }
static constexpr bool isAsciiPunct(U32 c) noexcept
{
return (c >= 33 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 96) || (c >= 123 && c <= 126);
}
// ===== Unicode White_Space =====
static constexpr bool isSpace(U32 c) noexcept
{
if (isAscii(c)) return isAsciiSpace(c);
switch (c)
{
case 0x0085:
case 0x00A0:
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200A:
case 0x2028:
case 0x2029:
case 0x202F:
case 0x205F:
case 0x3000: return true;
}
return false;
}
// ===== Unicode Decimal_Number =====
static constexpr bool isDigit(U32 c) noexcept
{
if (isAscii(c)) return isAsciiDigit(c);
return (c >= 0x0660 && c <= 0x0669) || (c >= 0x06F0 && c <= 0x06F9) || (c >= 0x0966 && c <= 0x096F)
|| (c >= 0x09E6 && c <= 0x09EF) || (c >= 0x0A66 && c <= 0x0A6F) || (c >= 0x0AE6 && c <= 0x0AEF)
|| (c >= 0x0B66 && c <= 0x0B6F) || (c >= 0x0BE6 && c <= 0x0BEF) || (c >= 0x0C66 && c <= 0x0C6F)
|| (c >= 0x0CE6 && c <= 0x0CEF) || (c >= 0x0D66 && c <= 0x0D6F) || (c >= 0x0E50 && c <= 0x0E59)
|| (c >= 0x0ED0 && c <= 0x0ED9) || (c >= 0x0F20 && c <= 0x0F29) || (c >= 0x1040 && c <= 0x1049)
|| (c >= 0x17E0 && c <= 0x17E9) || (c >= 0x1810 && c <= 0x1819) || (c >= 0xFF10 && c <= 0xFF19);
}
// ===== Unicode Letter =====
static constexpr bool isAlpha(U32 c) noexcept
{
if (isAscii(c)) return isAsciiAlpha(c);
return (c >= 0x00C0 && c <= 0x02AF) || (c >= 0x0370 && c <= 0x052F) || (c >= 0x0530 && c <= 0x058F)
|| (c >= 0x0590 && c <= 0x05FF) || (c >= 0x0600 && c <= 0x06FF) || (c >= 0x0900 && c <= 0x097F)
|| (c >= 0x3040 && c <= 0x30FF) || (c >= 0x3100 && c <= 0x312F) || (c >= 0x4E00 && c <= 0x9FFF)
|| (c >= 0xAC00 && c <= 0xD7AF);
}
// ===== 标点 / 符号 / 分隔符(工程近似)=====
static constexpr bool isPunct(U32 c) noexcept
{
if (isAscii(c)) return isAsciiPunct(c);
return (c >= 0x2000 && c <= 0x206F);
}
static constexpr bool isSymbol(U32 c) noexcept
{
return (c >= 0x20A0 && c <= 0x20CF) || // currency
(c >= 0x2100 && c <= 0x214F) || // letterlike
(c >= 0x2190 && c <= 0x21FF) || // arrows
(c >= 0x2600 && c <= 0x26FF) || // misc symbols
(c >= 0x1F300 && c <= 0x1FAFF); // emoji block
}
// ===== 组合 =====
static constexpr bool isAlnum(U32 c) noexcept { return isAlpha(c) || isDigit(c); }
static constexpr bool isHexDigit(U32 c) noexcept { return isAsciiHexDigit(c); }
static constexpr bool isIdentifierStart(U32 c) noexcept { return isAlpha(c) || c == U'_'; }
static constexpr bool isIdentifierContinue(U32 c) noexcept { return isAlnum(c) || c == U'_'; }
};
};

1074
src/Deps/String/String.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
/*!
@file src/Deps/String/StringTest.cpp
@brief String类测试代码
@author PuqiAR (im@puqiar.top)
@date 2026-02-13
*/
#include <cassert>
#include <iostream>
#include "String.hpp"
using Fig::Deps::String;
static void test_ascii_sso()
{
String s("hello");
assert(s.size() == 5);
assert(s[0] == U'h');
assert(s.toStdString() == "hello");
s.push_back(U'!');
assert(s.toStdString() == "hello!");
s.pop_back();
assert(s.toStdString() == "hello");
assert(s.starts_with("he"));
assert(s.ends_with("lo"));
assert(s.contains(U'e'));
}
static void test_ascii_heap()
{
String a("abcdefghijklmnopqrstuvwxyz"); // > SSO
assert(a.size() == 26);
String b("123");
a += b;
assert(a.ends_with("123"));
assert(a.find(U'1') == 26);
}
static void test_utf8_decode()
{
String s("你好");
assert(s.size() == 2);
assert(s.toStdString() == "你好");
s.push_back(U'!');
assert(s.toStdString() == "你好!");
}
static void test_concat_modes()
{
String a("abc");
String b("你好");
String c = a + b;
assert(c.size() == 5);
assert(c.toStdString() == "abc你好");
String d = b + a;
assert(d.toStdString() == "你好abc");
}
static void test_substr_erase_insert()
{
String s("abcdef");
String sub = s.substr(2, 3);
assert(sub.toStdString() == "cde");
s.erase(2, 2);
assert(s.toStdString() == "abef");
s.insert(2, String("CD"));
assert(s.toStdString() == "abCDef");
}
static void test_replace()
{
String s("hello world");
s.replace(6, 5, String("Fig"));
assert(s.toStdString() == "hello Fig");
}
static void test_find_rfind()
{
String s("abcabcabc");
assert(s.find(String("abc")) == 0);
assert(s.find(String("abc"), 1) == 3);
assert(s.rfind(String("abc")) == 6);
}
static void test_compare()
{
String a("abc");
String b("abd");
String c("abc");
assert(a.compare(b) < 0);
assert(b.compare(a) > 0);
assert(a.compare(c) == 0);
assert(a == c);
assert(a != b);
}
static void test_resize_append()
{
String s("abc");
s.resize(5, U'x');
assert(s.toStdString() == "abcxx");
s.append(3, U'y');
assert(s.toStdString() == "abcxxyyy");
}
static void test_std_interop()
{
std::string stds = "hello";
String s(stds);
assert(s.toStdString() == "hello");
s += " world";
assert(s.toStdString() == "hello world");
}
int main()
{
test_ascii_sso();
test_ascii_heap();
test_utf8_decode();
test_concat_modes();
test_substr_erase_insert();
test_replace();
test_find_rfind();
test_compare();
test_resize_append();
test_std_interop();
std::cout << "All String tests passed.\n";
}

47
src/Error/Diagnostics.hpp Normal file
View File

@@ -0,0 +1,47 @@
/*!
@file src/Error/Diagnostics.hpp
@brief 诊断信息收集器:用于存储警告与非致命错误
@author PuqiAR (im@puqiar.top)
@date 2026-03-08
*/
#pragma once
#include <Error/Error.hpp>
namespace Fig
{
class Diagnostics
{
private:
DynArray<Error> errors;
public:
void Report(Error err)
{
errors.push_back(std::move(err));
}
// 统一打印所有收集到的信息
void EmitAll(const SourceManager &manager)
{
for (const auto &err : errors)
{
ReportError(err, manager);
}
}
bool HasErrors() const
{
for (const auto &err : errors)
{
if (!err.IsWarning()) return true;
}
return false;
}
const DynArray<Error>& GetErrors() const { return errors; }
void Clear() { errors.clear(); }
};
} // namespace Fig

200
src/Error/Error.cpp Normal file
View File

@@ -0,0 +1,200 @@
/*!
@file src/Error/Error.cpp
@brief 错误报告实现
@author PuqiAR (im@puqiar.top)
@date 2026-02-14
*/
#include <Core/Core.hpp>
#include <Error/Error.hpp>
#include <sstream>
namespace Fig
{
void ColoredPrint(const char *color, const char *msg, std::ostream &ost = CoreIO::GetStdErr())
{
ost << color << msg << TerminalColors::Reset;
}
void
ColoredPrint(const char *color, const std::string &msg, std::ostream &ost = CoreIO::GetStdErr())
{
ost << color << msg << TerminalColors::Reset;
}
void ColoredPrint(const char *color, const String &msg, std::ostream &ost = CoreIO::GetStdErr())
{
ost << color << msg << TerminalColors::Reset;
}
std::string MultipleStr(const char *c, size_t n)
{
std::string buf;
for (size_t i = 0; i < n; ++i)
{
buf += c;
}
return buf;
}
const char *ErrorTypeToString(ErrorType type)
{
using enum ErrorType;
switch (type)
{
case UnusedSymbol: return "UnusedSymbol";
case UnnecessarySemicolon: return "UnnecessarySemicolon";
case MayBeNull: return "MaybeNull";
case UnterminatedString: return "UnterminatedString";
case UnterminatedComments: return "UnterminatedComments";
case InvalidNumberLiteral: return "InvalidNumberLiteral";
case InvalidCharacter: return "InvalidCharacter";
case InvalidSymbol: return "InvalidSymbol";
case ExpectedExpression: return "ExpectedExpression";
case SyntaxError: return "SyntaxError";
case RedeclarationError: return "RedeclarationError";
case UseUndeclaredIdentifier: return "UseUndeclaredIdentifier";
case NotAnLvalue: return "NotAnLvalue";
case TypeError: return "TypeError";
case TooManyLocals: return "TooManyLocals";
case TooManyConstants: return "TooManyConstants";
case RegisterOverflow: return "RegisterOverflow";
case InternalError:
return "InternalError";
// default: return "Some one forgot to add case to `ErrorTypeToString`";
}
return "UnknownError";
}
void PrintSystemInfos()
{
std::ostream &err = CoreIO::GetStdErr();
std::stringstream build_info;
build_info << "\r🌘 Fig v" << Core::VERSION << " on " << Core::PLATFORM << ' ' << Core::ARCH
<< '[' << Core::COMPILER << ']' << '\n'
<< " Build Time: " << Core::COMPILE_TIME;
const std::string &build_info_str = build_info.str();
err << MultipleStr("", build_info_str.size()) << '\n';
err << build_info_str << '\n';
err << MultipleStr("", build_info_str.size()) << '\n';
}
void PrintErrorInfo(const Error &error, const SourceManager &srcManager)
{
static constexpr const char *MinorColor = "\033[38;2;138;227;198m";
static constexpr const char *MediumColor = "\033[38;2;255;199;95m";
static constexpr const char *CriticalColor = "\033[38;2;255;107;107m";
namespace TC = TerminalColors;
std::ostream &err = CoreIO::GetStdErr();
uint8_t level = ErrorLevel(error.type);
// const char *level_name = (level == 1 ? "Minor" : (level == 2 ? "Medium" : "Critical"));
const char *level_color =
(level == 1 ? MinorColor : (level == 2 ? MediumColor : CriticalColor));
err << "🔥 "
<< level_color
//<< '(' << level_name << ')'
<< 'E' << static_cast<int>(error.type) << TC::Reset << ": " << level_color
<< ErrorTypeToString(error.type) << TC::Reset << '\n';
const SourceLocation &location = error.location;
err << TC::DarkGray << " ┌─> Fn " << TC::Cyan << '\'' << location.packageName << '.'
<< location.functionName << '\'' << " " << location.fileName << " (" << TC::DarkGray
<< location.sp.line << ":" << location.sp.column << TC::Cyan << ')' << TC::Reset
<< '\n';
err << TC::DarkGray << "" << '\n' << "" << TC::Reset << '\n';
// 尝试打印上3行 下2行
int64_t line_start = location.sp.line - 3, line_end = location.sp.line + 2;
while (!srcManager.HasLine(line_end))
{
--line_end;
}
while (!srcManager.HasLine(line_start))
{
++line_start;
}
const auto &getLineNumWidth = [](size_t l) {
unsigned int cnt = 0;
while (l != 0)
{
l = l / 10;
cnt++;
}
return cnt;
};
unsigned int max_line_number_width = getLineNumWidth(line_end);
for (size_t i = line_start; i <= line_end; ++i)
{
unsigned int offset = 2 + 2 + 1;
// ' └─ '
if (i == location.sp.line)
{
err << TC::DarkGray << " └─ " << TC::Reset;
}
else if (i < location.sp.line)
{
err << TC::DarkGray << "" << TC::Reset;
}
else
{
err << MultipleStr(" ", offset);
}
unsigned int cur_line_number_width = getLineNumWidth(i);
err << MultipleStr(" ", max_line_number_width - cur_line_number_width) << TC::Yellow
<< i << TC::Reset;
err << "" << srcManager.GetLine(i) << '\n';
if (i == location.sp.line)
{
unsigned int error_col_offset = offset + 1 + max_line_number_width + 2;
err << MultipleStr(" ", error_col_offset)
<< MultipleStr(" ", location.sp.column - 1) << TC::LightGreen
<< MultipleStr("^", location.sp.tok_length) << TC::Reset << '\n';
err << MultipleStr(" ", error_col_offset)
<< MultipleStr(" ", location.sp.column - 1 + location.sp.tok_length / 2)
<< "╰─ " << level_color << error.message << TC::Reset << "\n\n";
}
}
err << "\n";
err << "" << TC::DarkGray << "Thrower: " << error.thrower_loc.function_name() << " ("
<< error.thrower_loc.file_name() << ":" << error.thrower_loc.line() << ")" << TC::Reset
<< "\n";
err << "💡 " << TC::Blue << "Suggestion: " << error.suggestion << TC::Reset;
err << '\n';
}
void ReportError(const Error &error, const SourceManager &srcManager)
{
assert(srcManager.read && "ReportError: srcManager doesn't read source");
// assert(srcManager.HasLine(error.location.sp.line));
PrintSystemInfos();
PrintErrorInfo(error, srcManager);
}
void ReportErrors(const std::vector<Error> &errors, const SourceManager &srcManager)
{
std::ostream &ost = CoreIO::GetStdErr();
PrintSystemInfos();
for (const auto &err : errors)
{
PrintErrorInfo(err, srcManager);
ost << '\n';
}
}
}; // namespace Fig

185
src/Error/Error.hpp Normal file
View File

@@ -0,0 +1,185 @@
/*!
@file src/Error/Error.hpp
@brief Error定义
@author PuqiAR (im@puqiar.top)
@date 2026-02-13
*/
#pragma once
#include <Core/SourceLocations.hpp>
#include <Deps/Deps.hpp>
#include <SourceManager/SourceManager.hpp>
#include <source_location>
namespace Fig
{
/*
0-1000 Minor
1001-2000 Medium
2001-3000 Critical
*/
enum class ErrorType : unsigned int
{
/* Minor */
UnusedSymbol = 0,
UnnecessarySemicolon,
TrailingComma,
/* Medium */
MayBeNull = 1001,
/* Critical */
// lexer errors
UnterminatedString = 2001,
UnterminatedComments,
InvalidNumberLiteral,
InvalidCharacter,
InvalidSymbol,
// parser errors
ExpectedExpression,
SyntaxError,
// analyzer errors
RedeclarationError,
UseUndeclaredIdentifier,
NotAnLvalue,
TypeError,
// compile errors
TooManyLocals,
TooManyConstants,
// 编译器内部约束
RegisterOverflow,
InternalError,
};
const char *ErrorTypeToString(ErrorType type);
struct Error
{
ErrorType type;
String message;
String suggestion;
SourceLocation location;
std::source_location thrower_loc;
Error() {}
Error(ErrorType _type,
const String &_message,
const String &_suggestion,
const SourceLocation &_location,
const std::source_location &_throwerloc = std::source_location::current())
{
type = _type;
message = _message;
suggestion = _suggestion;
location = _location;
thrower_loc = _throwerloc;
}
// 新增:适配诊断系统的辅助判定
bool IsWarning() const { return static_cast<unsigned int>(type) < 2000; }
};
namespace TerminalColors
{
constexpr const char *Reset = "\033[0m";
constexpr const char *Bold = "\033[1m";
constexpr const char *Dim = "\033[2m";
constexpr const char *Italic = "\033[3m";
constexpr const char *Underline = "\033[4m";
constexpr const char *Blink = "\033[5m";
constexpr const char *Reverse = "\033[7m"; // 前背景反色
constexpr const char *Hidden = "\033[8m"; // 隐藏文本
constexpr const char *Strike = "\033[9m"; // 删除线
constexpr const char *Black = "\033[30m";
constexpr const char *Red = "\033[31m";
constexpr const char *Green = "\033[32m";
constexpr const char *Yellow = "\033[33m";
constexpr const char *Blue = "\033[34m";
constexpr const char *Magenta = "\033[35m";
constexpr const char *Cyan = "\033[36m";
constexpr const char *White = "\033[37m";
constexpr const char *LightBlack = "\033[90m";
constexpr const char *LightRed = "\033[91m";
constexpr const char *LightGreen = "\033[92m";
constexpr const char *LightYellow = "\033[93m";
constexpr const char *LightBlue = "\033[94m";
constexpr const char *LightMagenta = "\033[95m";
constexpr const char *LightCyan = "\033[96m";
constexpr const char *LightWhite = "\033[97m";
constexpr const char *DarkRed = "\033[38;2;128;0;0m";
constexpr const char *DarkGreen = "\033[38;2;0;100;0m";
constexpr const char *DarkYellow = "\033[38;2;128;128;0m";
constexpr const char *DarkBlue = "\033[38;2;0;0;128m";
constexpr const char *DarkMagenta = "\033[38;2;100;0;100m";
constexpr const char *DarkCyan = "\033[38;2;0;128;128m";
constexpr const char *DarkGray = "\033[38;2;64;64;64m";
constexpr const char *Gray = "\033[38;2;128;128;128m";
constexpr const char *Silver = "\033[38;2;192;192;192m";
constexpr const char *Navy = "\033[38;2;0;0;128m";
constexpr const char *RoyalBlue = "\033[38;2;65;105;225m";
constexpr const char *ForestGreen = "\033[38;2;34;139;34m";
constexpr const char *Olive = "\033[38;2;128;128;0m";
constexpr const char *Teal = "\033[38;2;0;128;128m";
constexpr const char *Maroon = "\033[38;2;128;0;0m";
constexpr const char *Purple = "\033[38;2;128;0;128m";
constexpr const char *Orange = "\033[38;2;255;165;0m";
constexpr const char *Gold = "\033[38;2;255;215;0m";
constexpr const char *Pink = "\033[38;2;255;192;203m";
constexpr const char *Crimson = "\033[38;2;220;20;60m";
constexpr const char *OnBlack = "\033[40m";
constexpr const char *OnRed = "\033[41m";
constexpr const char *OnGreen = "\033[42m";
constexpr const char *OnYellow = "\033[43m";
constexpr const char *OnBlue = "\033[44m";
constexpr const char *OnMagenta = "\033[45m";
constexpr const char *OnCyan = "\033[46m";
constexpr const char *OnWhite = "\033[47m";
constexpr const char *OnLightBlack = "\033[100m";
constexpr const char *OnLightRed = "\033[101m";
constexpr const char *OnLightGreen = "\033[102m";
constexpr const char *OnLightYellow = "\033[103m";
constexpr const char *OnLightBlue = "\033[104m";
constexpr const char *OnLightMagenta = "\033[105m";
constexpr const char *OnLightCyan = "\033[106m";
constexpr const char *OnLightWhite = "\033[107m";
constexpr const char *OnDarkBlue = "\033[48;2;0;0;128m";
constexpr const char *OnGreenYellow = "\033[48;2;173;255;47m";
constexpr const char *OnOrange = "\033[48;2;255;165;0m";
constexpr const char *OnGray = "\033[48;2;128;128;128m";
}; // namespace TerminalColors
inline uint8_t ErrorLevel(ErrorType t)
{
unsigned int id = static_cast<int>(t);
if (id <= 1000)
{
return 1;
}
if (id > 1000 && id <= 2000)
{
return 2;
}
if (id > 2000)
{
return 3;
}
return 0;
}
void ReportError(const Error &error, const SourceManager &srcManager);
}; // namespace Fig

Some files were not shown because too many files have changed in this diff Show More