阶段3-1:编译器语言层 V2 完成(19 类型/字面量扩展/禁隐式转换/配置驱动 FB)
Lexer V2:
- 19 类型关键字(BOOL..DT);FB 名不再设关键字(IDENT)
- FLOAT_LIT(1.5/2.5e3)、进制前缀(2#/8#/16# 换算十进制)
- 日期字面量 D#/TOD#/DT#(历法换算 1970 纪元、闰年、值域检查)
Parser V2:
- TypeRef 字符串化(TypeKind{Scalar,FbBuiltin,FbUser},名字小写)
- ExprKind 增 LitReal/LitDate/LitTod/LitDt;Expr.double_value
- IDENT 类型统一 FbUser(内建判定在 Linker 查 fb 表)
Typecheck V2:
- TType 19 + Unresolved(字面量未定型)
- 禁隐式转换(赋值/算术/比较严格同型)
- resolve_to:未定型→消费点定型+值域检查(BOOL 0/1、无符号负数、
溢出、浮点仅 REAL/LREAL);算术按 kind 允许(TIME 仅 ±)
- Call 实参按函数参数类型;check_project 加 cfg
Linker V2:
- FbField.type_name 字符串化;内建 FB 布局查 machine.toml [[fb]] 表
(删 8 个硬编码布局,带缓存);link_project 加 cfg
Codegen V2:
- 浮点/日期字面量(LOADK func 8/4/4/6;LitReal 按 REAL 装载,LREAL TODO)
- 算术/比较 func 按操作数类型(expr_func)
- TypeKind 连锁适配
main.cpp:link/check 传 cfg;--disasm 头 22 字段逐行中文注释(L5)
+ slots 节;StbView 补 offset_const/offset_funcs
测试适配:parser/linker/typecheck/vm/cases 适配新接口与 V2 语义。
ctest 11/13(vm_cycles/cases_all 待阶段 4)。codegen_test 仍剥离(阶段 3-2 重写)。
This commit is contained in:
@@ -37,8 +37,24 @@ namespace compiler {
|
||||
VAR_EXTERNAL, ///< 外部变量段起始
|
||||
END_VAR, ///< 变量段结束
|
||||
BOOL, ///< 布尔类型
|
||||
INT, ///< 整数类型
|
||||
TIME, ///< 时间类型
|
||||
BYTE, ///< 位串类型(8 位)
|
||||
WORD, ///< 位串类型(16 位)
|
||||
DWORD, ///< 位串类型(32 位)
|
||||
LWORD, ///< 位串类型(64 位)
|
||||
SINT, ///< 有符号整数(8 位)
|
||||
INT, ///< 有符号整数(16 位)
|
||||
DINT, ///< 有符号整数(32 位)
|
||||
LINT, ///< 有符号整数(64 位)
|
||||
USINT, ///< 无符号整数(8 位)
|
||||
UINT, ///< 无符号整数(16 位)
|
||||
UDINT, ///< 无符号整数(32 位)
|
||||
ULINT, ///< 无符号整数(64 位)
|
||||
TIME, ///< 时间类型(毫秒)
|
||||
REAL, ///< 浮点类型(32 位)
|
||||
LREAL, ///< 浮点类型(64 位)
|
||||
DATE, ///< 日期类型(天数)
|
||||
TOD, ///< 时刻类型(当日毫秒)
|
||||
DT, ///< 日期时刻类型(64 位)
|
||||
IF, ///< IF 关键字
|
||||
ELSIF, ///< ELSIF 关键字
|
||||
ELSE, ///< ELSE 关键字
|
||||
@@ -52,18 +68,14 @@ namespace compiler {
|
||||
NOT, ///< 逻辑非(前缀)
|
||||
TRUE, ///< 布尔真字面量
|
||||
FALSE, ///< 布尔假字面量
|
||||
TON, ///< 内建 FB 类型名
|
||||
TOF, ///< 内建 FB 类型名
|
||||
TP, ///< 内建 FB 类型名
|
||||
CTU, ///< 内建 FB 类型名
|
||||
CTD, ///< 内建 FB 类型名
|
||||
CTUD, ///< 内建 FB 类型名
|
||||
R_TRIG, ///< 内建 FB 类型名
|
||||
F_TRIG, ///< 内建 FB 类型名
|
||||
// 字面量
|
||||
IDENT, ///< 标识符(text = 小写名)
|
||||
INT_LIT, ///< 整数字面量(int_value)
|
||||
IDENT, ///< 标识符(text = 小写名;含 FB 类型名,Typecheck 查表)
|
||||
INT_LIT, ///< 整数字面量(int_value;含进制 2#/8#/16# 已换算)
|
||||
FLOAT_LIT, ///< 浮点字面量(double_value)
|
||||
TIME_LIT, ///< 时间字面量(int_value = 毫秒)
|
||||
DATE_LIT, ///< 日期字面量(int_value = 天数,1970 纪元)
|
||||
TOD_LIT, ///< 时刻字面量(int_value = 当日毫秒)
|
||||
DT_LIT, ///< 日期时刻字面量(int64 = date<<32|tod)
|
||||
// 符号
|
||||
ASSIGN, ///< :=(赋值)
|
||||
EQ, ///< =(等于)
|
||||
@@ -92,7 +104,8 @@ namespace compiler {
|
||||
struct Token {
|
||||
Tok type; ///< token 类型
|
||||
std::string text; ///< 标识符/关键字的小写文本;字面量原文
|
||||
int64_t int_value; ///< INT_LIT / TIME_LIT(毫秒)的字面量值
|
||||
int64_t int_value; ///< INT_LIT / TIME_LIT / DATE_LIT / TOD_LIT / DT_LIT 的字面量值
|
||||
double double_value; ///< FLOAT_LIT 的字面量值
|
||||
std::string source_file; ///< 来源文件名
|
||||
uint32_t line; ///< 起始行(从 1 起)
|
||||
uint32_t col; ///< 起始列(从 1 起)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/MachineConfig.h"
|
||||
#include "compiler/Parser.h"
|
||||
#include "compiler/Project.h"
|
||||
|
||||
@@ -51,11 +52,11 @@ namespace compiler {
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief FB 类型字段(实例布局用;v1 字段类型仅 BOOL/INT/TIME)。
|
||||
* @brief FB 类型字段(实例布局用;V2 字段类型为标量类型名小写)。
|
||||
*/
|
||||
struct FbField {
|
||||
std::string name; ///< 字段名
|
||||
TypeKind type = TypeKind::Bool; ///< 字段类型(仅标量)
|
||||
std::string type_name; ///< 字段类型名(小写,如 "bool"/"time")
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -108,7 +109,7 @@ namespace compiler {
|
||||
* @return true 成功;false 失败(err 已写)
|
||||
*/
|
||||
bool link_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
LinkResult* out, std::string* err);
|
||||
const MachineConfig& cfg, LinkResult* out, std::string* err);
|
||||
|
||||
/**
|
||||
* @brief 读取并解析一个 .st 文件成 SourceUnit。
|
||||
|
||||
@@ -25,14 +25,14 @@ namespace compiler {
|
||||
* FbBuiltin=内建 FB 类型(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG);
|
||||
* FbUser=用户 FB 类型(存在性由 12.6 链接层校验)。
|
||||
*/
|
||||
enum class TypeKind { Bool, Int, Time, FbBuiltin, FbUser };
|
||||
enum class TypeKind { Scalar, FbBuiltin, FbUser };
|
||||
|
||||
/**
|
||||
* @brief 类型引用:种类 + (用户 FB 的)类型名。
|
||||
*/
|
||||
struct TypeRef {
|
||||
TypeKind kind = TypeKind::Bool; ///< 类型种类
|
||||
std::string name; ///< FbUser 时为用户 FB 类型名(小写)
|
||||
TypeKind kind = TypeKind::Scalar; ///< 类型种类(Scalar=标量 19 型 / FbBuiltin / FbUser)
|
||||
std::string name; ///< 类型名(小写:标量 = machine.toml type 键;FB = fb 表名或用户声明名)
|
||||
};
|
||||
|
||||
// ---- 变量段 ----
|
||||
@@ -75,8 +75,12 @@ namespace compiler {
|
||||
*/
|
||||
enum class ExprKind {
|
||||
LitBool, ///< 布尔字面量(int_value 0/1)
|
||||
LitInt, ///< 整数字面量(int_value)
|
||||
LitInt, ///< 整数字面量(int_value,含进制已换算)
|
||||
LitReal, ///< 浮点字面量(double_value)
|
||||
LitTime, ///< 时间字面量(int_value 毫秒)
|
||||
LitDate, ///< 日期字面量(int_value 天数,1970 纪元)
|
||||
LitTod, ///< 时刻字面量(int_value 当日毫秒)
|
||||
LitDt, ///< 日期时刻字面量(int64 = date<<32|tod)
|
||||
VarRef, ///< 变量引用(name)
|
||||
Field, ///< FB 字段访问(name.field)
|
||||
Not, ///< 逻辑非(operand)
|
||||
@@ -104,7 +108,8 @@ namespace compiler {
|
||||
BinOp op = BinOp::Eq; ///< 二目运算(Cmp/Add/Sub/Mul/Div 用)
|
||||
std::string name; ///< VarRef / Field 对象 / Call 函数名
|
||||
std::string field; ///< Field 字段名
|
||||
int64_t int_value = 0; ///< 字面量值(BOOL 0/1、INT、TIME 毫秒)
|
||||
int64_t int_value = 0; ///< 字面量值(BOOL 0/1、INT、TIME ms、DATE 天、TOD ms、DT)
|
||||
double double_value = 0; ///< LitReal 字面量值
|
||||
std::unique_ptr<Expr> lhs; ///< 二目左操作数
|
||||
std::unique_ptr<Expr> rhs; ///< 二目右操作数
|
||||
std::unique_ptr<Expr> operand; ///< Not / Neg 的操作数
|
||||
|
||||
@@ -164,6 +164,10 @@ namespace compiler {
|
||||
uint32_t n_consts() const { return n_consts_; }
|
||||
/// 函数表行数(头 @48)。
|
||||
uint32_t n_funcs() const { return n_funcs_; }
|
||||
/// 常量表段偏移(头 @52)。
|
||||
uint32_t offset_const() const { return offs_of_const(); }
|
||||
/// 函数表段偏移(头 @56)。
|
||||
uint32_t offset_funcs() const { return static_cast<uint32_t>(get_le32_(56)); }
|
||||
/// 字节码段偏移(头 @60)。
|
||||
uint32_t offset_code() const { return offset_code_; }
|
||||
/// 槽表段偏移(头 @64)。
|
||||
@@ -207,6 +211,13 @@ namespace compiler {
|
||||
|
||||
/// 常量表偏移(from 已校验的段起点)。
|
||||
uint32_t offs_of_const() const;
|
||||
/// 读头内 u32(小端;内部辅助)。
|
||||
uint32_t get_le32_(size_t off) const {
|
||||
return static_cast<uint32_t>(buf_[off])
|
||||
| (static_cast<uint32_t>(buf_[off + 1]) << 8)
|
||||
| (static_cast<uint32_t>(buf_[off + 2]) << 16)
|
||||
| (static_cast<uint32_t>(buf_[off + 3]) << 24);
|
||||
}
|
||||
|
||||
const uint8_t* buf_; ///< 映像缓冲(不拥有)
|
||||
size_t len_; ///< 缓冲字节数
|
||||
|
||||
@@ -15,19 +15,23 @@
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/MachineConfig.h"
|
||||
#include "compiler/Parser.h"
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 表达式求值类型(v1 三型,无隐式宽化)。
|
||||
* @details 各成员含义:
|
||||
* - Bool:布尔量(逻辑运算 / 条件表达式)
|
||||
* - Int:整数(算术运算)
|
||||
* - Time:时间量(仅比较,无算术);INT 与 TIME 不混用
|
||||
* @brief 表达式求值类型(V2:19 标量 + 未定型)。
|
||||
* @details 无隐式转换(赋值/算术/比较严格同型);字面量未定型(Unresolved),
|
||||
* 由消费点(赋值目标/实参/条件/另一操作数)按上下文定型 + 值域检查;
|
||||
* BOOL 编译器强制 0/1。
|
||||
*/
|
||||
enum class TType { Bool, Int, Time };
|
||||
enum class TType {
|
||||
Bool, Byte, Word, Dword, Lword, Sint, Int, Dint, Lint,
|
||||
Usint, Uint, Udint, Ulint, Time, Real, Lreal, Date, Tod, Dt,
|
||||
Unresolved, ///< 字面量未定型(待消费点定型)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 对工程做类型检查(在链接成功后调用)。
|
||||
@@ -39,5 +43,5 @@ namespace compiler {
|
||||
* @return true 全部通过;false 失败(err 已写)
|
||||
*/
|
||||
bool check_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, std::string* err);
|
||||
const LinkResult& link, const MachineConfig& cfg, std::string* err);
|
||||
}
|
||||
|
||||
+58
-28
@@ -29,6 +29,7 @@
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -216,16 +217,6 @@ namespace {
|
||||
return t ? static_cast<uint8_t>(t->func) : 0;
|
||||
}
|
||||
|
||||
/// TypeKind 枚举 → 配置类型名(BOOL/INT/TIME;其他回退 INT)。
|
||||
static const char* type_kind_name(TypeKind k) {
|
||||
switch (k) {
|
||||
case TypeKind::Bool: return "BOOL";
|
||||
case TypeKind::Int: return "INT";
|
||||
case TypeKind::Time: return "TIME";
|
||||
default: return "INT";
|
||||
}
|
||||
}
|
||||
|
||||
const POU* find_pou(const std::string& name) const {
|
||||
for (const SourceUnit& u : units_) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
@@ -258,19 +249,16 @@ namespace {
|
||||
return r;
|
||||
}
|
||||
|
||||
/// 变量名 → 语言类型名(局部声明)。
|
||||
/// 变量名 → 语言类型名(局部声明;标量 = d.type.name 小写)。
|
||||
std::string type_name_of_var(const POU& pou, const std::string& name) const {
|
||||
for (const VarBlock& b : pou.blocks) {
|
||||
for (const VarDecl& d : b.vars) {
|
||||
if (d.name == name) {
|
||||
if (d.type.kind == TypeKind::Bool) return "BOOL";
|
||||
if (d.type.kind == TypeKind::Int) return "INT";
|
||||
if (d.type.kind == TypeKind::Time) return "TIME";
|
||||
return "INT";
|
||||
return d.type.kind == TypeKind::Scalar ? d.type.name : "int";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "INT";
|
||||
return "int";
|
||||
}
|
||||
|
||||
bool build_function(const POU& pou, FuncCtx* f) {
|
||||
@@ -496,11 +484,29 @@ namespace {
|
||||
|
||||
bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) {
|
||||
if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt ||
|
||||
e.kind == ExprKind::LitTime) {
|
||||
const char* type_name = e.kind == ExprKind::LitBool ? "BOOL"
|
||||
: e.kind == ExprKind::LitInt ? "INT"
|
||||
: "TIME";
|
||||
const size_t s = E_imm(f, "LOADK", rd, const_id(e.int_value));
|
||||
e.kind == ExprKind::LitTime || e.kind == ExprKind::LitReal ||
|
||||
e.kind == ExprKind::LitDate || e.kind == ExprKind::LitTod ||
|
||||
e.kind == ExprKind::LitDt) {
|
||||
const char* type_name = "INT";
|
||||
int64_t value = e.int_value;
|
||||
switch (e.kind) {
|
||||
case ExprKind::LitBool: type_name = "BOOL"; break;
|
||||
case ExprKind::LitInt: type_name = "INT"; break;
|
||||
case ExprKind::LitTime: type_name = "TIME"; break;
|
||||
case ExprKind::LitReal: {
|
||||
// TODO(V2): 浮点字面量按目标类型装载(REAL/LREAL 位模式不同),
|
||||
// 需 compile_expr 携带目标 func;当前按 REAL(float32)装载
|
||||
type_name = "REAL";
|
||||
float fv = static_cast<float>(e.double_value);
|
||||
std::memcpy(&value, &fv, sizeof fv);
|
||||
break;
|
||||
}
|
||||
case ExprKind::LitDate: type_name = "DATE"; break;
|
||||
case ExprKind::LitTod: type_name = "TOD"; break;
|
||||
case ExprKind::LitDt: type_name = "DT"; break;
|
||||
default: break;
|
||||
}
|
||||
const size_t s = E_imm(f, "LOADK", rd, const_id(value));
|
||||
set_func(f.code.data() + s, func_of_name(type_name));
|
||||
return true;
|
||||
}
|
||||
@@ -560,7 +566,7 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
const size_t s = E_rrr(f, arith_name(e.kind), rd, l, r);
|
||||
set_func(f.code.data() + s, func_of_name("INT"));
|
||||
set_func(f.code.data() + s, expr_func(f, *e.lhs, *e.rhs));
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::Cmp) {
|
||||
@@ -573,7 +579,7 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
const size_t s = E_rrr(f, cmp_name(e.op), rd, l, r);
|
||||
set_func(f.code.data() + s, func_of_name("INT"));
|
||||
set_func(f.code.data() + s, expr_func(f, *e.lhs, *e.rhs));
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::Not) {
|
||||
@@ -625,6 +631,32 @@ namespace {
|
||||
return fail("expression not supported");
|
||||
}
|
||||
|
||||
/// 表达式类型 func(算术/比较用):VarRef 取 reg_func/全局;
|
||||
/// 嵌套表达式左右递归;字面量按装载类型(LitInt→INT、LitReal→REAL 等)。
|
||||
uint8_t expr_func(const FuncCtx& f, const Expr& l, const Expr& r) const {
|
||||
if (l.kind == ExprKind::VarRef) {
|
||||
const auto tf = f.reg_func.find(l.name);
|
||||
if (tf != f.reg_func.end()) {
|
||||
return tf->second;
|
||||
}
|
||||
return func_of_global(l.name);
|
||||
}
|
||||
if (l.kind == ExprKind::LitBool || l.kind == ExprKind::LitInt ||
|
||||
l.kind == ExprKind::LitTime || l.kind == ExprKind::LitReal ||
|
||||
l.kind == ExprKind::LitDate || l.kind == ExprKind::LitTod ||
|
||||
l.kind == ExprKind::LitDt) {
|
||||
// 左是字面量 → 取右操作数(Typecheck 已定型同型)
|
||||
if (r.kind == ExprKind::VarRef) {
|
||||
const auto tf = f.reg_func.find(r.name);
|
||||
if (tf != f.reg_func.end()) {
|
||||
return tf->second;
|
||||
}
|
||||
return func_of_global(r.name);
|
||||
}
|
||||
}
|
||||
return func_of_name("INT");
|
||||
}
|
||||
|
||||
/// 全局变量 → func(查链接符号类型)。
|
||||
uint8_t func_of_global(const std::string& name) const {
|
||||
for (const Symbol& s : link_.globals) {
|
||||
@@ -726,10 +758,9 @@ namespace {
|
||||
inst.type_name = s.type_name;
|
||||
int32_t off = 0;
|
||||
for (const FbField& fd : lay->second.fields) {
|
||||
const char* tn = type_kind_name(fd.type);
|
||||
const TypeMeta tm = type_meta(cfg_, tn);
|
||||
const TypeMeta tm = type_meta(cfg_, fd.type_name);
|
||||
if (!tm) {
|
||||
return fail("no type for fb field '" + std::string(tn) + "'");
|
||||
return fail("no type for fb field '" + fd.type_name + "'");
|
||||
}
|
||||
off = static_cast<int32_t>(align_up(off, tm.width()));
|
||||
inst.field_off[fd.name] = off;
|
||||
@@ -739,8 +770,7 @@ namespace {
|
||||
// 实例块对齐到 max(字段对齐)
|
||||
uint32_t max_align = 1;
|
||||
for (const FbField& fd : lay->second.fields) {
|
||||
const char* tn = type_kind_name(fd.type);
|
||||
const TypeMeta tm = type_meta(cfg_, tn);
|
||||
const TypeMeta tm = type_meta(cfg_, fd.type_name);
|
||||
max_align = max_align > tm.width() ? max_align : tm.width();
|
||||
}
|
||||
cur_addr = align_up(cur_addr, max_align);
|
||||
|
||||
+250
-15
@@ -31,6 +31,7 @@
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
@@ -60,10 +61,26 @@ namespace {
|
||||
{"var_global", Tok::VAR_GLOBAL},
|
||||
{"var_external", Tok::VAR_EXTERNAL},
|
||||
{"end_var", Tok::END_VAR},
|
||||
// 类型
|
||||
// 类型(V2:19 种 IEC 类型)
|
||||
{"bool", Tok::BOOL},
|
||||
{"byte", Tok::BYTE},
|
||||
{"word", Tok::WORD},
|
||||
{"dword", Tok::DWORD},
|
||||
{"lword", Tok::LWORD},
|
||||
{"sint", Tok::SINT},
|
||||
{"int", Tok::INT},
|
||||
{"dint", Tok::DINT},
|
||||
{"lint", Tok::LINT},
|
||||
{"usint", Tok::USINT},
|
||||
{"uint", Tok::UINT},
|
||||
{"udint", Tok::UDINT},
|
||||
{"ulint", Tok::ULINT},
|
||||
{"time", Tok::TIME},
|
||||
{"real", Tok::REAL},
|
||||
{"lreal", Tok::LREAL},
|
||||
{"date", Tok::DATE},
|
||||
{"tod", Tok::TOD},
|
||||
{"dt", Tok::DT},
|
||||
// 控制
|
||||
{"if", Tok::IF},
|
||||
{"elsif", Tok::ELSIF},
|
||||
@@ -80,15 +97,7 @@ namespace {
|
||||
// 字面量
|
||||
{"true", Tok::TRUE},
|
||||
{"false", Tok::FALSE},
|
||||
// 内置 FB 类型名(12.11 扩充为 8 个)
|
||||
{"ton", Tok::TON},
|
||||
{"tof", Tok::TOF},
|
||||
{"tp", Tok::TP},
|
||||
{"ctu", Tok::CTU},
|
||||
{"ctd", Tok::CTD},
|
||||
{"ctud", Tok::CTUD},
|
||||
{"r_trig", Tok::R_TRIG},
|
||||
{"f_trig", Tok::F_TRIG},
|
||||
// FB 类型名不再设关键字(V2:IDENT + Typecheck 查 machine.toml [[fb]] 表)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -261,9 +270,25 @@ namespace {
|
||||
tok_col_ = col_;
|
||||
const char c = cur();
|
||||
|
||||
// TIME 字面量:T#(大小写不敏感)优先于标识符
|
||||
if ((c == 't' || c == 'T') && pos_ + 1 < src_.size() && src_[pos_ + 1] == '#') {
|
||||
return lex_time();
|
||||
// 日期/TIME 字面量前缀:D# / T# / TOD# / DT#(大小写不敏感,最长匹配)
|
||||
// 字母序列后跟 '#' 才进入;"ton"、"timer" 等普通词走标识符分支。
|
||||
if (std::isalpha(static_cast<unsigned char>(c))) {
|
||||
const uint32_t wstart = pos_;
|
||||
size_t j = pos_;
|
||||
while (j < src_.size() &&
|
||||
std::isalpha(static_cast<unsigned char>(src_[j]))) {
|
||||
++j;
|
||||
}
|
||||
if (j < src_.size() && src_[j] == '#') {
|
||||
std::string w = src_.substr(wstart, j - wstart);
|
||||
for (char& ch : w) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
if (w == "t") return lex_time(); // T#
|
||||
if (w == "d") return lex_date(); // D#
|
||||
if (w == "tod") return lex_tod(); // TOD#
|
||||
if (w == "dt") return lex_dt(); // DT#
|
||||
}
|
||||
}
|
||||
|
||||
// 标识符 / 关键字:统一折小写存入 text(内部形),
|
||||
@@ -288,14 +313,50 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 整数:十进制 [0-9]+,int64_t 存储;
|
||||
// 溢出检查在累加前做(v 上限 INT64_MAX)。
|
||||
// 数字:进制整数(2#/8#/16#)→ 浮点(. / e)→ 十进制整数
|
||||
if (std::isdigit(static_cast<unsigned char>(c))) {
|
||||
const uint32_t start = pos_;
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
advance();
|
||||
}
|
||||
// 进制前缀:<基数>#<数字>(2/8/16)
|
||||
if (!at_end() && cur() == '#') {
|
||||
const std::string base_str = src_.substr(start, pos_ - start);
|
||||
const uint32_t base = static_cast<uint32_t>(std::stoul(base_str));
|
||||
if (base != 2 && base != 8 && base != 16) {
|
||||
return fail("bad radix prefix");
|
||||
}
|
||||
return lex_base_int(start, base);
|
||||
}
|
||||
// 浮点:小数点或指数(.5 需数字前缀,由本分支覆盖)
|
||||
bool is_float = false;
|
||||
if (!at_end() && cur() == '.' && pos_ + 1 < src_.size() &&
|
||||
std::isdigit(static_cast<unsigned char>(src_[pos_ + 1]))) {
|
||||
is_float = true;
|
||||
advance(); // .
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
advance();
|
||||
}
|
||||
}
|
||||
if (!at_end() && (cur() == 'e' || cur() == 'E')) {
|
||||
is_float = true;
|
||||
advance();
|
||||
if (!at_end() && (cur() == '+' || cur() == '-')) {
|
||||
advance();
|
||||
}
|
||||
if (at_end() || !std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
return fail("bad float literal");
|
||||
}
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
advance();
|
||||
}
|
||||
}
|
||||
const std::string digits = src_.substr(start, pos_ - start);
|
||||
if (is_float) {
|
||||
push(Tok::FLOAT_LIT, digits, 0);
|
||||
out_->back().double_value = std::strtod(digits.c_str(), nullptr);
|
||||
return true;
|
||||
}
|
||||
uint64_t v = 0;
|
||||
for (const char ch : digits) {
|
||||
const uint64_t d = static_cast<uint64_t>(ch - '0');
|
||||
@@ -434,6 +495,180 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析进制整数字面量 <基数>#<数字>(2/8/16)。
|
||||
* @param start 起始下标(含基数数字)
|
||||
* @param base 进制(2/8/16,已校验)
|
||||
* @return true 成功(push INT_LIT,值已换算十进制);
|
||||
* false 位数非法 / 溢出(err 已写)
|
||||
*/
|
||||
bool lex_base_int(uint32_t start, uint32_t base) {
|
||||
advance(); // #
|
||||
uint64_t v = 0;
|
||||
bool any = false;
|
||||
while (!at_end()) {
|
||||
const char ch = cur();
|
||||
uint64_t d = 0;
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
d = static_cast<uint64_t>(ch - '0');
|
||||
} else if (ch >= 'a' && ch <= 'f') {
|
||||
d = static_cast<uint64_t>(ch - 'a' + 10);
|
||||
} else if (ch >= 'A' && ch <= 'F') {
|
||||
d = static_cast<uint64_t>(ch - 'A' + 10);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (d >= base) {
|
||||
return fail("bad base literal digit");
|
||||
}
|
||||
if (v > (static_cast<uint64_t>(INT64_MAX) - d) / base) {
|
||||
return fail("base literal too large");
|
||||
}
|
||||
v = v * base + d;
|
||||
any = true;
|
||||
advance();
|
||||
}
|
||||
if (!any) {
|
||||
return fail("bad base literal");
|
||||
}
|
||||
push(Tok::INT_LIT, src_.substr(start, pos_ - start), static_cast<int64_t>(v));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 公历 → 1970-01-01 起的天数(Hinnant 算法;1970-01-01 = 0)。
|
||||
static int64_t days_from_civil(int64_t y, unsigned m, unsigned d) {
|
||||
y -= m <= 2;
|
||||
const int64_t era = (y >= 0 ? y : y - 399) / 400;
|
||||
const unsigned yoe = static_cast<unsigned>(y - era * 400);
|
||||
const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
||||
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
||||
return era * 146097 + static_cast<int64_t>(doe) - 719468;
|
||||
}
|
||||
|
||||
/// 某年某月的天数(含闰年)。
|
||||
static unsigned days_in_month(int64_t y, unsigned m) {
|
||||
static const unsigned kDays[12] = {31, 28, 31, 30, 31, 30,
|
||||
31, 31, 30, 31, 30, 31};
|
||||
if (m == 2 && (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))) {
|
||||
return 29;
|
||||
}
|
||||
return kDays[m - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析日期段(y-m-d),换算为天数(1970 纪元)。
|
||||
* @param out_days 输出天数
|
||||
* @return true 成功;false 格式/值域非法
|
||||
*/
|
||||
bool parse_date_part(int64_t* out_days) {
|
||||
int64_t y = 0, m = 0, d = 0;
|
||||
if (!read_uint(&y)) return false;
|
||||
if (!expect_char('-')) return false;
|
||||
if (!read_uint(&m)) return false;
|
||||
if (!expect_char('-')) return false;
|
||||
if (!read_uint(&d)) return false;
|
||||
if (y < 1 || y > 9999) return false;
|
||||
if (m < 1 || m > 12) return false;
|
||||
if (d < 1 || d > days_in_month(y, static_cast<unsigned>(m))) return false;
|
||||
*out_days = days_from_civil(y, static_cast<unsigned>(m), static_cast<unsigned>(d));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 读一个无符号整数段(消费数字)。
|
||||
bool read_uint(int64_t* out) {
|
||||
if (at_end() || !std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
return false;
|
||||
}
|
||||
int64_t v = 0;
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
v = v * 10 + static_cast<int64_t>(cur() - '0');
|
||||
advance();
|
||||
}
|
||||
*out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 期望指定字符并消费;不匹配返回 false。
|
||||
bool expect_char(char want) {
|
||||
if (at_end() || cur() != want) {
|
||||
return false;
|
||||
}
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析 D#<年>-<月>-<日> 日期字面量。
|
||||
* @return true 成功(push DATE_LIT,int_value = 天数);
|
||||
* false 格式/值域非法(err 已写)
|
||||
*/
|
||||
bool lex_date() {
|
||||
const uint32_t start = pos_;
|
||||
advance(); // D
|
||||
advance(); // #
|
||||
int64_t days = 0;
|
||||
if (!parse_date_part(&days)) {
|
||||
return fail("bad date literal");
|
||||
}
|
||||
push(Tok::DATE_LIT, src_.substr(start, pos_ - start), days);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析 TOD#<时>:<分>:<秒> 时刻字面量(当日毫秒,无小数秒)。
|
||||
* @return true 成功(push TOD_LIT,int_value = 当日毫秒);
|
||||
* false 格式/值域非法(err 已写)
|
||||
*/
|
||||
bool lex_tod() {
|
||||
const uint32_t start = pos_;
|
||||
advance(); // T
|
||||
advance(); // O
|
||||
advance(); // D
|
||||
advance(); // #
|
||||
int64_t h = 0, m = 0, s = 0;
|
||||
if (!read_uint(&h)) return fail("bad tod literal");
|
||||
if (!expect_char(':')) return fail("bad tod literal");
|
||||
if (!read_uint(&m)) return fail("bad tod literal");
|
||||
if (!expect_char(':')) return fail("bad tod literal");
|
||||
if (!read_uint(&s)) return fail("bad tod literal");
|
||||
if (h > 23 || m > 59 || s > 59) {
|
||||
return fail("bad tod literal");
|
||||
}
|
||||
const int64_t ms = h * 3600000 + m * 60000 + s * 1000;
|
||||
push(Tok::TOD_LIT, src_.substr(start, pos_ - start), ms);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析 DT#<年>-<月>-<日>-<时>:<分>:<秒> 日期时刻字面量。
|
||||
* @return true 成功(push DT_LIT,int64 = date<<32 | tod);
|
||||
* false 格式/值域非法(err 已写)
|
||||
*/
|
||||
bool lex_dt() {
|
||||
const uint32_t start = pos_;
|
||||
advance(); // D
|
||||
advance(); // T
|
||||
advance(); // #
|
||||
int64_t days = 0;
|
||||
if (!parse_date_part(&days)) {
|
||||
return fail("bad dt literal");
|
||||
}
|
||||
if (!expect_char('-')) return fail("bad dt literal");
|
||||
int64_t h = 0, m = 0, s = 0;
|
||||
if (!read_uint(&h)) return fail("bad dt literal");
|
||||
if (!expect_char(':')) return fail("bad dt literal");
|
||||
if (!read_uint(&m)) return fail("bad dt literal");
|
||||
if (!expect_char(':')) return fail("bad dt literal");
|
||||
if (!read_uint(&s)) return fail("bad dt literal");
|
||||
if (h > 23 || m > 59 || s > 59) {
|
||||
return fail("bad dt literal");
|
||||
}
|
||||
const uint64_t tod = static_cast<uint64_t>(h * 3600000 + m * 60000 + s * 1000);
|
||||
const int64_t dt = (static_cast<uint64_t>(days) << 32) | tod;
|
||||
push(Tok::DT_LIT, src_.substr(start, pos_ - start), dt);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 状态 ----
|
||||
const std::string& src_; ///< 源文本(外部所有,不拷贝)
|
||||
const std::string& sf_; ///< 源文件名(报错用)
|
||||
|
||||
+52
-58
@@ -77,60 +77,47 @@ namespace {
|
||||
* @return "bool" / "int" / "time" / FB 类型名(小写);未知返回 "?"
|
||||
*/
|
||||
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 t.name; // V2:所有种类都存小写类型名(标量 = type 表键 / FB 名)
|
||||
}
|
||||
|
||||
/// 小写化(内建 FB 名 → fb 表名映射用)。
|
||||
std::string lower_of(const std::string& s) {
|
||||
std::string out = s;
|
||||
for (char& ch : out) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
return "?";
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 是否为标量类型(BOOL / INT / TIME)
|
||||
* @param k 类型种类
|
||||
* @return true 标量(FB 字段只允许标量,见 collect_fb_types)
|
||||
*/
|
||||
bool is_scalar(TypeKind k) {
|
||||
return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time;
|
||||
}
|
||||
|
||||
/// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.11 扩为 8 个)
|
||||
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 kTpLayout{"tp", {{"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 kCtdLayout{"ctd", {{"cd", TypeKind::Bool}, {"ld", TypeKind::Bool},
|
||||
{"pv", TypeKind::Int}, {"q", TypeKind::Bool},
|
||||
{"cv", TypeKind::Int}}};
|
||||
const FbLayout kCtudLayout{"ctud", {{"cu", TypeKind::Bool}, {"cd", TypeKind::Bool},
|
||||
{"r", TypeKind::Bool}, {"lu", TypeKind::Bool},
|
||||
{"pv", TypeKind::Int}, {"qu", TypeKind::Bool},
|
||||
{"qd", TypeKind::Bool}, {"cv", TypeKind::Int}}};
|
||||
const FbLayout kRTrigLayout{"r_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}};
|
||||
const FbLayout kFTrigLayout{"f_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}};
|
||||
|
||||
/**
|
||||
* @brief 内建 FB 名 → 冻结布局
|
||||
* @brief 内建 FB 名 → 布局(查 machine.toml [[fb]] 表,V2 配置驱动)。
|
||||
* @param name FB 类型名(小写)
|
||||
* @return 布局指针(ton/tof/tp/ctu/ctd/ctud/r_trig/f_trig);未知返回 nullptr
|
||||
* @return 布局指针(缓存);未知返回 nullptr
|
||||
*/
|
||||
const FbLayout* builtin_layout(const std::string& name) {
|
||||
if (name == "ton") return &kTonLayout;
|
||||
if (name == "tof") return &kTofLayout;
|
||||
if (name == "tp") return &kTpLayout;
|
||||
if (name == "ctu") return &kCtuLayout;
|
||||
if (name == "ctd") return &kCtdLayout;
|
||||
if (name == "ctud") return &kCtudLayout;
|
||||
if (name == "r_trig") return &kRTrigLayout;
|
||||
if (name == "f_trig") return &kFTrigLayout;
|
||||
return nullptr;
|
||||
const FbLayout* builtin_layout(const MachineConfig& cfg, const std::string& name) {
|
||||
static std::map<std::string, FbLayout> cache;
|
||||
auto it = cache.find(name);
|
||||
if (it != cache.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
std::string up = name;
|
||||
for (char& ch : up) {
|
||||
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
const ConfigFb* fb = cfg.find_fb(up);
|
||||
if (fb == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
FbLayout lay;
|
||||
lay.type_name = name;
|
||||
for (const ConfigFbField& f : fb->fields) {
|
||||
FbField field;
|
||||
field.name = f.name;
|
||||
field.type_name = lower_of(f.type);
|
||||
lay.fields.push_back(field);
|
||||
}
|
||||
auto r = cache.emplace(name, std::move(lay));
|
||||
return &r.first->second;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,8 +135,8 @@ namespace {
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
*/
|
||||
Linker(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
LinkResult* out, std::string* err)
|
||||
: proj_(proj), units_(units), out_(out), err_(err) {
|
||||
const MachineConfig& cfg, LinkResult* out, std::string* err)
|
||||
: proj_(proj), units_(units), cfg_(cfg), out_(out), err_(err) {
|
||||
// gvl 路径规范化一次,供 is_gvl_file 比较(容忍相对/绝对写法差异)
|
||||
gvl_path_ = std::filesystem::weakly_canonical(
|
||||
std::filesystem::path(proj.base_dir) / proj.gvl_file);
|
||||
@@ -285,15 +272,16 @@ namespace {
|
||||
continue;
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
if (!is_scalar(d.type.kind)) {
|
||||
// FB 字段必须是标量类型(FB 实例字段不允许)
|
||||
if (d.type.kind != TypeKind::Scalar) {
|
||||
return fail(u.path,
|
||||
"FB field type must be BOOL/INT/TIME ('" +
|
||||
d.name + "')",
|
||||
"FB field type must be scalar ('" + d.name +
|
||||
"')",
|
||||
d.line, d.col);
|
||||
}
|
||||
FbField f;
|
||||
f.name = d.name;
|
||||
f.type = d.type.kind;
|
||||
f.type_name = d.type.name; // 标量类型名(小写)
|
||||
lay.fields.push_back(f);
|
||||
}
|
||||
}
|
||||
@@ -399,8 +387,13 @@ namespace {
|
||||
sc.syms.push_back(s);
|
||||
if (is_fb) {
|
||||
FbLayout lay;
|
||||
if (d.type.kind == TypeKind::FbBuiltin) {
|
||||
const FbLayout* bl = builtin_layout(d.type.name);
|
||||
// 内建判定:查 machine.toml [[fb]] 表(V2 配置驱动)
|
||||
std::string up = d.type.name;
|
||||
for (char& ch : up) {
|
||||
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
if (cfg_.find_fb(up) != nullptr) {
|
||||
const FbLayout* bl = builtin_layout(cfg_, d.type.name);
|
||||
if (!bl) {
|
||||
return fail(file, "unknown builtin FB '" + d.type.name + "'",
|
||||
d.line, d.col);
|
||||
@@ -693,6 +686,7 @@ namespace {
|
||||
// ---- 成员 ----
|
||||
const Project& proj_; ///< 工程定义(toml)
|
||||
const std::vector<SourceUnit>& units_; ///< 全部源文件 AST
|
||||
const MachineConfig& cfg_; ///< 机器定义(内建 FB 布局来源)
|
||||
LinkResult* out_; ///< 链接结果
|
||||
std::string* err_; ///< 错误输出(可空)
|
||||
std::filesystem::path gvl_path_; ///< 规范化后的 gvl 路径
|
||||
@@ -712,13 +706,13 @@ namespace {
|
||||
* @return true 成功;false 失败(err 前缀 "link error")
|
||||
*/
|
||||
bool link_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
LinkResult* out, std::string* err) {
|
||||
const MachineConfig& cfg, 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);
|
||||
Linker l(proj, units, cfg, out, err);
|
||||
return l.run();
|
||||
}
|
||||
|
||||
|
||||
+64
-55
@@ -73,6 +73,36 @@ namespace {
|
||||
* @param word 小写单词
|
||||
* @return true 命中(var_in_out / ref / class / any / pointer / interface / method)
|
||||
*/
|
||||
/**
|
||||
* @brief 类型关键字 → 小写类型名(machine.toml type 表键)。
|
||||
* @param tk 类型 Tok(BOOL..DT)
|
||||
* @return 小写名(如 "dint");非类型 Tok 返回空串
|
||||
*/
|
||||
std::string tok_name(Tok tk) {
|
||||
switch (tk) {
|
||||
case Tok::BOOL: return "bool";
|
||||
case Tok::BYTE: return "byte";
|
||||
case Tok::WORD: return "word";
|
||||
case Tok::DWORD: return "dword";
|
||||
case Tok::LWORD: return "lword";
|
||||
case Tok::SINT: return "sint";
|
||||
case Tok::INT: return "int";
|
||||
case Tok::DINT: return "dint";
|
||||
case Tok::LINT: return "lint";
|
||||
case Tok::USINT: return "usint";
|
||||
case Tok::UINT: return "uint";
|
||||
case Tok::UDINT: return "udint";
|
||||
case Tok::ULINT: return "ulint";
|
||||
case Tok::TIME: return "time";
|
||||
case Tok::REAL: return "real";
|
||||
case Tok::LREAL: return "lreal";
|
||||
case Tok::DATE: return "date";
|
||||
case Tok::TOD: return "tod";
|
||||
case Tok::DT: return "dt";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool is_forbidden(const std::string& word) {
|
||||
for (const char* f : kForbidden) {
|
||||
if (word == f) {
|
||||
@@ -425,70 +455,49 @@ namespace {
|
||||
e->int_value = cur().int_value;
|
||||
advance();
|
||||
return true;
|
||||
case Tok::FLOAT_LIT:
|
||||
e->kind = ExprKind::LitReal;
|
||||
e->double_value = cur().double_value;
|
||||
advance();
|
||||
return true;
|
||||
case Tok::DATE_LIT:
|
||||
e->kind = ExprKind::LitDate;
|
||||
e->int_value = cur().int_value;
|
||||
advance();
|
||||
return true;
|
||||
case Tok::TOD_LIT:
|
||||
e->kind = ExprKind::LitTod;
|
||||
e->int_value = cur().int_value;
|
||||
advance();
|
||||
return true;
|
||||
case Tok::DT_LIT:
|
||||
e->kind = ExprKind::LitDt;
|
||||
e->int_value = cur().int_value;
|
||||
advance();
|
||||
return true;
|
||||
default:
|
||||
return fail("expected literal initializer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析类型名
|
||||
* @details 内建标量 BOOL/INT/TIME、内建 FB TON/TOF/CTU、
|
||||
* 或标识符(用户 FB 类型,存在性由 12.6 链接层校验)
|
||||
* @brief 解析类型名(V2)
|
||||
* @details 19 种 IEC 类型关键字(type_name 小写字符串)、
|
||||
* 或标识符(FB 类型名——内建查 machine.toml、用户查符号表,Typecheck 判定)
|
||||
* @param t 输出 TypeRef
|
||||
* @return true 成功;false 语法错误(err 已写)
|
||||
*/
|
||||
bool parse_type(TypeRef* t) {
|
||||
switch (type()) {
|
||||
case Tok::BOOL:
|
||||
t->kind = TypeKind::Bool;
|
||||
advance();
|
||||
return true;
|
||||
case Tok::INT:
|
||||
t->kind = TypeKind::Int;
|
||||
advance();
|
||||
return true;
|
||||
case Tok::TIME:
|
||||
t->kind = TypeKind::Time;
|
||||
advance();
|
||||
return true;
|
||||
case Tok::TON:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "ton";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::TOF:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "tof";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::TP:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "tp";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::CTU:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "ctu";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::CTD:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "ctd";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::CTUD:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "ctud";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::R_TRIG:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "r_trig";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::F_TRIG:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "f_trig";
|
||||
// 类型关键字(小写名 = machine.toml type 表键)
|
||||
const Tok tk = type();
|
||||
switch (tk) {
|
||||
case Tok::BOOL: case Tok::BYTE: case Tok::WORD: case Tok::DWORD:
|
||||
case Tok::LWORD: case Tok::SINT: case Tok::INT: case Tok::DINT:
|
||||
case Tok::LINT: case Tok::USINT: case Tok::UINT: case Tok::UDINT:
|
||||
case Tok::ULINT: case Tok::TIME: case Tok::REAL: case Tok::LREAL:
|
||||
case Tok::DATE: case Tok::TOD: case Tok::DT:
|
||||
t->kind = TypeKind::Scalar;
|
||||
t->name = tok_name(tk);
|
||||
advance();
|
||||
return true;
|
||||
case Tok::IDENT: {
|
||||
@@ -496,7 +505,7 @@ namespace {
|
||||
if (is_forbidden(word)) {
|
||||
return fail("'" + word + "' is not supported");
|
||||
}
|
||||
t->kind = TypeKind::FbUser;
|
||||
t->kind = TypeKind::FbUser; // 内建或用户 FB(Linker/Typecheck 查表判定)
|
||||
t->name = word;
|
||||
advance();
|
||||
return true;
|
||||
|
||||
+283
-115
@@ -1,86 +1,130 @@
|
||||
/**
|
||||
* @file Typecheck.cpp
|
||||
* @brief 类型检查
|
||||
* @brief 类型检查(V2)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/compiler/类型检查.md):
|
||||
* @details 设计说明(详见 Doc/compiler/类型检查.md 与阶段3-语言层V2.md):
|
||||
* - 在链接成功后运行:符号/字段/函数存在性已由 12.6 保证,本阶段只查类型
|
||||
* - 表达式求值类型三型:BOOL / INT / TIME;无隐式宽化,INT 与 TIME 不混用
|
||||
* - 语句规则:赋值类型相等、FUNCTION 禁写全局(含 EXTERNAL,用例 14)、
|
||||
* FB 命名实参匹配字段类型、IF/WHILE 条件必须 BOOL
|
||||
* - 错误:稳定前缀 "type error" + 文件(Expr/Stmt 无行列信息,只带文件)
|
||||
* - 19 种 IEC 标量类型 + Unresolved(字面量未定型);**禁隐式转换**
|
||||
* (赋值/算术/比较严格同型)
|
||||
* - 字面量机制:整数字面量 / 浮点字面量未定型(Unresolved),由消费点
|
||||
* (赋值目标/实参/条件/另一操作数)按上下文定型 + **值域检查**(冲突报错);
|
||||
* TRUE/FALSE(BOOL)、T#(TIME)、D#/TOD#/DT#(日期)字面量类型固定
|
||||
* - BOOL 编译器强制 0/1;IF/WHILE 条件必须 BOOL
|
||||
* - 错误:稳定前缀 "type error" + 文件
|
||||
*
|
||||
* 函数清单:
|
||||
* - tname TType → 文本名(BOOL/INT/TIME)
|
||||
* - to_type(TypeKind) TypeKind → TType(FB 类型返回 false)
|
||||
* - to_type(string) 符号 type_name 字符串 → TType
|
||||
* - Checker::Checker (构造)存工程/源文件/链接结果/错误输出
|
||||
* - Checker::run 逐 POU 逐语句检查
|
||||
* - Checker::fail 组装 "type error: <msg> (file)" 返回 false
|
||||
* - find_scope 按 POU 名取作用域
|
||||
* - find_sym 在作用域里按名查符号
|
||||
* - find_global 按名查全局符号
|
||||
* - find_pou 按名查 POU AST(函数返回类型用)
|
||||
* - check_stmt 语句规则分发(赋值/FB 调用/IF/WHILE)
|
||||
* - assign_target_type 解析赋值左值类型(局部/全局/函数名),FUNCTION 禁写全局
|
||||
* - check_expr 表达式定类型(递归)
|
||||
* - check_project 对外入口:逐 POU 检查
|
||||
* - tname / tname_lower TType ↔ 文本名(19 + Unresolved)
|
||||
* - to_type(string) 符号 type_name 字符串 → TType
|
||||
* - Checker::Checker (构造)存工程/源文件/链接结果/机器配置/错误输出
|
||||
* - Checker::run 逐 POU 逐语句检查
|
||||
* - fail 组装 "type error: <msg> (file)" 返回 false
|
||||
* - resolve_to 表达式定型:未定型 → 值域检查后按目标定型;
|
||||
* 已定型 → 必须与目标相同(禁隐式转换)
|
||||
* - literal_range_ok 字面量按目标类型的值域检查(宽度/符号/BOOL 0/1)
|
||||
* - arith_allowed 目标类型是否允许算术(int/uint/float 四则;
|
||||
* TIME 仅 Add/Sub;其他拒绝)
|
||||
* - check_stmt 语句规则分发(赋值/FB 调用/IF/WHILE)
|
||||
* - assign_target_type 解析赋值左值类型(局部/全局/函数名)
|
||||
* - check_cond 条件必须 BOOL(未定型按 BOOL 定型)
|
||||
* - check_expr 表达式定类型(递归)
|
||||
* - check_project 对外入口:逐 POU 检查
|
||||
*/
|
||||
|
||||
#include "compiler/Typecheck.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/TypeInfo.h"
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief TType → 文本名(错误报文用)
|
||||
* @brief TType → 文本名(错误报文用,大写)。
|
||||
* @param t 求值类型
|
||||
* @return "BOOL" / "INT" / "TIME"
|
||||
* @return 如 "DINT";Unresolved 返回 "?";未知返回 "?"
|
||||
*/
|
||||
const char* tname(TType t) {
|
||||
switch (t) {
|
||||
case TType::Bool: return "BOOL";
|
||||
case TType::Byte: return "BYTE";
|
||||
case TType::Word: return "WORD";
|
||||
case TType::Dword: return "DWORD";
|
||||
case TType::Lword: return "LWORD";
|
||||
case TType::Sint: return "SINT";
|
||||
case TType::Int: return "INT";
|
||||
case TType::Dint: return "DINT";
|
||||
case TType::Lint: return "LINT";
|
||||
case TType::Usint: return "USINT";
|
||||
case TType::Uint: return "UINT";
|
||||
case TType::Udint: return "UDINT";
|
||||
case TType::Ulint: return "ULINT";
|
||||
case TType::Time: return "TIME";
|
||||
case TType::Real: return "REAL";
|
||||
case TType::Lreal: return "LREAL";
|
||||
case TType::Date: return "DATE";
|
||||
case TType::Tod: return "TOD";
|
||||
case TType::Dt: return "DT";
|
||||
case TType::Unresolved: return "?";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TypeKind → TType
|
||||
* @param k 类型种类
|
||||
* @param out 输出求值类型
|
||||
* @return true 标量(BOOL/INT/TIME);FB 类型返回 false
|
||||
* @brief TType → 小写类型名(machine.toml type 表键)。
|
||||
* @param t 求值类型
|
||||
* @return 如 "dint";Unresolved/未知返回空串
|
||||
*/
|
||||
bool to_type(TypeKind k, TType* out) {
|
||||
switch (k) {
|
||||
case TypeKind::Bool: *out = TType::Bool; return true;
|
||||
case TypeKind::Int: *out = TType::Int; return true;
|
||||
case TypeKind::Time: *out = TType::Time; return true;
|
||||
default: return false;
|
||||
const char* tname_lower(TType t) {
|
||||
switch (t) {
|
||||
case TType::Bool: return "bool";
|
||||
case TType::Byte: return "byte";
|
||||
case TType::Word: return "word";
|
||||
case TType::Dword: return "dword";
|
||||
case TType::Lword: return "lword";
|
||||
case TType::Sint: return "sint";
|
||||
case TType::Int: return "int";
|
||||
case TType::Dint: return "dint";
|
||||
case TType::Lint: return "lint";
|
||||
case TType::Usint: return "usint";
|
||||
case TType::Uint: return "uint";
|
||||
case TType::Udint: return "udint";
|
||||
case TType::Ulint: return "ulint";
|
||||
case TType::Time: return "time";
|
||||
case TType::Real: return "real";
|
||||
case TType::Lreal: return "lreal";
|
||||
case TType::Date: return "date";
|
||||
case TType::Tod: return "tod";
|
||||
case TType::Dt: return "dt";
|
||||
case TType::Unresolved: return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 符号 type_name 字符串 → TType
|
||||
* @param name "bool" / "int" / "time"(小写)
|
||||
* @brief 符号 type_name 字符串 → TType(19 标量)。
|
||||
* @param name 小写类型名("bool".."dt")
|
||||
* @param out 输出求值类型
|
||||
* @return true 标量;FB 类型名返回 false
|
||||
* @return true 标量;未知(FB 类型名等)返回 false
|
||||
*/
|
||||
bool to_type(const std::string& name, TType* out) {
|
||||
if (name == "bool") { *out = TType::Bool; return true; }
|
||||
if (name == "int") { *out = TType::Int; return true; }
|
||||
if (name == "time") { *out = TType::Time; return true; }
|
||||
for (int i = 0; i < 19; ++i) {
|
||||
const TType t = static_cast<TType>(i);
|
||||
if (name == tname_lower(t)) {
|
||||
*out = t;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 类型检查器
|
||||
* @brief 类型检查器(V2)
|
||||
*
|
||||
* @details 链接结果只读;不修改 AST 与符号表。
|
||||
*/
|
||||
@@ -88,14 +132,15 @@ namespace {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造检查器
|
||||
* @param proj 工程定义(未用,保留接口对称;io 类型映射留待 12.8)
|
||||
* @param proj 工程定义(未用,保留接口对称)
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(符号/布局已解析)
|
||||
* @param cfg 机器定义(类型元数据/值域来源)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
*/
|
||||
Checker(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, std::string* err)
|
||||
: proj_(proj), units_(units), link_(link), err_(err) {}
|
||||
const LinkResult& link, const MachineConfig& cfg, std::string* err)
|
||||
: proj_(proj), units_(units), link_(link), cfg_(cfg), err_(err) {}
|
||||
|
||||
/**
|
||||
* @brief 逐 POU 逐语句做类型检查
|
||||
@@ -121,7 +166,6 @@ namespace {
|
||||
private:
|
||||
/**
|
||||
* @brief 组装 "type error: <msg> (file)" 写入 err
|
||||
* @details Expr/Stmt 不携带行列,只报文件;err_ 为 nullptr 时静默
|
||||
* @param file 出错文件
|
||||
* @param msg 错误描述(不含前缀)
|
||||
* @return 恒 false(便于 return fail(...) 一行退出)
|
||||
@@ -133,11 +177,6 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 POU 名取作用域
|
||||
* @param name POU 名(小写)
|
||||
* @return 作用域指针;未找到返回 nullptr
|
||||
*/
|
||||
const LinkResult::PouScope* find_scope(const std::string& name) const {
|
||||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||||
if (sc.name == name) {
|
||||
@@ -147,12 +186,6 @@ namespace {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 在作用域里按名查符号
|
||||
* @param sc 作用域
|
||||
* @param name 符号名(小写)
|
||||
* @return 符号指针;未找到返回 nullptr
|
||||
*/
|
||||
const Symbol* find_sym(const LinkResult::PouScope& sc,
|
||||
const std::string& name) const {
|
||||
for (const Symbol& s : sc.syms) {
|
||||
@@ -163,11 +196,6 @@ namespace {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名查全局符号
|
||||
* @param name 全局名(小写)
|
||||
* @return 符号指针;未找到返回 nullptr
|
||||
*/
|
||||
const Symbol* find_global(const std::string& name) const {
|
||||
const auto it = link_.global_index.find(name);
|
||||
if (it == link_.global_index.end()) {
|
||||
@@ -176,11 +204,6 @@ namespace {
|
||||
return &link_.globals[it->second];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名查 POU AST(函数返回类型用)
|
||||
* @param name POU 名(小写)
|
||||
* @return POU 指针;未找到返回 nullptr
|
||||
*/
|
||||
const POU* find_pou(const std::string& name) const {
|
||||
for (const SourceUnit& u : units_) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
@@ -192,6 +215,116 @@ namespace {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 表达式定型到目标类型(禁隐式转换 + 字面量值域检查)。
|
||||
* @param file 出错文件
|
||||
* @param e 表达式(已 check_expr,got 为其求值类型)
|
||||
* @param got 表达式当前类型(Unresolved = 字面量未定型)
|
||||
* @param want 目标类型(消费点)
|
||||
* @return true 定型成功;false(类型不匹配 / 字面量越域)
|
||||
*/
|
||||
bool resolve_to(const std::string& file, const Expr& e, TType got, TType want) {
|
||||
if (got == want) {
|
||||
return true;
|
||||
}
|
||||
if (got != TType::Unresolved) {
|
||||
return fail(file, "type mismatch: " + std::string(tname(got)) +
|
||||
" vs " + std::string(tname(want)));
|
||||
}
|
||||
// 未定型字面量 → 按目标类型值域检查后定型
|
||||
return literal_range_ok(file, e, want);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 字面量按目标类型的值域检查。
|
||||
* @details LitInt:整型目标按宽度/符号检查(BOOL 0/1、无符号负数拒绝、
|
||||
* 溢出拒绝);REAL/LREAL 目标合法。LitReal:仅 REAL/LREAL 目标
|
||||
* (REAL 检查 ±3.4e38);整数目标拒绝。
|
||||
* @return true 值域内;false(err 已写)
|
||||
*/
|
||||
bool literal_range_ok(const std::string& file, const Expr& e, TType want) {
|
||||
if (e.kind == ExprKind::LitInt) {
|
||||
if (want == TType::Real || want == TType::Lreal) {
|
||||
return true; // 整数字面量 → 浮点目标合法
|
||||
}
|
||||
const TypeMeta tm = type_meta(cfg_, tname_lower(want));
|
||||
if (!tm) {
|
||||
return fail(file, "unknown target type for literal");
|
||||
}
|
||||
if (tm.kind() == "bool") {
|
||||
if (e.int_value != 0 && e.int_value != 1) {
|
||||
return fail(file, "BOOL literal must be 0 or 1");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!tm.is_signed() && e.int_value < 0) {
|
||||
return fail(file, "negative literal for unsigned type " +
|
||||
std::string(tname(want)));
|
||||
}
|
||||
const int w = static_cast<int>(tm.width());
|
||||
if (tm.is_signed()) {
|
||||
const int64_t minv =
|
||||
(w == 8) ? INT64_MIN : -(static_cast<int64_t>(1) << (8 * w - 1));
|
||||
const int64_t maxv =
|
||||
(w == 8) ? INT64_MAX : (static_cast<int64_t>(1) << (8 * w - 1)) - 1;
|
||||
if (e.int_value < minv || e.int_value > maxv) {
|
||||
return fail(file, "integer literal out of range for " +
|
||||
std::string(tname(want)));
|
||||
}
|
||||
} else {
|
||||
const uint64_t maxv =
|
||||
(w == 8) ? UINT64_MAX : ((static_cast<uint64_t>(1) << (8 * w)) - 1);
|
||||
if (static_cast<uint64_t>(e.int_value) > maxv) {
|
||||
return fail(file, "integer literal out of range for " +
|
||||
std::string(tname(want)));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::LitReal) {
|
||||
if (want == TType::Real) {
|
||||
if (std::fabs(e.double_value) > 3.40282347e38) {
|
||||
return fail(file, "float literal out of range for REAL");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (want == TType::Lreal) {
|
||||
return true;
|
||||
}
|
||||
return fail(file, "float literal cannot be assigned to " +
|
||||
std::string(tname(want)));
|
||||
}
|
||||
return true; // 其他 Unresolved(不应发生)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 目标类型是否允许四则算术。
|
||||
* @param file 出错文件
|
||||
* @param t 目标类型(已定型)
|
||||
* @param is_add_sub 是否 Add/Sub(TIME 允许 ±,不允许 ×/÷)
|
||||
* @return true 允许;false(err 已写)
|
||||
*/
|
||||
bool arith_allowed(const std::string& file, TType t, bool is_add_sub) {
|
||||
if (t == TType::Unresolved) {
|
||||
return true; // 两侧都是字面量,向上传播
|
||||
}
|
||||
const TypeMeta tm = type_meta(cfg_, tname_lower(t));
|
||||
if (!tm) {
|
||||
return fail(file, "arithmetic on non-scalar type");
|
||||
}
|
||||
const std::string& kind = tm.kind();
|
||||
if (kind == "int" || kind == "uint" || kind == "float") {
|
||||
return true;
|
||||
}
|
||||
if (kind == "time") {
|
||||
if (is_add_sub) {
|
||||
return true;
|
||||
}
|
||||
return fail(file, "TIME has no multiply/divide");
|
||||
}
|
||||
return fail(file, std::string(tname(t)) + " has no arithmetic");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 语句规则分发
|
||||
* @param file 出错文件
|
||||
@@ -212,9 +345,8 @@ namespace {
|
||||
if (!check_expr(file, sc, *st.value, &rhs)) {
|
||||
return false;
|
||||
}
|
||||
if (lhs != rhs) {
|
||||
return fail(file, "type mismatch in assignment to '" + st.target +
|
||||
"' (" + tname(lhs) + " vs " + tname(rhs) + ")");
|
||||
if (!resolve_to(file, *st.value, rhs, lhs)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -228,7 +360,7 @@ namespace {
|
||||
bool found = false;
|
||||
for (const FbField& f : it->second.fields) {
|
||||
if (f.name == a.name) {
|
||||
found = to_type(f.type, &want);
|
||||
found = to_type(f.type_name, &want);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -240,9 +372,8 @@ namespace {
|
||||
if (!check_expr(file, sc, *a.value, &got)) {
|
||||
return false;
|
||||
}
|
||||
if (got != want) {
|
||||
return fail(file, "FB input '" + a.name + "' expects " +
|
||||
tname(want));
|
||||
if (!resolve_to(file, *a.value, got, want)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -277,11 +408,7 @@ namespace {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 条件表达式必须为 BOOL(IF / WHILE 共用)
|
||||
* @param file 出错文件
|
||||
* @param sc 当前作用域
|
||||
* @param e 条件表达式
|
||||
* @return true 合法;false(err 已写)
|
||||
* @brief 条件表达式必须为 BOOL(IF / WHILE 共用;未定型按 BOOL 定型)。
|
||||
*/
|
||||
bool check_cond(const std::string& file, const LinkResult::PouScope& sc,
|
||||
const Expr& e) {
|
||||
@@ -289,30 +416,23 @@ namespace {
|
||||
if (!check_expr(file, sc, e, &t)) {
|
||||
return false;
|
||||
}
|
||||
if (t != TType::Bool) {
|
||||
return fail(file, "condition must be BOOL, got " + std::string(tname(t)));
|
||||
if (!resolve_to(file, e, t, TType::Bool)) {
|
||||
return fail(file, "condition must be BOOL");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析赋值左值类型
|
||||
* @brief 解析赋值左值类型。
|
||||
* @details 普通左值:局部/输入/输出/外部/全局 → 符号类型;
|
||||
* FUNCTION 内函数名 → 返回类型;**FUNCTION 内左值是全局/外部 → 拒绝**
|
||||
* (用例 14:FUNCTION 禁止写全局,含经 VAR_EXTERNAL)
|
||||
* @param file 出错文件
|
||||
* @param sc 当前作用域
|
||||
* @param pou 当前 POU
|
||||
* @param target 左值标识符
|
||||
* @param out 输出左值类型
|
||||
* @return true 合法;false(err 已写)
|
||||
* FUNCTION 内函数名 → 返回类型;**FUNCTION 内左值是全局/外部 → 拒绝**。
|
||||
*/
|
||||
bool assign_target_type(const std::string& file,
|
||||
const LinkResult::PouScope& sc, const POU& pou,
|
||||
const std::string& target, TType* out) {
|
||||
// FUNCTION 内对函数名赋值 = 结果值写入(用例 13 约定)
|
||||
if (pou.kind == PouKind::Function && target == pou.name) {
|
||||
return to_type(pou.result_type.kind, out) ||
|
||||
return to_type(pou.result_type.name, out) ||
|
||||
fail(file, "function result must be scalar");
|
||||
}
|
||||
|
||||
@@ -337,12 +457,7 @@ namespace {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 表达式定类型(递归)
|
||||
* @param file 出错文件
|
||||
* @param sc 当前作用域
|
||||
* @param e 表达式 AST
|
||||
* @param out 输出求值类型
|
||||
* @return true 合法;false(err 已写)
|
||||
* @brief 表达式定类型(递归;V2 字面量未定型机制)。
|
||||
*/
|
||||
bool check_expr(const std::string& file, const LinkResult::PouScope& sc,
|
||||
const Expr& e, TType* out) {
|
||||
@@ -351,11 +466,21 @@ namespace {
|
||||
*out = TType::Bool;
|
||||
return true;
|
||||
case ExprKind::LitInt:
|
||||
*out = TType::Int;
|
||||
case ExprKind::LitReal:
|
||||
*out = TType::Unresolved; // 未定型,消费点定型 + 值域检查
|
||||
return true;
|
||||
case ExprKind::LitTime:
|
||||
*out = TType::Time;
|
||||
return true;
|
||||
case ExprKind::LitDate:
|
||||
*out = TType::Date;
|
||||
return true;
|
||||
case ExprKind::LitTod:
|
||||
*out = TType::Tod;
|
||||
return true;
|
||||
case ExprKind::LitDt:
|
||||
*out = TType::Dt;
|
||||
return true;
|
||||
case ExprKind::VarRef: {
|
||||
const Symbol* s = find_sym(sc, e.name);
|
||||
if (s == nullptr) {
|
||||
@@ -379,7 +504,7 @@ namespace {
|
||||
}
|
||||
for (const FbField& f : it->second.fields) {
|
||||
if (f.name == e.field) {
|
||||
return to_type(f.type, out) ||
|
||||
return to_type(f.type_name, out) ||
|
||||
fail(file, "field '" + e.field + "' has no scalar type");
|
||||
}
|
||||
}
|
||||
@@ -390,7 +515,7 @@ namespace {
|
||||
if (!check_expr(file, sc, *e.operand, &t)) {
|
||||
return false;
|
||||
}
|
||||
if (t != TType::Bool) {
|
||||
if (!resolve_to(file, *e.operand, t, TType::Bool)) {
|
||||
return fail(file, "NOT operand must be BOOL");
|
||||
}
|
||||
*out = TType::Bool;
|
||||
@@ -402,7 +527,8 @@ namespace {
|
||||
if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) {
|
||||
return false;
|
||||
}
|
||||
if (l != TType::Bool || r != TType::Bool) {
|
||||
if (!resolve_to(file, *e.lhs, l, TType::Bool) ||
|
||||
!resolve_to(file, *e.rhs, r, TType::Bool)) {
|
||||
const char* op = (e.kind == ExprKind::And) ? "AND" : "OR";
|
||||
return fail(file, std::string(op) + " operands must be BOOL");
|
||||
}
|
||||
@@ -414,7 +540,14 @@ namespace {
|
||||
if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) {
|
||||
return false;
|
||||
}
|
||||
if (l != r) {
|
||||
// 比较同型:一侧未定型 → 按另一侧定型;两侧未定型 → 结果 BOOL
|
||||
if (l == TType::Unresolved && r == TType::Unresolved) {
|
||||
*out = TType::Bool;
|
||||
return true;
|
||||
}
|
||||
const TType want = (l == TType::Unresolved) ? r : l;
|
||||
if (!resolve_to(file, *e.lhs, l, want) ||
|
||||
!resolve_to(file, *e.rhs, r, want)) {
|
||||
return fail(file, "comparison of mismatched types (" +
|
||||
std::string(tname(l)) + " vs " +
|
||||
std::string(tname(r)) + ")");
|
||||
@@ -426,14 +559,26 @@ namespace {
|
||||
case ExprKind::Sub:
|
||||
case ExprKind::Mul:
|
||||
case ExprKind::Div: {
|
||||
const bool is_add_sub =
|
||||
(e.kind == ExprKind::Add || e.kind == ExprKind::Sub);
|
||||
TType l, r;
|
||||
if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) {
|
||||
return false;
|
||||
}
|
||||
if (l != TType::Int || r != TType::Int) {
|
||||
return fail(file, "arithmetic operands must be INT (TIME has no arithmetic)");
|
||||
// 同型定型:一侧未定型 → 按另一侧
|
||||
TType t = (l == TType::Unresolved) ? r : l;
|
||||
if (l == TType::Unresolved && r == TType::Unresolved) {
|
||||
t = TType::Unresolved; // 两侧字面量,向上传播
|
||||
} else if (!resolve_to(file, *e.lhs, l, t) ||
|
||||
!resolve_to(file, *e.rhs, r, t)) {
|
||||
return fail(file, "arithmetic operands must have same type (" +
|
||||
std::string(tname(l)) + " vs " +
|
||||
std::string(tname(r)) + ")");
|
||||
}
|
||||
*out = TType::Int;
|
||||
if (!arith_allowed(file, t, is_add_sub)) {
|
||||
return false;
|
||||
}
|
||||
*out = t;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Neg: {
|
||||
@@ -441,10 +586,14 @@ namespace {
|
||||
if (!check_expr(file, sc, *e.operand, &t)) {
|
||||
return false;
|
||||
}
|
||||
if (t != TType::Int) {
|
||||
return fail(file, "unary minus operand must be INT");
|
||||
if (t != TType::Unresolved) {
|
||||
const TypeMeta tm = type_meta(cfg_, tname_lower(t));
|
||||
const std::string& kind = tm ? tm.kind() : "";
|
||||
if (kind != "int" && kind != "uint" && kind != "float") {
|
||||
return fail(file, "unary minus operand must be numeric");
|
||||
}
|
||||
}
|
||||
*out = TType::Int;
|
||||
*out = t;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Call: {
|
||||
@@ -452,13 +601,31 @@ namespace {
|
||||
if (f == nullptr) {
|
||||
return fail(file, "undeclared function '" + e.name + "'");
|
||||
}
|
||||
for (const auto& a : e.args) {
|
||||
TType at;
|
||||
if (!check_expr(file, sc, *a, &at)) {
|
||||
return false;
|
||||
// 实参类型 = 函数 VAR_INPUT 声明序
|
||||
std::vector<TType> params;
|
||||
for (const VarBlock& b : f->blocks) {
|
||||
if (b.section != VarSection::Input) {
|
||||
continue;
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
TType pt;
|
||||
if (to_type(d.type.name, &pt)) {
|
||||
params.push_back(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!to_type(f->result_type.kind, out)) {
|
||||
for (size_t i = 0; i < e.args.size(); ++i) {
|
||||
TType at;
|
||||
if (!check_expr(file, sc, *e.args[i], &at)) {
|
||||
return false;
|
||||
}
|
||||
if (i < params.size()) {
|
||||
if (!resolve_to(file, *e.args[i], at, params[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!to_type(f->result_type.name, out)) {
|
||||
return fail(file, "function '" + e.name + "' result must be scalar");
|
||||
}
|
||||
return true;
|
||||
@@ -471,23 +638,24 @@ namespace {
|
||||
const Project& proj_; ///< 工程定义(本阶段未用)
|
||||
const std::vector<SourceUnit>& units_; ///< 全部源文件 AST
|
||||
const LinkResult& link_; ///< 链接结果(只读)
|
||||
const MachineConfig& cfg_; ///< 机器定义(类型元数据/值域)
|
||||
std::string* err_; ///< 错误输出(可空)
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 对工程做类型检查(对外入口,链接成功后调用)
|
||||
* @details 逐 POU 逐语句检查;规则见 Doc/compiler/类型检查.md
|
||||
* @param proj 工程定义(本阶段未用,保留接口对称)
|
||||
* @brief 对工程做类型检查(对外入口,链接成功后调用;V2)
|
||||
* @param proj 工程定义
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(符号/布局已解析)
|
||||
* @param cfg 机器定义(machine.toml)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 全部通过;false 失败(err 前缀 "type error")
|
||||
*/
|
||||
bool check_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, std::string* err) {
|
||||
Checker c(proj, units, link, err);
|
||||
const LinkResult& link, const MachineConfig& cfg, std::string* err) {
|
||||
Checker c(proj, units, link, cfg, err);
|
||||
return c.run();
|
||||
}
|
||||
|
||||
|
||||
+43
-8
@@ -59,12 +59,35 @@ namespace {
|
||||
std::fprintf(stderr, "error: %s\n", v.error().c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("image: %s (%zu bytes, %u functions, %u globals, entry fn %u)\n",
|
||||
path, bytes.size(), v.n_funcs(), v.n_globals(), v.entry_fn_id());
|
||||
std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx model=%s sha=%s\n",
|
||||
v.dt_ms(), v.cycle_limit(),
|
||||
static_cast<unsigned long long>(v.project_hash()),
|
||||
v.model_id().c_str(), v.sha_ok() ? "ok" : "BAD");
|
||||
std::printf("image: %s (%zu bytes)\n", path, bytes.size());
|
||||
std::printf("header:\n");
|
||||
std::printf(" magic = 0x%08x # 魔数 \"STSC\"\n",
|
||||
static_cast<unsigned>(compiler::kMagic));
|
||||
std::printf(" version = %u # 格式版本(V2)\n",
|
||||
static_cast<unsigned>(compiler::kVersion));
|
||||
std::printf(" model = %-14s # 型号标识\n", v.model_id().c_str());
|
||||
std::printf(" cycle_limit = %-14u # 每周期指令数上限\n", v.cycle_limit());
|
||||
std::printf(" dt_ms = %-14u # 周期时长(毫秒)\n", v.dt_ms());
|
||||
std::printf(" hash = 0x%016llx # 工程哈希(FNV-1a 64)\n",
|
||||
static_cast<unsigned long long>(v.project_hash()));
|
||||
std::printf(" entry_fn_id = %-14u # 入口函数(MAIN)fn_id\n", v.entry_fn_id());
|
||||
std::printf(" n_globals = %-14u # 全局槽数\n", v.n_globals());
|
||||
std::printf(" n_i = %-14u # 输入槽数(保留恒 0)\n", 0u);
|
||||
std::printf(" n_q = %-14u # 输出槽数(保留恒 0)\n", 0u);
|
||||
std::printf(" n_m = %-14u # 中间槽数(保留恒 0)\n", 0u);
|
||||
std::printf(" n_consts = %-14u # 常量表条目数\n", v.n_consts());
|
||||
std::printf(" n_funcs = %-14u # 函数表行数\n", v.n_funcs());
|
||||
std::printf(" offset_const = %-9u # 常量表段偏移\n", v.offset_const());
|
||||
std::printf(" offset_funcs = %-9u # 函数表段偏移\n", v.offset_funcs());
|
||||
std::printf(" offset_code = %-9u # 字节码段偏移\n", v.offset_code());
|
||||
std::printf(" offset_slots = %-9u # 槽表段偏移\n", v.offset_slots());
|
||||
std::printf(" offset_values = %-8u # 值段偏移\n", v.offset_values());
|
||||
std::printf(" n_slots = %-14u # 槽表条目数\n", v.n_slots());
|
||||
std::printf(" values_size = %-14u # 值段字节数\n", v.values_size());
|
||||
std::printf(" offset_meta = %-14u # 元数据段偏移(0 = 空段)\n", v.offset_meta());
|
||||
std::printf(" max_stack = %-14u # 栈区字节数(编译期算)\n", v.max_stack());
|
||||
std::printf(" sha = %-14s # 文件尾 SHA-256 完整性\n",
|
||||
v.sha_ok() ? "ok" : "BAD");
|
||||
|
||||
if (v.n_consts()) {
|
||||
std::printf("constants:\n");
|
||||
@@ -92,6 +115,18 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
if (v.n_slots()) {
|
||||
std::printf("slots:\n");
|
||||
for (uint32_t i = 0; i < v.n_slots(); ++i) {
|
||||
const uint32_t addr =
|
||||
static_cast<uint32_t>(v.slots_bytes()[i * 8])
|
||||
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 1]) << 8)
|
||||
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 2]) << 16)
|
||||
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 3]) << 24);
|
||||
std::printf(" [%u] addr=%u\n", i, addr);
|
||||
}
|
||||
}
|
||||
|
||||
if (v.data_len()) {
|
||||
std::printf("data (%zu bytes):\n", v.data_len());
|
||||
const uint8_t* d = v.data_bytes();
|
||||
@@ -209,11 +244,11 @@ int main(int argc, char** argv) {
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
compiler::LinkResult link;
|
||||
if (!compiler::link_project(proj, units, &link, &err)) {
|
||||
if (!compiler::link_project(proj, units, cfg, &link, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (!compiler::check_project(proj, units, link, &err)) {
|
||||
if (!compiler::check_project(proj, units, link, cfg, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -94,8 +94,14 @@ namespace {
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
compiler::MachineConfig cfg;
|
||||
std::string cerr;
|
||||
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) {
|
||||
std::printf("FAIL %s machine: %s\n", dir, cerr.c_str());
|
||||
return false;
|
||||
}
|
||||
LinkResult link;
|
||||
if (!link_project(p, units, &link, &err)) {
|
||||
if (!link_project(p, units, cfg, &link, &err)) {
|
||||
if (want == Outcome::CompileError) {
|
||||
if (err.find(keyword) == std::string::npos) {
|
||||
std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str());
|
||||
@@ -107,7 +113,7 @@ namespace {
|
||||
std::printf("FAIL %s link: %s\n", dir, err.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!check_project(p, units, link, &err)) {
|
||||
if (!check_project(p, units, link, cfg, &err)) {
|
||||
if (want == Outcome::CompileError) {
|
||||
if (err.find(keyword) == std::string::npos) {
|
||||
std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str());
|
||||
@@ -119,11 +125,6 @@ namespace {
|
||||
std::printf("FAIL %s type: %s\n", dir, err.c_str());
|
||||
return false;
|
||||
}
|
||||
MachineConfig cfg;
|
||||
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) {
|
||||
std::printf("FAIL %s machine load\n", dir);
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> img;
|
||||
if (!codegen_project(p, units, link, cfg, &img, &err)) {
|
||||
if (want == Outcome::CompileError) {
|
||||
|
||||
@@ -43,7 +43,12 @@ static bool link_case(const char* dir, compiler::LinkResult* out, std::string* e
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
return link_project(p, units, out, err);
|
||||
compiler::MachineConfig cfg;
|
||||
std::string cerr;
|
||||
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) {
|
||||
return false;
|
||||
}
|
||||
return link_project(p, units, cfg, out, err);
|
||||
}
|
||||
|
||||
static bool expect_link_err(const char* dir, const char* keyword) {
|
||||
@@ -146,7 +151,10 @@ static bool test_line1() {
|
||||
CHECK(load_unit(p.base_dir + "/" + f, &u, &err));
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
CHECK(link_project(p, units, &r, &err));
|
||||
compiler::MachineConfig cfg;
|
||||
std::string cerr;
|
||||
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr));
|
||||
CHECK(link_project(p, units, cfg, &r, &err));
|
||||
|
||||
// 全局槽序 = 声明顺序
|
||||
CHECK(r.globals.size() == 4);
|
||||
@@ -162,7 +170,7 @@ static bool test_line1() {
|
||||
CHECK(it->second.fields[0].name == "start");
|
||||
CHECK(it->second.fields[1].name == "stop");
|
||||
CHECK(it->second.fields[2].name == "q");
|
||||
CHECK(it->second.fields[2].type == TypeKind::Bool);
|
||||
CHECK(it->second.fields[2].type_name == "bool");
|
||||
|
||||
// 内建 TON 布局(用例 17 的 t)
|
||||
const FbLayout* ton = nullptr;
|
||||
@@ -204,16 +212,19 @@ static bool test_builtin() {
|
||||
CHECK(load_unit(p.base_dir + "/" + f, &u, &err));
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
CHECK(link_project(p, units, &r, &err));
|
||||
compiler::MachineConfig cfg;
|
||||
std::string cerr;
|
||||
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr));
|
||||
CHECK(link_project(p, units, cfg, &r, &err));
|
||||
CHECK(r.scopes.size() == 1);
|
||||
const LinkResult::PouScope& main = r.scopes[0];
|
||||
const auto it = main.fb_instances.find("t");
|
||||
CHECK(it != main.fb_instances.end());
|
||||
CHECK(it->second.fields.size() == 4);
|
||||
CHECK(it->second.fields[0].name == "in" && it->second.fields[0].type == TypeKind::Bool);
|
||||
CHECK(it->second.fields[1].name == "pt" && it->second.fields[1].type == TypeKind::Time);
|
||||
CHECK(it->second.fields[2].name == "q" && it->second.fields[2].type == TypeKind::Bool);
|
||||
CHECK(it->second.fields[3].name == "et" && it->second.fields[3].type == TypeKind::Time);
|
||||
CHECK(it->second.fields[0].name == "in" && it->second.fields[0].type_name == "bool");
|
||||
CHECK(it->second.fields[1].name == "pt" && it->second.fields[1].type_name == "time");
|
||||
CHECK(it->second.fields[2].name == "q" && it->second.fields[2].type_name == "bool");
|
||||
CHECK(it->second.fields[3].name == "et" && it->second.fields[3].type_name == "time");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ static bool test_gvl_file() {
|
||||
CHECK(g.section == VarSection::Global);
|
||||
CHECK(g.vars.size() == 4);
|
||||
CHECK(g.vars[0].name == "emergencystop");
|
||||
CHECK(g.vars[0].type.kind == TypeKind::Bool);
|
||||
CHECK(g.vars[0].type.kind == TypeKind::Scalar && g.vars[0].type.name == "bool");
|
||||
CHECK(g.vars[0].has_init && g.vars[0].init_value == 0); // := FALSE
|
||||
CHECK(!g.vars[1].has_init); // I0_0
|
||||
return true;
|
||||
@@ -145,7 +145,7 @@ static bool test_function() {
|
||||
const POU& f = u.pous[0];
|
||||
CHECK(f.kind == PouKind::Function);
|
||||
CHECK(f.name == "add");
|
||||
CHECK(f.result_type.kind == TypeKind::Int);
|
||||
CHECK(f.result_type.kind == TypeKind::Scalar && f.result_type.name == "int");
|
||||
CHECK(f.body[0].kind == StmtKind::Assign);
|
||||
CHECK(f.body[0].value->kind == ExprKind::Add);
|
||||
|
||||
@@ -192,7 +192,7 @@ static bool test_control() {
|
||||
CHECK(parse_pous("t.st", src, &u, &err));
|
||||
const POU& p = u.pous[0];
|
||||
CHECK(p.blocks[0].vars[2].name == "t");
|
||||
CHECK(p.blocks[0].vars[2].type.kind == TypeKind::FbBuiltin);
|
||||
CHECK(p.blocks[0].vars[2].type.kind == TypeKind::FbUser);
|
||||
CHECK(p.blocks[0].vars[2].type.name == "ton");
|
||||
|
||||
CHECK(p.body.size() == 4);
|
||||
|
||||
@@ -45,10 +45,15 @@ static bool check_case(const char* dir, compiler::LinkResult* r, std::string* er
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
if (!link_project(p, units, r, err)) {
|
||||
compiler::MachineConfig cfg;
|
||||
std::string cerr;
|
||||
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) {
|
||||
return false;
|
||||
}
|
||||
return check_project(p, units, *r, err);
|
||||
if (!link_project(p, units, cfg, r, err)) {
|
||||
return false;
|
||||
}
|
||||
return check_project(p, units, *r, cfg, err);
|
||||
}
|
||||
|
||||
// 从内联源码构造工程(写临时目录),加载、链接、类型检查
|
||||
@@ -81,10 +86,15 @@ static bool check_src(const char* src_name, const char* st_content,
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult r;
|
||||
if (!link_project(p, units, &r, err)) {
|
||||
compiler::MachineConfig cfg;
|
||||
std::string cerr;
|
||||
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) {
|
||||
return false;
|
||||
}
|
||||
return check_project(p, units, r, err);
|
||||
if (!link_project(p, units, cfg, &r, err)) {
|
||||
return false;
|
||||
}
|
||||
return check_project(p, units, r, cfg, err);
|
||||
}
|
||||
|
||||
static bool expect_type_err(const char* src_name, const char* st_content,
|
||||
@@ -134,14 +144,14 @@ static bool test_negative() {
|
||||
if (!expect_type_err("assign_mismatch",
|
||||
"PROGRAM MAIN\nVAR\n b : BOOL;\nEND_VAR\n"
|
||||
" b := 5;\nEND_PROGRAM\n",
|
||||
"type mismatch in assignment to 'b'")) {
|
||||
"BOOL literal must be 0 or 1")) {
|
||||
return false;
|
||||
}
|
||||
// INT 与 TIME 混用算术
|
||||
if (!expect_type_err("int_time",
|
||||
"PROGRAM MAIN\nVAR\n x : INT;\n t : TIME;\n y : INT;\nEND_VAR\n"
|
||||
" y := x + t;\nEND_PROGRAM\n",
|
||||
"TIME has no arithmetic")) {
|
||||
"arithmetic operands must have same type")) {
|
||||
return false;
|
||||
}
|
||||
// NOT 吃 INT
|
||||
@@ -163,7 +173,7 @@ static bool test_negative() {
|
||||
"PROGRAM MAIN\nVAR\n t : TON;\n q : BOOL;\nEND_VAR\n"
|
||||
" t(in := TRUE, pt := TRUE);\n"
|
||||
" q := t.Q;\nEND_PROGRAM\n",
|
||||
"FB input 'pt' expects TIME")) {
|
||||
"type mismatch: BOOL vs TIME")) {
|
||||
return false;
|
||||
}
|
||||
// IF 条件非 BOOL
|
||||
|
||||
@@ -73,10 +73,10 @@ static bool make_machine(const char* dir, vm::Machine* m, std::string* err) {
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult link;
|
||||
if (!link_project(p, units, &link, err)) {
|
||||
if (!link_project(p, units, cfg, &link, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!check_project(p, units, link, err)) {
|
||||
if (!check_project(p, units, link, cfg, err)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> img;
|
||||
@@ -460,10 +460,10 @@ static bool test_verify() {
|
||||
CHECK(compiler::load_unit(p.base_dir + "/" + f, &u, &err));
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
compiler::LinkResult link;
|
||||
CHECK(compiler::link_project(p, units, &link, &err));
|
||||
compiler::MachineConfig cfg;
|
||||
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err));
|
||||
compiler::LinkResult link;
|
||||
CHECK(compiler::link_project(p, units, cfg, &link, &err));
|
||||
std::vector<uint8_t> img;
|
||||
CHECK(compiler::codegen_project(p, units, link, cfg, &img, &err));
|
||||
img[100] ^= 0x01; // 篡改代码段一字节
|
||||
|
||||
Reference in New Issue
Block a user