好的,遵照您的要求,我为您整理了一份极其详细、完整的步骤指南,涵盖了在 Windows 上使用 Visual Studio 2022 和 MASM 从零开始实现 Nova 编译器的全过程。您可以将以下内容复制到文本文件或 Word 文档中。
Nova 编译器实现详细步骤指南
项目概述
目标: 在 Windows 平台上,使用 Visual Studio 2022 (C++) 和 MASM,从零开始实现一个能将 Nova 语言源码编译为原生 x64 可执行文件的编译器。
核心理念: 绝对零开销抽象,追求极致性能。
技术路线: 手写词法分析器、语法分析器、语义分析器,设计自定义中间表示(IR),实现优化,并最终通过 MASM 生成高性能机器码。
阶段零:环境准备与项目初始化 (预计时间: 1天)
步骤 0.1: 安装开发环境
1. 安装 Visual Studio 2022
◦ 从 https://visualstudio.microsoft.com/下载安装程序。
◦ 在工作负载中选择 “使用 C++ 的桌面开发”。
◦ 在 “单个组件” 选项卡中,确保勾选了 “MSVC v143 - VS 2022 C++ x64/x86 生成工具” 和 “C++ AddressSanitizer” (后者通常会自动包含 MASM 汇编器 ml64.exe)。
2. 安装 Git (可选,但强烈推荐)
◦ 从 https://git-scm.com/ 下载并安装,用于版本控制。
步骤 0.2: 验证环境
1. 打开 “Developer Command Prompt for VS 2022” 或 “Developer PowerShell for VS 2022”。
2. 运行以下命令,确认工具可用:
cl # 应显示 Microsoft C/C++ 编译器版本信息
ml64 # 应显示 Microsoft Macro Assembler 版本信息
link # 应显示 Microsoft Linker 版本信息
步骤 0.3: 创建项目结构
1. 创建项目根目录,例如 C:\dev\nova-compiler\。
2. 在根目录下创建以下子文件夹结构:
nova-compiler/
├── include/ # 存放所有头文件 (.h)
├── src/ # 存放所有 C++ 源文件 (.cpp)
├── tests/ # 存放测试用例 (.nova 文件)
└── lib/ # 未来存放第三方库 (可选)
3. 使用 Visual Studio 2022 创建新项目:
◦ 选择 “空项目”,命名为 NovaCompiler,位置选择刚才创建的 nova-compiler 目录。
◦ 在 “解决方案资源管理器” 中,右键点击 “源文件” -> “添加” -> “新建项” -> “C++ 文件 (.cpp)”,命名为 main.cpp。
步骤 0.4: 配置项目属性 (至关重要!)
1. 右键点击项目 NovaCompiler -> “属性”。
2. 确保 “配置” 和 “平台” 设置为 “所有配置” 和 “x64”。
3. 进入 “配置属性” -> “高级” -> 将 “字符集” 设置为 “使用多字节字符集”。 这可以避免 Unicode 带来的复杂问题。
4. 点击 “确定”。
阶段一:打通核心流程 - 编译 return 42; (预计时间: 3-5天)
步骤 1.1: 编写最小编译器驱动代码
目标: 创建一个能调用 MASM 和 Linker 的程序框架。
文件: src/main.cpp
代码:
#include <iostream>
#include <fstream>
#include <cstdlib> // for system()
int main(int argc, char* argv[]) {
std::cout << "Nova Compiler Bootstrapping..." << std::endl;
if (argc != 2) {
std::cerr << "Usage: novac <sourcefile.nova>" << std::endl;
return 1;
}
// --- 后续阶段将在这里进行真正的编译 ---
// 1. 词法分析 (Lexing)
// 2. 语法分析 (Parsing)
// 3. 代码生成 (Code Generation)
// --- 现阶段我们硬编码一个测试程序 ---
std::cout << "[模拟] 编译开始: " << argv[1] << std::endl;
// 生成 MASM 格式的汇编代码文件
std::ofstream asmFile("output.asm");
asmFile <<
".code\n"
"main PROC PUBLIC\n"
" mov rax, 42 ; Hardcoded return value\n"
" ret\n"
"main ENDP\n"
"END\n";
asmFile.close();
std::cout << "[模拟] 生成汇编: output.asm" << std::endl;
// 调用 MASM 汇编器 (ml64.exe) 将 .asm 编译成 .obj
std::cout << "调用 MASM 进行汇编..." << std::endl;
int masmResult = std::system("ml64 /nologo /c output.asm");
if (masmResult != 0) {
std::cerr << "错误: MASM 汇编失败!" << std::endl;
return 1;
}
std::cout << "汇编成功: output.obj" << std::endl;
// 调用链接器 (link.exe) 将 .obj 链接成 .exe
std::cout << "调用链接器..." << std::endl;
int linkResult = std::system("link /nologo /subsystem:console /entry:main output.obj");
if (linkResult != 0) {
std::cerr << "错误: 链接失败!" << std::endl;
return 1;
}
std::cout << "链接成功: output.exe" << std::endl;
// 运行生成的程序
std::cout << "运行程序..." << std::endl;
int runResult = std::system("output.exe");
std::cout << "程序运行完毕,退出代码: " << runResult << " (应为 42)" << std::endl;
return 0;
}
步骤 1.2: 测试核心流程
1. 在 Visual Studio 中,确保解决方案平台是 x64,然后按 Ctrl + F5 (开始执行不调试)。
2. 程序会运行,并尝试编译一个硬编码的程序。它会成功,因为所有步骤都是写死的。
3. 验证: 在项目目录下,你应该能看到生成的 output.asm, output.obj, output.exe 文件。命令行输出应显示程序退出代码为 42。
4. 结论: 你已成功建立 C++ 驱动器 -> MASM -> LINKER -> .EXE 的自动化流水线。这是项目的基石。
阶段二:实现词法分析器 (Lexer) (预计时间: 3-5天)
步骤 2.1: 定义 Token 类型
文件: include/Token.h
代码:
#pragma once
#include <string>
#include <optional>
namespace Nova {
enum class TokenType {
// 文件结束
EndOfFile,
// 关键字
Return,
Int,
// 字面量
Identifier,
IntegerLiteral,
// 运算符和分隔符
Semicolon, // ;
Colon, // :
Equals, // =
Plus, // +
Minus, // -
ParenOpen, // (
ParenClose, // )
BraceOpen, // {
BraceClose, // }
};
struct Token {
TokenType type;
std::optional<std::string> value; // 对于标识符、整数字面量,存储其值
int line;
int column;
Token(TokenType type, int line, int col, std::optional<std::string> val = {})
: type(type), value(val), line(line), column(col) {}
};
} // namespace Nova
步骤 2.2: 实现词法分析器
文件: src/Lexer.cpp
代码: (这是一个简化版,需实现 advance, peek, skipWhitespace, number, identifier 等辅助函数)
#include "Token.h"
#include <string>
#include <vector>
#include <cctype>
#include <map>
namespace Nova {
class Lexer {
std::string source;
size_t start = 0;
size_t current = 0;
int line = 1;
int column = 1;
std::map<std::string, TokenType> keywords = {
{"return", TokenType::Return},
{"int", TokenType::Int},
};
char advance() { /* ... 实现向前移动一个字符 ... */ }
bool isAtEnd() const { /* ... */ }
char peek() const { /* ... 查看当前字符而不消耗它 ... */ }
void skipWhitespace() { /* ... 跳过空格、制表符、换行符 ... */ }
Token number() {
// 循环读取所有连续的数字
while (std::isdigit(peek())) advance();
std::string numStr = source.substr(start, current - start);
return Token(TokenType::IntegerLiteral, line, column, numStr);
}
Token identifier() {
// 循环读取字母、数字、下划线
while (std::isalnum(peek()) || peek() == '_') advance();
std::string idStr = source.substr(start, current - start);
// 检查是否是关键字
auto it = keywords.find(idStr);
if (it != keywords.end()) {
return Token(it->second, line, column);
}
return Token(TokenType::Identifier, line, column, idStr);
}
public:
Lexer(const std::string& src) : source(src) {}
std::vector<Token> scanTokens() {
std::vector<Token> tokens;
start = current = 0;
while (!isAtEnd()) {
start = current;
char c = advance();
skipWhitespace();
if (isAtEnd()) break;
if (std::isdigit(c)) {
tokens.push_back(number());
} else if (std::isalpha(c)) {
tokens.push_back(identifier());
} else {
switch (c) {
case ';': tokens.push_back(Token(TokenType::Semicolon, line, column)); break;
case ':': tokens.push_back(Token(TokenType::Colon, line, column)); break;
case '=': tokens.push_back(Token(TokenType::Equals, line, column)); break;
case '+': tokens.push_back(Token(TokenType::Plus, line, column)); break;
case '(': tokens.push_back(Token(TokenType::ParenOpen, line, column)); break;
case ')': tokens.push_back(Token(TokenType::ParenClose, line, column)); break;
case '{': tokens.push_back(Token(TokenType::BraceOpen, line, column)); break;
case '}': tokens.push_back(Token(TokenType::BraceClose, line, column)); break;
default:
std::cerr << "Error: Unexpected character '" << c << "' at line " << line << ", col " << column << std::endl;
exit(1);
}
}
}
tokens.push_back(Token(TokenType::EndOfFile, line, column));
return tokens;
}
};
} // namespace Nova
步骤 2.3: 集成并测试词法分析器
1. 修改 main.cpp:包含 #include "Lexer.cpp",并在硬编码生成汇编之前添加代码来读取源文件并调用词法分析器。
2. 创建测试文件:在 tests/ 目录下创建 test_01.nova,内容为 return 42;。
3. 运行测试:修改 main.cpp 参数处理逻辑,使其读取 argv[1] 的文件。运行程序并观察输出的 Token 列表,确认它能正确识别 return, 42, ;。
(Due to the character limit, the document continues in the next message. This covers the first two phases. The subsequent phases would be detailed in a similar step-by-step manner, covering Parser, AST, Sema, IR, Optimization, CodeGen, etc.)
。你有其他的更能优化性能想法吗?
最新发布