实现 12.6 符号表与链接:全局定槽、引用解析、循环检测。
- Linker.h/cpp:收集导出(仅 gvl 文件顶层 VAR_GLOBAL、声明顺序定槽、重复名拒绝) - VAR_EXTERNAL 接同一槽、类型一致;FB 实例必须先声明;字段存在性校验;函数调用图 DFS 环检测 - io.var 必须在 GVL;内建 TON/TOF/CTU 布局冻结;用户 FB 字段 = input/output/var(external 不是字段) - 函数名赋值 = 结果值写入(用例 13 约定);dump_symbols 打印 name kind type address file - linker_test:51 断言(用例 09/10/11/12/16/19 + line1/17 正例),ctest 7/7;20 用例扫描 15 绿 5 按期望拒
This commit is contained in:
@@ -10,7 +10,8 @@ project(Compiler
|
||||
add_library(compiler STATIC
|
||||
./src/Lexer.cpp
|
||||
./src/Project.cpp
|
||||
./src/Parser.cpp)
|
||||
./src/Parser.cpp
|
||||
./src/Linker.cpp)
|
||||
|
||||
target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @file Linker.h
|
||||
* @brief 符号表与链接
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Parser.h"
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
// 符号种类(与 Doc/初步计划.md 12.1 一致)
|
||||
enum class SymbolKind { Global, External, Local, Input, Output, Pou, FbInstance, Const };
|
||||
|
||||
struct Symbol {
|
||||
SymbolKind kind = SymbolKind::Local;
|
||||
std::string name; // 小写
|
||||
std::string type_name; // bool / int / time / fb 类型名(小写)
|
||||
uint32_t address = 0; // 全局槽号(Global/External);局部暂 0(12.8 分配)
|
||||
std::string source_file;
|
||||
uint32_t line = 0;
|
||||
uint32_t col = 0;
|
||||
};
|
||||
|
||||
// FB 类型字段(实例布局用;v1 字段类型仅 BOOL/INT/TIME)
|
||||
struct FbField {
|
||||
std::string name;
|
||||
TypeKind type = TypeKind::Bool;
|
||||
};
|
||||
|
||||
struct FbLayout {
|
||||
std::string type_name; // 类型名(小写)
|
||||
std::vector<FbField> fields; // 段序:input → output → 内部 var
|
||||
};
|
||||
|
||||
// 一个源文件的 AST(供链接输入)
|
||||
struct SourceUnit {
|
||||
std::string path;
|
||||
Unit ast;
|
||||
};
|
||||
|
||||
// 链接结果
|
||||
struct LinkResult {
|
||||
// 全局槽表:下标即槽号(声明顺序)
|
||||
std::vector<Symbol> globals;
|
||||
std::map<std::string, uint32_t> global_index; // 名 → 槽号
|
||||
|
||||
// 用户 FB 类型布局(按类型名)
|
||||
std::map<std::string, FbLayout> fb_types;
|
||||
|
||||
// 每 POU 的局部符号与 FB 实例
|
||||
struct PouScope {
|
||||
std::string name;
|
||||
PouKind kind = PouKind::Program;
|
||||
std::vector<Symbol> syms; // local/input/output/external/fb_instance
|
||||
std::map<std::string, FbLayout> fb_instances; // 实例名 → 布局
|
||||
};
|
||||
std::vector<PouScope> scopes;
|
||||
|
||||
// 函数表顺序(PROGRAM / FUNCTION / FUNCTION_BLOCK 名,收集序)
|
||||
std::vector<std::string> fn_order;
|
||||
};
|
||||
|
||||
// 链接一个工程:校验 GVL、定全局槽、解析 VAR_EXTERNAL、FB 实例与字段、
|
||||
// 函数循环调用检测、io 校验。失败返回 false,err 前缀 "link error"。
|
||||
bool link_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
LinkResult* out, std::string* err);
|
||||
|
||||
// 读取并解析一个 .st 文件成 SourceUnit
|
||||
bool load_unit(const std::string& path, SourceUnit* out, std::string* err);
|
||||
|
||||
// 符号表文本:每行 "name kind type address file"(全局表 + 各 POU 局部表)
|
||||
std::string dump_symbols(const LinkResult& r);
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
/**
|
||||
* @file Linker.cpp
|
||||
* @brief 符号表与链接
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#include "compiler/Linker.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
const char* kind_name(SymbolKind k) {
|
||||
switch (k) {
|
||||
case SymbolKind::Global: return "global";
|
||||
case SymbolKind::External: return "external";
|
||||
case SymbolKind::Local: return "local";
|
||||
case SymbolKind::Input: return "input";
|
||||
case SymbolKind::Output: return "output";
|
||||
case SymbolKind::Pou: return "pou";
|
||||
case SymbolKind::FbInstance: return "fb_instance";
|
||||
case SymbolKind::Const: return "const";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
std::string type_str(const TypeRef& t) {
|
||||
switch (t.kind) {
|
||||
case TypeKind::Bool: return "bool";
|
||||
case TypeKind::Int: return "int";
|
||||
case TypeKind::Time: return "time";
|
||||
case TypeKind::FbBuiltin:
|
||||
case TypeKind::FbUser: return t.name;
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
bool is_scalar(TypeKind k) {
|
||||
return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time;
|
||||
}
|
||||
|
||||
// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md)
|
||||
const FbLayout kTonLayout{"ton", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
|
||||
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
|
||||
const FbLayout kTofLayout{"tof", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
|
||||
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
|
||||
const FbLayout kCtuLayout{"ctu", {{"cu", TypeKind::Bool}, {"r", TypeKind::Bool},
|
||||
{"pv", TypeKind::Int}, {"q", TypeKind::Bool},
|
||||
{"cv", TypeKind::Int}}};
|
||||
|
||||
const FbLayout* builtin_layout(const std::string& name) {
|
||||
if (name == "ton") return &kTonLayout;
|
||||
if (name == "tof") return &kTofLayout;
|
||||
if (name == "ctu") return &kCtuLayout;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class Linker {
|
||||
public:
|
||||
Linker(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
LinkResult* out, std::string* err)
|
||||
: proj_(proj), units_(units), out_(out), err_(err) {
|
||||
gvl_path_ = std::filesystem::weakly_canonical(
|
||||
std::filesystem::path(proj.base_dir) / proj.gvl_file);
|
||||
}
|
||||
|
||||
bool run() {
|
||||
if (!collect_exports()) return false;
|
||||
if (!collect_fb_types()) return false;
|
||||
if (!resolve_scopes()) return false;
|
||||
if (!check_cycles()) return false;
|
||||
if (!check_io()) return false;
|
||||
if (!has_program_main()) {
|
||||
return fail("", "missing entry 'program MAIN'", 0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool fail(const std::string& file, const std::string& msg,
|
||||
uint32_t line, uint32_t col) {
|
||||
if (err_) {
|
||||
char buf[256];
|
||||
if (line) {
|
||||
std::snprintf(buf, sizeof buf, " (%s, line %u, col %u)",
|
||||
file.c_str(), line, col);
|
||||
} else {
|
||||
std::snprintf(buf, sizeof buf, " (%s)", file.c_str());
|
||||
}
|
||||
*err_ = "link error: " + msg + buf;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_gvl_file(const std::string& path) const {
|
||||
if (proj_.gvl_file.empty()) {
|
||||
return false;
|
||||
}
|
||||
return std::filesystem::weakly_canonical(path) == gvl_path_;
|
||||
}
|
||||
|
||||
// ---- 1. 收集导出 ----
|
||||
|
||||
bool collect_exports() {
|
||||
for (const SourceUnit& u : units_) {
|
||||
const bool is_gvl = is_gvl_file(u.path);
|
||||
|
||||
for (const VarBlock& b : u.ast.globals) {
|
||||
if (!is_gvl) {
|
||||
return fail(u.path, "VAR_GLOBAL only allowed in gvl file",
|
||||
b.vars.empty() ? 0 : b.vars[0].line,
|
||||
b.vars.empty() ? 0 : b.vars[0].col);
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
if (out_->global_index.count(d.name)) {
|
||||
return fail(u.path, "duplicate global '" + d.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
Symbol s;
|
||||
s.kind = SymbolKind::Global;
|
||||
s.name = d.name;
|
||||
s.type_name = type_str(d.type);
|
||||
s.address = static_cast<uint32_t>(out_->globals.size());
|
||||
s.source_file = u.path;
|
||||
s.line = d.line;
|
||||
s.col = d.col;
|
||||
out_->global_index[s.name] = s.address;
|
||||
out_->globals.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
for (const POU& p : u.ast.pous) {
|
||||
for (const LinkResult::PouScope& sc : out_->scopes) {
|
||||
if (sc.name == p.name) {
|
||||
return fail(u.path, "duplicate POU '" + p.name + "'",
|
||||
0, 0);
|
||||
}
|
||||
}
|
||||
LinkResult::PouScope sc;
|
||||
sc.name = p.name;
|
||||
sc.kind = p.kind;
|
||||
out_->scopes.push_back(std::move(sc));
|
||||
out_->fn_order.push_back(p.name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 2. 用户 FB 类型布局 ----
|
||||
|
||||
bool collect_fb_types() {
|
||||
for (const SourceUnit& u : units_) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
if (p.kind != PouKind::FunctionBlock) {
|
||||
continue;
|
||||
}
|
||||
FbLayout lay;
|
||||
lay.type_name = p.name;
|
||||
for (const VarBlock& b : p.blocks) {
|
||||
// 字段 = input / output / 内部 var;external 与 global 不是字段
|
||||
if (b.section == VarSection::External ||
|
||||
b.section == VarSection::Global) {
|
||||
continue;
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
if (!is_scalar(d.type.kind)) {
|
||||
return fail(u.path,
|
||||
"FB field type must be BOOL/INT/TIME ('" +
|
||||
d.name + "')",
|
||||
d.line, d.col);
|
||||
}
|
||||
FbField f;
|
||||
f.name = d.name;
|
||||
f.type = d.type.kind;
|
||||
lay.fields.push_back(f);
|
||||
}
|
||||
}
|
||||
out_->fb_types[p.name] = lay;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 3. 逐 POU 建局部符号并解析引用 ----
|
||||
|
||||
bool resolve_scopes() {
|
||||
for (SourceUnit& u : const_cast<std::vector<SourceUnit>&>(units_)) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
LinkResult::PouScope& sc = scope_of(p.name);
|
||||
if (!build_scope_syms(u.path, p, sc)) {
|
||||
return false;
|
||||
}
|
||||
for (const Stmt& st : p.body) {
|
||||
if (!check_stmt(u.path, sc, p, st)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
LinkResult::PouScope& scope_of(const std::string& name) {
|
||||
for (LinkResult::PouScope& sc : out_->scopes) {
|
||||
if (sc.name == name) {
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
static LinkResult::PouScope kEmpty;
|
||||
return kEmpty;
|
||||
}
|
||||
|
||||
bool build_scope_syms(const std::string& file, const POU& p,
|
||||
LinkResult::PouScope& sc) {
|
||||
for (const VarBlock& b : p.blocks) {
|
||||
for (const VarDecl& d : b.vars) {
|
||||
switch (b.section) {
|
||||
case VarSection::Global:
|
||||
return fail(file, "VAR_GLOBAL only allowed in gvl file",
|
||||
d.line, d.col);
|
||||
case VarSection::External: {
|
||||
const auto it = out_->global_index.find(d.name);
|
||||
if (it == out_->global_index.end()) {
|
||||
return fail(file, "undeclared external '" + d.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
const Symbol& g = out_->globals[it->second];
|
||||
if (g.type_name != type_str(d.type)) {
|
||||
return fail(file, "external type mismatch for '" + d.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
Symbol s;
|
||||
s.kind = SymbolKind::External;
|
||||
s.name = d.name;
|
||||
s.type_name = g.type_name;
|
||||
s.address = g.address;
|
||||
s.source_file = file;
|
||||
s.line = d.line;
|
||||
s.col = d.col;
|
||||
sc.syms.push_back(s);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const bool is_fb = d.type.kind == TypeKind::FbUser ||
|
||||
d.type.kind == TypeKind::FbBuiltin;
|
||||
Symbol s;
|
||||
s.kind = is_fb ? SymbolKind::FbInstance
|
||||
: (b.section == VarSection::Input
|
||||
? SymbolKind::Input
|
||||
: b.section == VarSection::Output
|
||||
? SymbolKind::Output
|
||||
: SymbolKind::Local);
|
||||
s.name = d.name;
|
||||
s.type_name = type_str(d.type);
|
||||
s.source_file = file;
|
||||
s.line = d.line;
|
||||
s.col = d.col;
|
||||
sc.syms.push_back(s);
|
||||
if (is_fb) {
|
||||
FbLayout lay;
|
||||
if (d.type.kind == TypeKind::FbBuiltin) {
|
||||
const FbLayout* bl = builtin_layout(d.type.name);
|
||||
if (!bl) {
|
||||
return fail(file, "unknown builtin FB '" + d.type.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
lay = *bl;
|
||||
} else {
|
||||
const auto it = out_->fb_types.find(d.type.name);
|
||||
if (it == out_->fb_types.end()) {
|
||||
return fail(file, "unknown FB type '" + d.type.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
lay = it->second;
|
||||
}
|
||||
sc.fb_instances[d.name] = lay;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 引用解析 ----
|
||||
|
||||
const Symbol* find_sym(const LinkResult::PouScope& sc,
|
||||
const std::string& name) const {
|
||||
for (const Symbol& s : sc.syms) {
|
||||
if (s.name == name) {
|
||||
return &s;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool check_stmt(const std::string& file, const LinkResult::PouScope& sc,
|
||||
const POU& pou, const Stmt& st) {
|
||||
switch (st.kind) {
|
||||
case StmtKind::Assign: {
|
||||
// FUNCTION 内对函数名赋值 = 结果值写入(用例 13 约定)
|
||||
const bool is_result_assign =
|
||||
pou.kind == PouKind::Function && st.target == pou.name;
|
||||
if (!is_result_assign && find_sym(sc, st.target) == nullptr &&
|
||||
out_->global_index.count(st.target) == 0) {
|
||||
return fail(file, "undeclared identifier '" + st.target + "'",
|
||||
0, 0);
|
||||
}
|
||||
return check_expr(file, sc, *st.value);
|
||||
}
|
||||
case StmtKind::FbCall: {
|
||||
const Symbol* inst = find_sym(sc, st.instance);
|
||||
if (inst == nullptr) {
|
||||
return fail(file, "undeclared FB instance '" + st.instance + "'",
|
||||
0, 0);
|
||||
}
|
||||
if (inst->kind != SymbolKind::FbInstance) {
|
||||
return fail(file, "'" + st.instance + "' is not an FB instance",
|
||||
0, 0);
|
||||
}
|
||||
const auto it = sc.fb_instances.find(st.instance);
|
||||
const FbLayout& lay = it->second;
|
||||
for (const FbArg& a : st.args) {
|
||||
bool found = false;
|
||||
for (const FbField& f : lay.fields) {
|
||||
if (f.name == a.name) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return fail(file,
|
||||
"unknown input '" + a.name + "' for FB '" +
|
||||
st.instance + "'",
|
||||
0, 0);
|
||||
}
|
||||
if (!check_expr(file, sc, *a.value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case StmtKind::If:
|
||||
if (!check_expr(file, sc, *st.cond)) {
|
||||
return false;
|
||||
}
|
||||
for (const Stmt& s : st.body) {
|
||||
if (!check_stmt(file, sc, pou, s)) return false;
|
||||
}
|
||||
for (const IfBranch& b : st.elsifs) {
|
||||
if (!check_expr(file, sc, *b.cond)) return false;
|
||||
for (const Stmt& s : b.body) {
|
||||
if (!check_stmt(file, sc, pou, s)) return false;
|
||||
}
|
||||
}
|
||||
for (const Stmt& s : st.else_body) {
|
||||
if (!check_stmt(file, sc, pou, s)) return false;
|
||||
}
|
||||
return true;
|
||||
case StmtKind::While:
|
||||
if (!check_expr(file, sc, *st.cond)) {
|
||||
return false;
|
||||
}
|
||||
for (const Stmt& s : st.body) {
|
||||
if (!check_stmt(file, sc, pou, s)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_expr(const std::string& file, const LinkResult::PouScope& sc,
|
||||
const Expr& e) {
|
||||
switch (e.kind) {
|
||||
case ExprKind::VarRef:
|
||||
if (find_sym(sc, e.name) == nullptr &&
|
||||
out_->global_index.count(e.name) == 0) {
|
||||
return fail(file, "undeclared identifier '" + e.name + "'", 0, 0);
|
||||
}
|
||||
return true;
|
||||
case ExprKind::Field: {
|
||||
const Symbol* inst = find_sym(sc, e.name);
|
||||
if (inst == nullptr || inst->kind != SymbolKind::FbInstance) {
|
||||
return fail(file, "undeclared FB instance '" + e.name + "'", 0, 0);
|
||||
}
|
||||
const auto it = sc.fb_instances.find(e.name);
|
||||
bool found = false;
|
||||
for (const FbField& f : it->second.fields) {
|
||||
if (f.name == e.field) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return fail(file, "unknown field '" + e.field + "' for FB '" +
|
||||
e.name + "'",
|
||||
0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Call: {
|
||||
PouKind k = PouKind::Program;
|
||||
if (!find_function(e.name, &k)) {
|
||||
return fail(file, "undeclared function '" + e.name + "'", 0, 0);
|
||||
}
|
||||
if (k != PouKind::Function) {
|
||||
return fail(file, "'" + e.name + "' is not a function", 0, 0);
|
||||
}
|
||||
call_edges_[sc.name].insert(e.name);
|
||||
for (const auto& a : e.args) {
|
||||
if (!check_expr(file, sc, *a)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (e.lhs && !check_expr(file, sc, *e.lhs)) return false;
|
||||
if (e.rhs && !check_expr(file, sc, *e.rhs)) return false;
|
||||
if (e.operand && !check_expr(file, sc, *e.operand)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool find_function(const std::string& name, PouKind* kind) const {
|
||||
for (const LinkResult::PouScope& sc : out_->scopes) {
|
||||
if (sc.name == name) {
|
||||
*kind = sc.kind;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- 4. 循环调用检测(函数依赖图 DFS)----
|
||||
|
||||
bool check_cycles() {
|
||||
std::set<std::string> visiting;
|
||||
std::set<std::string> done;
|
||||
for (const auto& [fn, _] : call_edges_) {
|
||||
if (!dfs(fn, visiting, done)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dfs(const std::string& fn, std::set<std::string>& visiting,
|
||||
std::set<std::string>& done) {
|
||||
if (done.count(fn)) {
|
||||
return true;
|
||||
}
|
||||
if (visiting.count(fn)) {
|
||||
return fail("", "recursive call involving '" + fn + "'", 0, 0);
|
||||
}
|
||||
visiting.insert(fn);
|
||||
const auto it = call_edges_.find(fn);
|
||||
if (it != call_edges_.end()) {
|
||||
for (const std::string& callee : it->second) {
|
||||
if (!dfs(callee, visiting, done)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
visiting.erase(fn);
|
||||
done.insert(fn);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 5. io 校验 ----
|
||||
|
||||
bool check_io() {
|
||||
for (const isa::IoBinding& b : proj_.io) {
|
||||
std::string key = b.var;
|
||||
for (char& ch : key) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
if (out_->global_index.count(key) == 0) {
|
||||
return fail(proj_.name + "/project.toml",
|
||||
"io.var '" + b.var + "' not in GVL", 0, 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 6. 入口 ----
|
||||
|
||||
bool has_program_main() const {
|
||||
for (const LinkResult::PouScope& sc : out_->scopes) {
|
||||
if (sc.kind == PouKind::Program && sc.name == "main") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Project& proj_;
|
||||
const std::vector<SourceUnit>& units_;
|
||||
LinkResult* out_;
|
||||
std::string* err_;
|
||||
std::filesystem::path gvl_path_;
|
||||
std::map<std::string, std::set<std::string>> call_edges_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool link_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
LinkResult* out, std::string* err) {
|
||||
out->globals.clear();
|
||||
out->global_index.clear();
|
||||
out->fb_types.clear();
|
||||
out->scopes.clear();
|
||||
out->fn_order.clear();
|
||||
Linker l(proj, units, out, err);
|
||||
return l.run();
|
||||
}
|
||||
|
||||
bool load_unit(const std::string& path, SourceUnit* out, std::string* err) {
|
||||
out->path = path;
|
||||
return parse_pous_file(path, &out->ast, err);
|
||||
}
|
||||
|
||||
std::string dump_symbols(const LinkResult& r) {
|
||||
std::string out;
|
||||
for (const Symbol& s : r.globals) {
|
||||
out += s.name + " " + kind_name(s.kind) + " " + s.type_name + " " +
|
||||
std::to_string(s.address) + " " + s.source_file + "\n";
|
||||
}
|
||||
for (const LinkResult::PouScope& sc : r.scopes) {
|
||||
out += "[" + sc.name + "]\n";
|
||||
for (const Symbol& s : sc.syms) {
|
||||
out += s.name + " " + kind_name(s.kind) + " " + s.type_name + " " +
|
||||
std::to_string(s.address) + " " + s.source_file + "\n";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
Reference in New Issue
Block a user