feat: 增加了analyzer, compiler不再分析并且报错, 只生产 bytecode, analyzer作为前端结束最后一道防线检查代码,同时引入一个简单的LSP
This commit is contained in:
BIN
Fig-VSCode/bin/Fig-LSP.exe
Normal file
BIN
Fig-VSCode/bin/Fig-LSP.exe
Normal file
Binary file not shown.
3156
Fig-VSCode/package-lock.json
generated
3156
Fig-VSCode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "fig-vscode",
|
"name": "fig-vscode",
|
||||||
"displayName": "Fig Language",
|
"displayName": "Fig Language",
|
||||||
"description": "VSCode extension for Fig language with syntax highlighting",
|
"description": "VSCode extension for Fig language with syntax highlighting and lsp support",
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"publisher": "PuqiAR",
|
"publisher": "PuqiAR",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.90.0"
|
"vscode": "^1.108.0"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
"Programming Languages"
|
"Programming Languages"
|
||||||
@@ -13,7 +13,9 @@
|
|||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/PuqiAR/Fig"
|
"url": "https://github.com/PuqiAR/Fig"
|
||||||
},
|
},
|
||||||
"activationEvents": [],
|
"activationEvents": [
|
||||||
|
"onLanguage:fig"
|
||||||
|
],
|
||||||
"main": "./out/extension.js",
|
"main": "./out/extension.js",
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"languages": [
|
"languages": [
|
||||||
@@ -33,14 +35,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"semanticTokenModifiers": [
|
|
||||||
{
|
|
||||||
"fig": {
|
|
||||||
"variable": "variable.other.fig",
|
|
||||||
"function": "entity.name.function.fig"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammars": [
|
"grammars": [
|
||||||
{
|
{
|
||||||
"language": "fig",
|
"language": "fig",
|
||||||
@@ -56,9 +50,13 @@
|
|||||||
"pretest": "npm run compile",
|
"pretest": "npm run compile",
|
||||||
"test": "node ./out/test/runTest.js"
|
"test": "node ./out/test/runTest.js"
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-languageclient": "^9.0.1"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.2.2",
|
"@types/mocha": "^10.0.10",
|
||||||
"vscode": "^1.90.0",
|
"@types/node": "^20.6.0",
|
||||||
"@types/node": "^20.6.0"
|
"@types/vscode": "^1.108.0",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,44 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as path from 'path';
|
||||||
|
import { ExtensionContext, workspace } from 'vscode';
|
||||||
|
import {
|
||||||
|
LanguageClient,
|
||||||
|
LanguageClientOptions,
|
||||||
|
ServerOptions
|
||||||
|
} from 'vscode-languageclient/node';
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
let client: LanguageClient;
|
||||||
console.log('Fig extension is now active!');
|
|
||||||
|
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() {}
|
export function deactivate(): Thenable<void> | undefined {
|
||||||
|
if (!client) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return client.stop();
|
||||||
|
}
|
||||||
75
LICENSE
75
LICENSE
@@ -1,18 +1,65 @@
|
|||||||
MIT License
|
MIT License (Fig)
|
||||||
|
|
||||||
Copyright (c) 2026 PuqiAR
|
Copyright (c) 2026 PuqiAR <im@puqiar.top>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
in the Software without restriction, including without limitation the rights
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
following conditions:
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
1. The above copyright notice and this permission notice shall be included in all
|
||||||
portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
2. This project includes code from the following projects with their respective licenses:
|
||||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
- argparse (MIT License)
|
||||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
Copyright (c) 2018 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
||||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <Core/SourceLocations.hpp>
|
#include <Core/SourceLocations.hpp>
|
||||||
#include <Deps/Deps.hpp>
|
#include <Deps/Deps.hpp>
|
||||||
|
|
||||||
|
#include <Sema/Type.hpp>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Fig
|
namespace Fig
|
||||||
@@ -49,6 +51,10 @@ namespace Fig
|
|||||||
|
|
||||||
struct Expr : public AstNode
|
struct Expr : public AstNode
|
||||||
{
|
{
|
||||||
|
TypeTag resolvedType = TypeTag::Any;
|
||||||
|
// TODO: 可选的常量折叠
|
||||||
|
// 拓展 isConstExpr和 constValue槽位
|
||||||
|
|
||||||
Expr()
|
Expr()
|
||||||
{
|
{
|
||||||
type = AstType::Expr;
|
type = AstType::Expr;
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ namespace Fig
|
|||||||
{
|
{
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
|
// Analyzer槽位,存储具体深度
|
||||||
|
int resolvedDepth = -1; // 代表未解析
|
||||||
|
bool isGlobal = false; // 是否全局/对外公开 (isPublic)
|
||||||
|
|
||||||
IdentiExpr()
|
IdentiExpr()
|
||||||
{
|
{
|
||||||
type = AstType::IdentiExpr;
|
type = AstType::IdentiExpr;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace Fig
|
|||||||
|
|
||||||
IndexExpr(Expr *_base, Expr *_index) : base(_base), index(_index)
|
IndexExpr(Expr *_base, Expr *_index) : base(_base), index(_index)
|
||||||
{
|
{
|
||||||
|
type = AstType::IndexExpr;
|
||||||
location = base->location;
|
location = base->location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace Fig
|
|||||||
{
|
{
|
||||||
String name;
|
String name;
|
||||||
Expr *typeSpecifier;
|
Expr *typeSpecifier;
|
||||||
|
bool isInfer; // 是否用了 := 类型推断
|
||||||
Expr *initExpr;
|
Expr *initExpr;
|
||||||
|
|
||||||
VarDecl()
|
VarDecl()
|
||||||
@@ -23,9 +24,10 @@ namespace Fig
|
|||||||
type = AstType::VarDecl;
|
type = AstType::VarDecl;
|
||||||
}
|
}
|
||||||
|
|
||||||
VarDecl(bool _isPublic, String _name, Expr *_typeSpecifier, Expr *_initExpr, SourceLocation _location) :
|
VarDecl(bool _isPublic, String _name, Expr *_typeSpecifier, bool _isInfer, Expr *_initExpr, SourceLocation _location) :
|
||||||
name(std::move(_name)),
|
name(std::move(_name)),
|
||||||
typeSpecifier(_typeSpecifier),
|
typeSpecifier(_typeSpecifier),
|
||||||
|
isInfer(_isInfer),
|
||||||
initExpr(_initExpr) // location 指向关键字 var/const位置
|
initExpr(_initExpr) // location 指向关键字 var/const位置
|
||||||
{
|
{
|
||||||
type = AstType::VarDecl;
|
type = AstType::VarDecl;
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ namespace Fig
|
|||||||
{
|
{
|
||||||
Result<std::uint8_t, Error> Compiler::CompileIdentiExpr(IdentiExpr *ie)
|
Result<std::uint8_t, Error> Compiler::CompileIdentiExpr(IdentiExpr *ie)
|
||||||
{
|
{
|
||||||
if (!HasLocal(ie->name))
|
// if (!HasLocal(ie->name))
|
||||||
{
|
// {
|
||||||
return std::unexpected(Error(ErrorType::UseUndeclaredIdentifier,
|
// return std::unexpected(Error(ErrorType::UseUndeclaredIdentifier,
|
||||||
std::format("`{}` has not been defined", ie->name),
|
// std::format("`{}` has not been defined", ie->name),
|
||||||
"none",
|
// "none",
|
||||||
makeSourceLocation(ie)));
|
// makeSourceLocation(ie)));
|
||||||
}
|
// }
|
||||||
return ResolveLocal(ie->name);
|
return ResolveLocal(ie->name);
|
||||||
}
|
}
|
||||||
Result<std::uint8_t, Error> Compiler::CompileLiteral(
|
Result<std::uint8_t, Error> Compiler::CompileLiteral(
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ namespace Fig
|
|||||||
Result<void, Error> Compiler::CompileVarDecl(VarDecl *varDecl)
|
Result<void, Error> Compiler::CompileVarDecl(VarDecl *varDecl)
|
||||||
{
|
{
|
||||||
const String &name = varDecl->name;
|
const String &name = varDecl->name;
|
||||||
if (HasLocalInCurrentScope(name))
|
// if (HasLocalInCurrentScope(name))
|
||||||
{
|
// {
|
||||||
return std::unexpected(Error(ErrorType::RedeclarationError,
|
// return std::unexpected(Error(ErrorType::RedeclarationError,
|
||||||
std::format("variable `{}` has already defined in this scope", name),
|
// std::format("variable `{}` has already defined in this scope", name),
|
||||||
"change its name",
|
// "change its name",
|
||||||
makeSourceLocation(varDecl)));
|
// makeSourceLocation(varDecl)));
|
||||||
}
|
// }
|
||||||
std::uint8_t varReg;
|
std::uint8_t varReg;
|
||||||
if (varDecl->initExpr)
|
if (varDecl->initExpr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ namespace Fig
|
|||||||
case RedeclarationError: return "RedeclarationError";
|
case RedeclarationError: return "RedeclarationError";
|
||||||
case UseUndeclaredIdentifier: return "UseUndeclaredIdentifier";
|
case UseUndeclaredIdentifier: return "UseUndeclaredIdentifier";
|
||||||
case NotAnLvalue: return "NotAnLvalue";
|
case NotAnLvalue: return "NotAnLvalue";
|
||||||
|
case TypeError: return "TypeError";
|
||||||
// default: return "Some one forgot to add case to `ErrorTypeToString`";
|
// default: return "Some one forgot to add case to `ErrorTypeToString`";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,10 +42,11 @@ namespace Fig
|
|||||||
ExpectedExpression,
|
ExpectedExpression,
|
||||||
SyntaxError,
|
SyntaxError,
|
||||||
|
|
||||||
// compiler errors
|
// analyzer errors
|
||||||
RedeclarationError,
|
RedeclarationError,
|
||||||
UseUndeclaredIdentifier,
|
UseUndeclaredIdentifier,
|
||||||
NotAnLvalue
|
NotAnLvalue,
|
||||||
|
TypeError,
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *ErrorTypeToString(ErrorType type);
|
const char *ErrorTypeToString(ErrorType type);
|
||||||
|
|||||||
19
src/LSP/FigLSPServer.cpp
Normal file
19
src/LSP/FigLSPServer.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#include <LSP/LSPServer.hpp>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <io.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
_setmode(_fileno(stdin), _O_BINARY);
|
||||||
|
_setmode(_fileno(stdout), _O_BINARY);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Fig::LspServer server;
|
||||||
|
|
||||||
|
server.Run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
167
src/LSP/LSPServer.hpp
Normal file
167
src/LSP/LSPServer.hpp
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Ast/Ast.hpp>
|
||||||
|
#include <Lexer/Lexer.hpp>
|
||||||
|
#include <Parser/Parser.hpp>
|
||||||
|
#include <Sema/Analyzer.hpp>
|
||||||
|
|
||||||
|
#include <Error/Error.hpp>
|
||||||
|
#include <SourceManager/SourceManager.hpp>
|
||||||
|
|
||||||
|
#include <charconv> // C++17/20 字符串转数字
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <Utils/json/json.hpp>
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace Fig
|
||||||
|
{
|
||||||
|
|
||||||
|
class LspServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Run()
|
||||||
|
{
|
||||||
|
std::ios_base::sync_with_stdio(false);
|
||||||
|
std::cin.tie(NULL);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
if (!std::getline(std::cin, line))
|
||||||
|
break; // 退出循环
|
||||||
|
|
||||||
|
// 解析 HTTP 风格的 Header: "Content-Length: 123\r"
|
||||||
|
if (line.starts_with("Content-Length: "))
|
||||||
|
{
|
||||||
|
std::size_t length = 0;
|
||||||
|
// 使用 C++ 极速的 from_chars 替代 stoi
|
||||||
|
auto res = std::from_chars(line.data() + 16, line.data() + line.size(), length);
|
||||||
|
if (res.ec != std::errc())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 消费 Header 和 Body 之间那行空的 "\r\n"
|
||||||
|
std::string emptyLine;
|
||||||
|
std::getline(std::cin, emptyLine);
|
||||||
|
|
||||||
|
// 读取对应字节的 JSON Body
|
||||||
|
std::string jsonBody(length, '\0');
|
||||||
|
std::cin.read(jsonBody.data(), length);
|
||||||
|
|
||||||
|
// 派发给路由
|
||||||
|
HandleMessage(jsonBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandleMessage(const std::string &rawJson)
|
||||||
|
{
|
||||||
|
json req = json::parse(rawJson, nullptr, false);
|
||||||
|
if (req.is_discarded())
|
||||||
|
return; // 忽略解析失败的报文
|
||||||
|
|
||||||
|
std::string method = req.value("method", "");
|
||||||
|
|
||||||
|
if (method == "initialize")
|
||||||
|
{
|
||||||
|
Respond(req["id"], R"({
|
||||||
|
"capabilities": {
|
||||||
|
"textDocumentSync": 1,
|
||||||
|
"hoverProvider": true
|
||||||
|
}
|
||||||
|
})");
|
||||||
|
}
|
||||||
|
else if (method == "textDocument/didOpen")
|
||||||
|
{
|
||||||
|
std::string uri = req["params"]["textDocument"]["uri"];
|
||||||
|
std::string text = req["params"]["textDocument"]["text"];
|
||||||
|
PublishDiagnostics(uri, text);
|
||||||
|
}
|
||||||
|
else if (method == "textDocument/didChange")
|
||||||
|
{
|
||||||
|
std::string uri = req["params"]["textDocument"]["uri"];
|
||||||
|
std::string text = req["params"]["contentChanges"][0]["text"];
|
||||||
|
PublishDiagnostics(uri, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Respond(int id, const std::string &resultJsonString)
|
||||||
|
{
|
||||||
|
std::string response = "{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(id)
|
||||||
|
+ ",\"result\":" + resultJsonString + "}";
|
||||||
|
std::cout << "Content-Length: " << response.size() << "\r\n\r\n"
|
||||||
|
<< response << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendDiagnostics(const std::string &uri, const Error *err = nullptr)
|
||||||
|
{
|
||||||
|
json diagnostics = json::array(); // 默认空数组,代码无错时擦除红线
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
// LSP 规定行列号必须从 0 开始算
|
||||||
|
int startLine = err->location.sp.line - 1;
|
||||||
|
int startChar = err->location.sp.column - 1;
|
||||||
|
int endLine = startLine;
|
||||||
|
int endChar = startChar + err->location.sp.tok_length;
|
||||||
|
|
||||||
|
std::string fullMessage = err->message.toStdString();
|
||||||
|
if (!err->suggestion.empty())
|
||||||
|
{
|
||||||
|
fullMessage += " 💡suggestion: " + err->suggestion.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics.push_back(
|
||||||
|
{{"range",
|
||||||
|
{{"start", {{"line", startLine}, {"character", startChar}}},
|
||||||
|
{"end", {{"line", endLine}, {"character", endChar}}}}},
|
||||||
|
{"severity", 1}, // 1 = 致命错误红线
|
||||||
|
{"source", "Fig LSP Server"},
|
||||||
|
{"message", fullMessage}});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装 Notification
|
||||||
|
json notification = {{"jsonrpc", "2.0"},
|
||||||
|
{"method", "textDocument/publishDiagnostics"},
|
||||||
|
{"params", {{"uri", uri}, {"diagnostics", diagnostics}}}};
|
||||||
|
|
||||||
|
std::string response = notification.dump();
|
||||||
|
std::cout << "Content-Length: " << response.size() << "\r\n\r\n"
|
||||||
|
<< response << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PublishDiagnostics(const std::string &uri, const std::string &sourceCode)
|
||||||
|
{
|
||||||
|
SourceManager manager;
|
||||||
|
manager.LoadFromMemory(sourceCode);
|
||||||
|
|
||||||
|
Lexer lexer(sourceCode, "");
|
||||||
|
Parser parser(lexer, manager, "");
|
||||||
|
|
||||||
|
// 1. 语法检查拦截
|
||||||
|
auto parserResult = parser.Parse();
|
||||||
|
if (!parserResult)
|
||||||
|
{
|
||||||
|
SendDiagnostics(uri, &parserResult.error());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Program *program = *parserResult;
|
||||||
|
|
||||||
|
Analyzer analyzer(manager);
|
||||||
|
|
||||||
|
// 语义检查拦截
|
||||||
|
auto analyzerResult = analyzer.Analyze(program);
|
||||||
|
if (!analyzerResult)
|
||||||
|
{
|
||||||
|
SendDiagnostics(uri, &analyzerResult.error());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 一切完美,发射空数组清空过去的错误红线
|
||||||
|
SendDiagnostics(uri, nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace Fig
|
||||||
@@ -64,7 +64,7 @@ namespace Fig
|
|||||||
String value; // 用于判断是标识符还是关键字
|
String value; // 用于判断是标识符还是关键字
|
||||||
value.push_back(rd.produce()); // 加入第一个
|
value.push_back(rd.produce()); // 加入第一个
|
||||||
|
|
||||||
while (CharUtils::isIdentifierContinue(rd.current())) // continue: _ / 0-9 / aA - zZ
|
while (!rd.isAtEnd() && CharUtils::isIdentifierContinue(rd.current())) // continue: _ / 0-9 / aA - zZ
|
||||||
{
|
{
|
||||||
tok.length++;
|
tok.length++;
|
||||||
value.push_back(rd.produce());
|
value.push_back(rd.produce());
|
||||||
|
|||||||
@@ -165,12 +165,12 @@ namespace Fig
|
|||||||
// 让 VM 的 OP_EQ 指令极简:`if (RA == RB)`
|
// 让 VM 的 OP_EQ 指令极简:`if (RA == RB)`
|
||||||
[[nodiscard]] constexpr bool operator==(const Value &other) const
|
[[nodiscard]] constexpr bool operator==(const Value &other) const
|
||||||
{
|
{
|
||||||
// IEEE 754 规定浮点数有 +0.0 == -0.0 的特殊规则
|
// IEEE 754 规定浮点数有 +0.0 == -0.0 的特殊规则,所以不直接比对raw bits而是转换成 double进行C++比对
|
||||||
if (IsDouble() && other.IsDouble())
|
if (IsDouble() && other.IsDouble())
|
||||||
{
|
{
|
||||||
return AsDouble() == other.AsDouble();
|
return AsDouble() == other.AsDouble();
|
||||||
}
|
}
|
||||||
// 直接比较 64 位整数内存
|
// 直接比较 64 位整数内存,所以堆对象比较为地址(运算符重载由Compiler处理)
|
||||||
return v_ == other.v_;
|
return v_ == other.v_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ namespace Fig
|
|||||||
{
|
{
|
||||||
return std::unexpected(result.error());
|
return std::unexpected(result.error());
|
||||||
}
|
}
|
||||||
program->nodes.push_back(*result);
|
Stmt *stmt = *result;
|
||||||
|
if (!stmt)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
program->nodes.push_back(stmt);
|
||||||
}
|
}
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace Fig
|
|||||||
Expr *typeSpeicifer = nullptr;
|
Expr *typeSpeicifer = nullptr;
|
||||||
if (match(TokenType::Colon)) // `:`
|
if (match(TokenType::Colon)) // `:`
|
||||||
{
|
{
|
||||||
const auto &result = parseExpression(0, TokenType::Assign);
|
const auto &result = parseExpression(0, TokenType::Assign, TokenType::Walrus);
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
return std::unexpected(result.error());
|
return std::unexpected(result.error());
|
||||||
@@ -61,6 +61,7 @@ namespace Fig
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expr *initExpr = nullptr;
|
Expr *initExpr = nullptr;
|
||||||
|
bool isInfer = false;
|
||||||
if (match(TokenType::Assign))
|
if (match(TokenType::Assign))
|
||||||
{
|
{
|
||||||
const auto &result = parseExpression();
|
const auto &result = parseExpression();
|
||||||
@@ -70,11 +71,30 @@ namespace Fig
|
|||||||
}
|
}
|
||||||
initExpr = *result;
|
initExpr = *result;
|
||||||
}
|
}
|
||||||
|
else if (match(TokenType::Walrus)) // :=
|
||||||
|
{
|
||||||
|
if (typeSpeicifer) // 指定了类型同时使用 :=
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(
|
||||||
|
ErrorType::SyntaxError,
|
||||||
|
"used type infer but specifying the type",
|
||||||
|
"change `:=` to '='",
|
||||||
|
makeSourceLocation(prevToken()) // :=
|
||||||
|
));
|
||||||
|
}
|
||||||
|
const auto &result = parseExpression();
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return std::unexpected(result.error());
|
||||||
|
}
|
||||||
|
initExpr = *result;
|
||||||
|
isInfer = true; // 使用类型自动推断 :=
|
||||||
|
}
|
||||||
if (!match(TokenType::Semicolon))
|
if (!match(TokenType::Semicolon))
|
||||||
{
|
{
|
||||||
makeExpectSemicolonError();
|
return std::unexpected(makeExpectSemicolonError());
|
||||||
}
|
}
|
||||||
VarDecl *varDecl = new VarDecl(isPublic, name, typeSpeicifer, initExpr, location);
|
VarDecl *varDecl = new VarDecl(isPublic, name, typeSpeicifer, isInfer, initExpr, location);
|
||||||
return varDecl;
|
return varDecl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +257,11 @@ namespace Fig
|
|||||||
return parseIfStmt();
|
return parseIfStmt();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &expr_result = parseExpression(0);
|
if (isEOF)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const auto &expr_result = parseExpression();
|
||||||
if (!expr_result)
|
if (!expr_result)
|
||||||
{
|
{
|
||||||
return std::unexpected(expr_result.error());
|
return std::unexpected(expr_result.error());
|
||||||
|
|||||||
420
src/Sema/Analyzer.cpp
Normal file
420
src/Sema/Analyzer.cpp
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
/*!
|
||||||
|
@file src/Sema/Analyzer.hpp
|
||||||
|
@brief 前端类型检查器实现
|
||||||
|
@author PuqiAR (im@puqiar.top)
|
||||||
|
@date 2026-02-23
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Sema/Analyzer.hpp>
|
||||||
|
|
||||||
|
namespace Fig
|
||||||
|
{
|
||||||
|
Result<void, Error> Analyzer::analyzeVarDecl(VarDecl *stmt)
|
||||||
|
{
|
||||||
|
if (env.Resolve(stmt->name) != std::nullopt)
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::RedeclarationError,
|
||||||
|
std::format("variable `{}` has already defined in this scope", stmt->name),
|
||||||
|
"change its name",
|
||||||
|
makeSourceLocation(stmt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeTag initType = TypeTag::Any;
|
||||||
|
if (stmt->initExpr)
|
||||||
|
{
|
||||||
|
const auto &res = analyzeExpr(stmt->initExpr);
|
||||||
|
if (!res)
|
||||||
|
{
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
initType = stmt->initExpr->resolvedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeTag declaredType = TypeTag::Any;
|
||||||
|
if (stmt->typeSpecifier)
|
||||||
|
{
|
||||||
|
// TODO: 解析类型定义
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt->isInfer)
|
||||||
|
{
|
||||||
|
declaredType = initType;
|
||||||
|
}
|
||||||
|
else if (declaredType != TypeTag::Any && declaredType != initType)
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
std::format("cannot assign type `{}` to variable {} which speicifer type is '{}'",
|
||||||
|
magic_enum::enum_name(initType),
|
||||||
|
stmt->name,
|
||||||
|
magic_enum::enum_name(declaredType)),
|
||||||
|
"none",
|
||||||
|
makeSourceLocation(stmt->initExpr)));
|
||||||
|
}
|
||||||
|
env.Define(stmt->name, declaredType, stmt->isPublic, false);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, Error> Analyzer::analyzeIfStmt(IfStmt *stmt)
|
||||||
|
{
|
||||||
|
auto condRes = analyzeExpr(stmt->cond);
|
||||||
|
if (!condRes)
|
||||||
|
{
|
||||||
|
return condRes;
|
||||||
|
}
|
||||||
|
if (stmt->cond->resolvedType != TypeTag::Any && stmt->cond->resolvedType != TypeTag::Bool)
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(
|
||||||
|
ErrorType::TypeError,
|
||||||
|
std::format("if condition must be boolean, got `{}`", magic_enum::enum_name(stmt->cond->resolvedType)),
|
||||||
|
"ensure condition is boolean",
|
||||||
|
makeSourceLocation(stmt->cond)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
auto consequentRes = analyzeStmt(stmt->consequent);
|
||||||
|
if (!consequentRes)
|
||||||
|
{
|
||||||
|
return consequentRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ElseIfStmt *elif : stmt->elifs)
|
||||||
|
{
|
||||||
|
auto condRes = analyzeExpr(elif->cond);
|
||||||
|
if (elif->cond->resolvedType != TypeTag::Any
|
||||||
|
&& elif->cond->resolvedType != TypeTag::Bool)
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
std::format("else if condition must be boolean, got `{}`",
|
||||||
|
magic_enum::enum_name(elif->cond->resolvedType)),
|
||||||
|
"ensure condition is boolean",
|
||||||
|
makeSourceLocation(elif->cond)));
|
||||||
|
}
|
||||||
|
auto consequentRes = analyzeStmt(elif->consequent);
|
||||||
|
if (!consequentRes)
|
||||||
|
{
|
||||||
|
return consequentRes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt->alternate)
|
||||||
|
{
|
||||||
|
auto alternateRes = analyzeStmt(stmt->alternate);
|
||||||
|
if (!alternateRes)
|
||||||
|
{
|
||||||
|
return alternateRes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, Error> Analyzer::analyzeIdentiExpr(IdentiExpr *expr)
|
||||||
|
{
|
||||||
|
auto sym = env.Resolve(expr->name);
|
||||||
|
if (sym == std::nullopt)
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::UseUndeclaredIdentifier,
|
||||||
|
std::format("`{}` has not been defined", expr->name),
|
||||||
|
"none",
|
||||||
|
makeSourceLocation(expr)));
|
||||||
|
}
|
||||||
|
// TODO: 引入 Module 跨文件 import,检查 isPublic
|
||||||
|
expr->resolvedType = sym->type;
|
||||||
|
expr->resolvedDepth = sym->depth;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, Error> Analyzer::analyzeInfixExpr(InfixExpr *expr)
|
||||||
|
{
|
||||||
|
auto resL = analyzeExpr(expr->left);
|
||||||
|
if (!resL)
|
||||||
|
return std::unexpected(resL.error());
|
||||||
|
|
||||||
|
auto resR = analyzeExpr(expr->right);
|
||||||
|
if (!resR)
|
||||||
|
return std::unexpected(resR.error());
|
||||||
|
|
||||||
|
TypeTag lType = expr->left->resolvedType;
|
||||||
|
TypeTag rType = expr->right->resolvedType;
|
||||||
|
|
||||||
|
switch (expr->op)
|
||||||
|
{
|
||||||
|
// 1. 算术族 (+, -, *, /, **)
|
||||||
|
case BinaryOperator::Add:
|
||||||
|
if (lType == TypeTag::String && rType == TypeTag::String)
|
||||||
|
{
|
||||||
|
expr->resolvedType = TypeTag::String;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
case BinaryOperator::Subtract:
|
||||||
|
case BinaryOperator::Multiply:
|
||||||
|
case BinaryOperator::Divide:
|
||||||
|
case BinaryOperator::Power:
|
||||||
|
if (lType == TypeTag::Int && rType == TypeTag::Int)
|
||||||
|
{
|
||||||
|
expr->resolvedType = TypeTag::Int;
|
||||||
|
}
|
||||||
|
else if ((lType == TypeTag::Int || lType == TypeTag::Double)
|
||||||
|
&& (rType == TypeTag::Int || rType == TypeTag::Double))
|
||||||
|
{
|
||||||
|
expr->resolvedType = TypeTag::Double;
|
||||||
|
}
|
||||||
|
else if (lType == TypeTag::Any || rType == TypeTag::Any)
|
||||||
|
{
|
||||||
|
expr->resolvedType = TypeTag::Any;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
"invalid operands for arithmetic operation",
|
||||||
|
"ensure both sides are numbers (Int or Double)",
|
||||||
|
makeSourceLocation(expr->right)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 2. 整数特化族 (%, &, |, ^, <<, >>)
|
||||||
|
case BinaryOperator::Modulo:
|
||||||
|
case BinaryOperator::BitAnd:
|
||||||
|
case BinaryOperator::BitOr:
|
||||||
|
case BinaryOperator::BitXor:
|
||||||
|
case BinaryOperator::ShiftLeft:
|
||||||
|
case BinaryOperator::ShiftRight:
|
||||||
|
if (lType == TypeTag::Int && rType == TypeTag::Int)
|
||||||
|
{
|
||||||
|
expr->resolvedType = TypeTag::Int;
|
||||||
|
}
|
||||||
|
else if (lType == TypeTag::Any || rType == TypeTag::Any)
|
||||||
|
{
|
||||||
|
expr->resolvedType = TypeTag::Any;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
"bitwise and modulo operations require Int operands",
|
||||||
|
"cast operands to Int before operation",
|
||||||
|
makeSourceLocation(expr->right)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// 3. 比较族 (==, !=, <, >, <=, >=)
|
||||||
|
|
||||||
|
case BinaryOperator::Equal:
|
||||||
|
case BinaryOperator::NotEqual:
|
||||||
|
case BinaryOperator::Less:
|
||||||
|
case BinaryOperator::Greater:
|
||||||
|
case BinaryOperator::LessEqual:
|
||||||
|
case BinaryOperator::GreaterEqual:
|
||||||
|
case BinaryOperator::Is:
|
||||||
|
if (lType != TypeTag::Any && rType != TypeTag::Any && lType != rType) // lType == rType放行
|
||||||
|
{
|
||||||
|
if (!((lType == TypeTag::Int && rType == TypeTag::Double)
|
||||||
|
|| (lType == TypeTag::Double && rType == TypeTag::Int)))
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
"cannot compare different types",
|
||||||
|
"ensure both sides of the comparison are of the same type",
|
||||||
|
makeSourceLocation(expr)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 支持Struct后进行检查,右操作数是 Struct才合理
|
||||||
|
// 如 1.2 is Int --> false
|
||||||
|
// 1 is Int --> true
|
||||||
|
|
||||||
|
expr->resolvedType = TypeTag::Bool;
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// 4. 逻辑族 (&&, ||)
|
||||||
|
case BinaryOperator::LogicalAnd:
|
||||||
|
case BinaryOperator::LogicalOr:
|
||||||
|
if (lType == TypeTag::Bool && rType == TypeTag::Bool)
|
||||||
|
{
|
||||||
|
expr->resolvedType = TypeTag::Bool;
|
||||||
|
}
|
||||||
|
else if (lType == TypeTag::Any || rType == TypeTag::Any)
|
||||||
|
{
|
||||||
|
expr->resolvedType = TypeTag::Bool;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
"logical operators require Bool operands",
|
||||||
|
"use boolean expressions",
|
||||||
|
makeSourceLocation(expr)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// 5. 纯赋值与复合赋值族 (=, +=, -=, ...)
|
||||||
|
case BinaryOperator::Assign:
|
||||||
|
case BinaryOperator::AddAssign:
|
||||||
|
case BinaryOperator::SubAssign:
|
||||||
|
case BinaryOperator::MultiplyAssign:
|
||||||
|
case BinaryOperator::DivideAssign:
|
||||||
|
case BinaryOperator::ModuloAssign:
|
||||||
|
case BinaryOperator::BitXorAssign:
|
||||||
|
// 左侧必须是合法的 L-Value
|
||||||
|
if (!isValidLvalue(expr->left))
|
||||||
|
{
|
||||||
|
return std::unexpected(
|
||||||
|
Error(ErrorType::NotAnLvalue,
|
||||||
|
"invalid assignment target",
|
||||||
|
"left side must be a variable, property, or indexable target",
|
||||||
|
makeSourceLocation(expr->left) // 错误精准定位到左侧节点
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型匹配拦截 (纯赋值)
|
||||||
|
if (expr->op == BinaryOperator::Assign)
|
||||||
|
{
|
||||||
|
if (lType != TypeTag::Any && rType != TypeTag::Any && lType != rType)
|
||||||
|
{
|
||||||
|
if (!(lType == TypeTag::Double && rType == TypeTag::Int))
|
||||||
|
{ // 允许 Int 赋给 Double
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
"cannot assign value to variable of different type",
|
||||||
|
"ensure the assigned value matches the declared type",
|
||||||
|
makeSourceLocation(expr->right)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expr->resolvedType = lType;
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// 6. 成员访问 (.)
|
||||||
|
case BinaryOperator::MemberAccess:
|
||||||
|
if (lType != TypeTag::Struct && lType != TypeTag::Any)
|
||||||
|
{
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
"member access requires a Struct object",
|
||||||
|
"check if the left side evaluates to an object",
|
||||||
|
makeSourceLocation(expr->left)));
|
||||||
|
}
|
||||||
|
expr->resolvedType = TypeTag::Any;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
"unknown binary operator in static analysis",
|
||||||
|
"this is likely an internal compiler error",
|
||||||
|
makeSourceLocation(expr)));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, Error> Analyzer::analyzeStmt(Stmt *stmt)
|
||||||
|
{
|
||||||
|
if (!stmt)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
switch (stmt->type)
|
||||||
|
{
|
||||||
|
case AstType::VarDecl: return analyzeVarDecl(static_cast<VarDecl *>(stmt));
|
||||||
|
|
||||||
|
case AstType::ExprStmt: {
|
||||||
|
auto *exprStmt = static_cast<ExprStmt *>(stmt);
|
||||||
|
return analyzeExpr(exprStmt->expr); // 表达式语句只需要推导内部表达式即可
|
||||||
|
}
|
||||||
|
|
||||||
|
case AstType::BlockStmt: {
|
||||||
|
auto *block = static_cast<BlockStmt *>(stmt);
|
||||||
|
env.EnterScope(); // 进入新大括号,作用域深度 +1
|
||||||
|
for (auto *s : block->nodes)
|
||||||
|
{
|
||||||
|
auto res = analyzeStmt(s);
|
||||||
|
if (!res)
|
||||||
|
return std::unexpected(res.error());
|
||||||
|
}
|
||||||
|
env.LeaveScope(); // 离开大括号,自动销毁局部类型记录
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
case AstType::IfStmt: {
|
||||||
|
return analyzeIfStmt(static_cast<IfStmt *>(stmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 其他语句分析
|
||||||
|
|
||||||
|
// default:
|
||||||
|
// return std::unexpected(Error(ErrorType::TypeError,
|
||||||
|
// "unsupported statement type in analyzer",
|
||||||
|
// "internal compiler error",
|
||||||
|
// makeSourceLocation(stmt)));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, Error> Analyzer::analyzeExpr(Expr *expr)
|
||||||
|
{
|
||||||
|
if (!expr)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
switch (expr->type)
|
||||||
|
{
|
||||||
|
case AstType::LiteralExpr: {
|
||||||
|
auto *lit = static_cast<LiteralExpr *>(expr);
|
||||||
|
switch(lit->token.type)
|
||||||
|
{
|
||||||
|
case TokenType::LiteralTrue:
|
||||||
|
case TokenType::LiteralFalse:
|
||||||
|
lit->resolvedType = TypeTag::Bool;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TokenType::LiteralNull:
|
||||||
|
lit->resolvedType = TypeTag::Null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TokenType::LiteralNumber: {
|
||||||
|
const String &lexeme = manager.GetSub(lit->token.index, lit->token.length);
|
||||||
|
if (lexeme.contains(U'.') || lexeme.contains(U'e'))
|
||||||
|
{
|
||||||
|
lit->resolvedType = TypeTag::Double;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lit->resolvedType = TypeTag::Int;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TokenType::LiteralString: {
|
||||||
|
lit->resolvedType = TypeTag::String;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
lit->resolvedType = TypeTag::Any;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
case AstType::IdentiExpr: return analyzeIdentiExpr(static_cast<IdentiExpr *>(expr));
|
||||||
|
|
||||||
|
case AstType::InfixExpr:
|
||||||
|
return analyzeInfixExpr(static_cast<InfixExpr *>(expr));
|
||||||
|
|
||||||
|
// TODO: PrefixExpr (前缀), CallExpr (函数调用), MemberExpr (属性访问)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 对于还没实现的表达式,默认降级为 Any 防止崩溃
|
||||||
|
expr->resolvedType = TypeTag::Any;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, Error> Analyzer::Analyze(Program *program)
|
||||||
|
{
|
||||||
|
for (auto *stmt : program->nodes)
|
||||||
|
{
|
||||||
|
auto res = analyzeStmt(stmt);
|
||||||
|
if (!res)
|
||||||
|
return std::unexpected(res.error()); // 遇到任何错误,立刻中断并向上传递
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace Fig
|
||||||
70
src/Sema/Analyzer.hpp
Normal file
70
src/Sema/Analyzer.hpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*!
|
||||||
|
@file src/Sema/Analyzer.hpp
|
||||||
|
@brief 前端类型检查器定义
|
||||||
|
@author PuqiAR (im@puqiar.top)
|
||||||
|
@date 2026-02-23
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Sema/Environment.hpp>
|
||||||
|
#include <Sema/Type.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
#include <Ast/Ast.hpp>
|
||||||
|
#include <Deps/Deps.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Fig
|
||||||
|
{
|
||||||
|
class Analyzer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Environment env;
|
||||||
|
SourceManager &manager;
|
||||||
|
|
||||||
|
SourceLocation makeSourceLocation(AstNode *ast, std::source_location loc = std::source_location::current())
|
||||||
|
{
|
||||||
|
return SourceLocation(
|
||||||
|
ast->location.sp,
|
||||||
|
ast->location.fileName,
|
||||||
|
"[internal analyzer]",
|
||||||
|
loc.function_name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValidLvalue(Expr *expr)
|
||||||
|
{
|
||||||
|
if (expr->type == AstType::IdentiExpr)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (expr->type == AstType::InfixExpr)
|
||||||
|
{
|
||||||
|
InfixExpr *infix = static_cast<InfixExpr *>(expr);
|
||||||
|
if (infix->op == BinaryOperator::MemberAccess)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expr->type == AstType::IndexExpr)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, Error> analyzeVarDecl(VarDecl *);
|
||||||
|
Result<void, Error> analyzeIfStmt(IfStmt *);
|
||||||
|
|
||||||
|
Result<void, Error> analyzeIdentiExpr(IdentiExpr *);
|
||||||
|
Result<void, Error> analyzeInfixExpr(InfixExpr *);
|
||||||
|
|
||||||
|
Result<void, Error> analyzeStmt(Stmt *);
|
||||||
|
Result<void, Error> analyzeExpr(Expr *);
|
||||||
|
public:
|
||||||
|
Result<void, Error> Analyze(Program *);
|
||||||
|
|
||||||
|
Analyzer(SourceManager &_manager) : manager(_manager) {}
|
||||||
|
};
|
||||||
|
}; // namespace Fig
|
||||||
50
src/Sema/AnalyzerTest.cpp
Normal file
50
src/Sema/AnalyzerTest.cpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#include <Sema/Analyzer.hpp>
|
||||||
|
|
||||||
|
#include <Deps/Deps.hpp>
|
||||||
|
#include <Error/Error.hpp>
|
||||||
|
#include <Lexer/Lexer.hpp>
|
||||||
|
#include <Parser/Parser.hpp>
|
||||||
|
#include <SourceManager/SourceManager.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
using namespace Fig;
|
||||||
|
|
||||||
|
const String &fileName = "test.fig";
|
||||||
|
const String &filePath = "T:/Files/Maker/Code/MyCodingLanguage/The Fig Project/Fig/test.fig";
|
||||||
|
|
||||||
|
SourceManager manager(filePath);
|
||||||
|
manager.Read();
|
||||||
|
|
||||||
|
if (!manager.read)
|
||||||
|
{
|
||||||
|
std::cerr << "Read file failed \n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexer lexer(manager.GetSource(), fileName);
|
||||||
|
Parser parser(lexer, manager, fileName);
|
||||||
|
|
||||||
|
const auto &result = parser.Parse();
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
ReportError(result.error(), manager);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Program *program = *result;
|
||||||
|
|
||||||
|
Analyzer analyzer(manager);
|
||||||
|
|
||||||
|
const auto &analyzeResult = analyzer.Analyze(program);
|
||||||
|
if (!analyzeResult)
|
||||||
|
{
|
||||||
|
ReportError(analyzeResult.error(), manager);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Analyze successfully, PROGRAM OK\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
80
src/Sema/Environment.hpp
Normal file
80
src/Sema/Environment.hpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*!
|
||||||
|
@file src/Sema/Environment.hpp
|
||||||
|
@brief 符号和作用域环境定义
|
||||||
|
@author PuqiAR (im@puqiar.top)
|
||||||
|
@date 2026-02-23
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Sema/Type.hpp>
|
||||||
|
#include <Deps/Deps.hpp>
|
||||||
|
#include <Error/Error.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace Fig
|
||||||
|
{
|
||||||
|
|
||||||
|
// 记录在 Analyzer 中的符号元数据
|
||||||
|
struct Symbol
|
||||||
|
{
|
||||||
|
String name;
|
||||||
|
TypeTag type;
|
||||||
|
bool isPublic;
|
||||||
|
int depth; // 词法作用域深度
|
||||||
|
bool isConstant; // 是否是 const 声明的不可变常量 (用于报错: 尝试修改常量)
|
||||||
|
};
|
||||||
|
|
||||||
|
class Environment
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
DynArray<Symbol> symbols;
|
||||||
|
int currentDepth = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void EnterScope()
|
||||||
|
{
|
||||||
|
currentDepth++;
|
||||||
|
}
|
||||||
|
void LeaveScope()
|
||||||
|
{
|
||||||
|
while (!symbols.empty() && symbols.back().depth > currentDepth)
|
||||||
|
{
|
||||||
|
symbols.pop_back();
|
||||||
|
}
|
||||||
|
currentDepth--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册符号, 调用前确保无重复 (内部assert)
|
||||||
|
void Define(const String &name, TypeTag type, bool isPublic, bool isConst)
|
||||||
|
{
|
||||||
|
for (auto it = symbols.rbegin(); it != symbols.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (it->depth < currentDepth)
|
||||||
|
break;
|
||||||
|
if (it->name == name)
|
||||||
|
{
|
||||||
|
assert(false && "Environment.Define: redefinition");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
symbols.push_back({name, type, isPublic, currentDepth, isConst});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析符号。找不到返回 nullopt
|
||||||
|
std::optional<Symbol> Resolve(const String &name)
|
||||||
|
{
|
||||||
|
for (auto it = symbols.rbegin(); it != symbols.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (it->name == name)
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetDepth() const
|
||||||
|
{
|
||||||
|
return currentDepth;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace Fig
|
||||||
28
src/Sema/Type.hpp
Normal file
28
src/Sema/Type.hpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*!
|
||||||
|
@file src/Sema/Type.hpp
|
||||||
|
@brief 前端类型检查的类型定义
|
||||||
|
@author PuqiAR (im@puqiar.top)
|
||||||
|
@date 2026-02-23
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Fig
|
||||||
|
{
|
||||||
|
enum class TypeTag : std::uint8_t
|
||||||
|
{
|
||||||
|
Any, // 动态类型底线
|
||||||
|
Null, // 空值
|
||||||
|
Int,
|
||||||
|
Double,
|
||||||
|
Bool,
|
||||||
|
String,
|
||||||
|
Function,
|
||||||
|
Struct,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: 复杂类型的推导(泛型,结构体)
|
||||||
|
// 添加 TypeInfo 结构体,目前先用 TypeTag
|
||||||
|
};
|
||||||
@@ -100,6 +100,11 @@ namespace Fig
|
|||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String &GetSource()
|
||||||
|
{
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<size_t, size_t> GetLineColumn(size_t index) const
|
std::pair<size_t, size_t> GetLineColumn(size_t index) const
|
||||||
{
|
{
|
||||||
if (lineStartIndex.empty())
|
if (lineStartIndex.empty())
|
||||||
@@ -130,5 +135,12 @@ namespace Fig
|
|||||||
size_t column = index - lineStartIndex[line] + 1;
|
size_t column = index - lineStartIndex[line] + 1;
|
||||||
return {line + 1, column};
|
return {line + 1, column};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoadFromMemory(String src)
|
||||||
|
{
|
||||||
|
source = std::move(src);
|
||||||
|
read = true;
|
||||||
|
preprocessLineIndices();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}; // namespace Fig
|
}; // namespace Fig
|
||||||
25526
src/Utils/json/json.hpp
Normal file
25526
src/Utils/json/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
11
src/main.cpp
11
src/main.cpp
@@ -4,6 +4,7 @@
|
|||||||
#include <Lexer/Lexer.hpp>
|
#include <Lexer/Lexer.hpp>
|
||||||
#include <Parser/Parser.hpp>
|
#include <Parser/Parser.hpp>
|
||||||
#include <SourceManager/SourceManager.hpp>
|
#include <SourceManager/SourceManager.hpp>
|
||||||
|
#include <Sema/Analyzer.hpp>
|
||||||
#include <VM/VM.hpp>
|
#include <VM/VM.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -37,6 +38,16 @@ int main()
|
|||||||
}
|
}
|
||||||
Program *program = *program_result;
|
Program *program = *program_result;
|
||||||
|
|
||||||
|
Analyzer analyzer(manager);
|
||||||
|
const auto &analyzeResult = analyzer.Analyze(program);
|
||||||
|
if (!analyzeResult)
|
||||||
|
{
|
||||||
|
ReportError(analyzeResult.error(), manager);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::cout << "analyzer: Program OK, PASSED\n";
|
||||||
|
|
||||||
|
|
||||||
Compiler compiler(fileName, manager);
|
Compiler compiler(fileName, manager);
|
||||||
const auto &proto_result = compiler.Compile(program);
|
const auto &proto_result = compiler.Compile(program);
|
||||||
if (!proto_result)
|
if (!proto_result)
|
||||||
|
|||||||
35
xmake.lua
35
xmake.lua
@@ -53,6 +53,20 @@ target("ObjectTest")
|
|||||||
add_files("src/Object/Object.cpp")
|
add_files("src/Object/Object.cpp")
|
||||||
add_files("src/Object/ObjectTest.cpp")
|
add_files("src/Object/ObjectTest.cpp")
|
||||||
|
|
||||||
|
target("AnalyzerTest")
|
||||||
|
add_files("src/Core/*.cpp")
|
||||||
|
add_files("src/Token/Token.cpp")
|
||||||
|
add_files("src/Error/Error.cpp")
|
||||||
|
add_files("src/Lexer/Lexer.cpp")
|
||||||
|
|
||||||
|
add_files("src/Ast/Operator.cpp")
|
||||||
|
add_files("src/Parser/ExprParser.cpp")
|
||||||
|
add_files("src/Parser/StmtParser.cpp")
|
||||||
|
add_files("src/Parser/Parser.cpp")
|
||||||
|
|
||||||
|
add_files("src/Sema/Analyzer.cpp")
|
||||||
|
add_files("src/Sema/AnalyzerTest.cpp")
|
||||||
|
|
||||||
target("CompilerTest")
|
target("CompilerTest")
|
||||||
add_files("src/Core/*.cpp")
|
add_files("src/Core/*.cpp")
|
||||||
add_files("src/Token/Token.cpp")
|
add_files("src/Token/Token.cpp")
|
||||||
@@ -66,12 +80,31 @@ target("CompilerTest")
|
|||||||
|
|
||||||
add_files("src/Object/Object.cpp")
|
add_files("src/Object/Object.cpp")
|
||||||
|
|
||||||
|
add_files("src/Sema/Analyzer.cpp")
|
||||||
|
|
||||||
add_files("src/Compiler/ExprCompiler.cpp")
|
add_files("src/Compiler/ExprCompiler.cpp")
|
||||||
add_files("src/Compiler/StmtCompiler.cpp")
|
add_files("src/Compiler/StmtCompiler.cpp")
|
||||||
add_files("src/Compiler/Compiler.cpp")
|
add_files("src/Compiler/Compiler.cpp")
|
||||||
|
|
||||||
add_files("src/Compiler/CompileTest.cpp")
|
add_files("src/Compiler/CompileTest.cpp")
|
||||||
|
|
||||||
|
target("LSP")
|
||||||
|
add_files("src/Core/*.cpp")
|
||||||
|
add_files("src/Token/Token.cpp")
|
||||||
|
add_files("src/Error/Error.cpp")
|
||||||
|
add_files("src/Lexer/Lexer.cpp")
|
||||||
|
|
||||||
|
add_files("src/Ast/Operator.cpp")
|
||||||
|
add_files("src/Parser/ExprParser.cpp")
|
||||||
|
add_files("src/Parser/StmtParser.cpp")
|
||||||
|
add_files("src/Parser/Parser.cpp")
|
||||||
|
|
||||||
|
add_files("src/Sema/Analyzer.cpp")
|
||||||
|
|
||||||
|
add_files("src/LSP/FigLSPServer.cpp")
|
||||||
|
|
||||||
|
set_filename("Fig-LSP")
|
||||||
|
|
||||||
target("Fig")
|
target("Fig")
|
||||||
add_files("src/Core/*.cpp")
|
add_files("src/Core/*.cpp")
|
||||||
add_files("src/Token/Token.cpp")
|
add_files("src/Token/Token.cpp")
|
||||||
@@ -85,6 +118,8 @@ target("Fig")
|
|||||||
|
|
||||||
add_files("src/Object/Object.cpp")
|
add_files("src/Object/Object.cpp")
|
||||||
|
|
||||||
|
add_files("src/Sema/Analyzer.cpp")
|
||||||
|
|
||||||
add_files("src/Compiler/ExprCompiler.cpp")
|
add_files("src/Compiler/ExprCompiler.cpp")
|
||||||
add_files("src/Compiler/StmtCompiler.cpp")
|
add_files("src/Compiler/StmtCompiler.cpp")
|
||||||
add_files("src/Compiler/Compiler.cpp")
|
add_files("src/Compiler/Compiler.cpp")
|
||||||
|
|||||||
Reference in New Issue
Block a user