FluffOS 驱动支持的现代 LPC 特性

第十章 FluffOS 现代特性

本章介绍 FluffOS 驱动支持的现代 LPC 特性,这些特性在传统 LPC 教程中较少涉及,但能显著提升代码的可读性与开发效率。建议配合 #pragma strict_types 使用。


10.1 模板字面量(Template Literals)

使用反引号 ` 包裹字符串,支持 ${expression} 插值,类似 JavaScript 的模板字符串。

string name = "Alice";
int count = 3;

`Hello, ${name}!`              // "Hello, Alice!"
`You have ${count} items.`     // "You have 3 items."
`Sum: ${1 + 2}`                // "Sum: 3"

转义:

`price is \$100`               // 美元符号转义
`use \`backticks\``            // 反引号转义

换行处理: 模板字面量内的换行会被折叠(移除):

`line one
line two`
// 结果: "line oneline two"

相邻拼接: 模板字面量可与普通字符串和其它模板字面量相邻拼接:

`Hello, ${name}! ` `How are you?`
`Count: ${n}` " items"
"Hello, " `${name}!`

10.2 可选链(Optional Chaining)

在访问可能为 0(undefined)的 mapping 时,可选链可以安全地返回 0 而不会报错。传统的 m["key"]m0 时会抛出 "Indexing on illegal type" 错误。

mapping m = 0;

// 传统写法会报错:
// mixed val = m["key"];     // Error: Indexing on illegal type

// 可选链安全返回 0:
mixed val = m?.key;           // 返回 0,不报错
mixed val2 = m?.["key"];      // 等价写法
mixed val3 = m.?[\"key"];     // 另一种写法

可选链支持三种语法形式,可根据代码风格选用:

语法 说明
m?.key 点号形式,适合简单键名
m?.["key"] 方括号形式,?. 在前
m.?[\"key"] 方括号形式,.? 在前

可选链与空值合并搭配使用效果更佳:

mapping user = get_user_data();
string name = user?.name ?? "匿名用户";

10.3 空值合并运算符(Null Coalescing)

?? 运算符在左侧为 undefined(即 0)时返回右侧值。注意:0"" 不是 undefined,只有未初始化/赋值为 0 的变量才触发。

mixed val = some_mapping["key"] ?? "default";

// 等价于
mixed val = some_mapping["key"];
if (undefinedp(val)) val = "default";

与三元运算符的区别:

int x = 0;
mixed a = x ?? 42;        // a = 0,因为 x 不是 undefined(它是 0)
mixed b = undefinedp(x) ? 42 : x;  // 同上

string s = "";
mixed c = s ?? "empty";   // c = "",因为空字符串不是 undefined

10.4 逻辑赋值运算符

FluffOS 支持三种短路逻辑赋值运算符,简化条件赋值模式:

运算符 等价形式 说明
\|\|= x = x \|\| val x 为 falsy 时赋值
&&= x = x && val x 为 truthy 时赋值
??= x = x ?? val x 为 undefined 时赋值
// ||=:设置默认值(如果当前值为 falsy)
mapping config = ([]);
config["debug"] ||= FALSE;       // 如果 debug 为 0/falsy,设为 FALSE

// &&=:仅在已有值时更新
int hp = 50;
hp &&= hp - 10;                  // hp 为 truthy 时减 10,结果 40

// ??=:仅在 undefined 时赋值
mapping m = ([]);
m["key"] ??= "default";          // key 不存在时设为 "default"

10.5 展开运算符(Spread Operator)

展开运算符 ... 可以将数组展开为独立元素,用于数组字面量和函数调用。

数组字面量中展开:

int *a = ({ 1, 2 });
int *b = ({ 3, 4 });
int *c = ({ a..., b... });       // ({ 1, 2, 3, 4 })

// 可以在展开间穿插元素
int *d = ({ a..., 99, b... });   // ({ 1, 2, 99, 3, 4 })

函数调用中展开:

void show(string a, string b, string c) {
    write(a + ", " + b + ", " + c + "\n");
}

string *args = ({ "Alice", "Bob", "Charlie" });
show(args...);                    // 等价于 show("Alice", "Bob", "Charlie")

// 展开后还可以追加参数
void log(string level, mixed *msgs...) { }
string *base_args = ({ "INFO" });
log(base_args..., "extra msg");   // level="INFO", msgs=({"extra msg"})

10.6 按引用传递(ref / & 参数)

函数参数默认按值传递(值类型)或按引用共享(引用类型)。使用 ref& 可以显式声明按引用传递,允许函数修改调用者的变量。

声明引用参数:

void increment(int ref value) {
    value++;
}

void append_item(mixed & arr, mixed item) {
    arr += ({ item });
}

调用时使用 ref 或 &:

int x = 10;
increment(ref x);        // x = 11
increment(& x);          // x = 12

mixed *items = ({ "a" });
append_item(& items, "b");   // items = ({ "a", "b" })

限制:

  • ref 必须在声明和调用处同时使用
  • 参数必须是左值(变量,不能是表达式)
  • 不能引用数组/字符串的范围

10.7 foreach 与 ref 结合

foreach 支持 ref(或 &)遍历,可以直接修改原数组或 buffer 的元素。

// 按引用遍历数组——修改原数组
int *nums = ({ 1, 2, 3 });
foreach (int ref n in nums) {
    n *= 2;                // nums 变为 ({ 2, 4, 6 })
}

// & 是 ref 的语法糖
foreach (int & n in nums) {
    n *= 2;                // nums 变为 ({ 4, 8, 12 })
}

// 按引用遍历 buffer
buffer buf = allocate_buffer(3);
buf[0] = 10; buf[1] = 20; buf[2] = 30;
foreach (int & b in buf) {
    b += 1;                // 每个字节值加 1
}

注意: ref 只能修改数组buffer 的元素。对字符串使用 ref 遍历不会修改原字符串(字符串是值类型,传入 foreach 的是副本)。


10.8 Heredoc 文本块(@ 和 @@)

Heredoc 语法用于定义多行文本,避免手动拼接换行符。

@ 产生单个字符串:

string text = @END
这里可以写
多行文本内容
END
// text 包含末尾换行符

@@ 产生字符串数组:

string *lines = @@END
第一行
第二行
第三行
END
// lines = ({ "第一行", "第二行", "第三行" })
// 不包含换行符

实际应用:

void show_help() {
    string help = @HELP
可用指令:
  look  - 查看周围
  north - 向北移动
  south - 向南移动
  quit  - 退出游戏
HELP
    write(help);
}

// @@ 适合定义配置列表
string *banned_words = @@WORDS
badword1
badword2
badword3
WORDS

10.9 类型严格检查(#pragma strict_types)

强烈推荐在每个 LPC 文件开头启用严格类型检查。启用后编译器会在编译时捕获类型错误,避免运行时意外。

#pragma strict_types

启用后的效果:

  1. 函数必须声明返回类型和参数类型:
// 正确
int add(int a, int b) {
    return a + b;
}

// 错误——缺少类型声明(编译报错)
// add(a, b) { return a + b; }
  1. 调用和赋值会进行类型检查:
int x = add(1, 2);       // 正确
string s = add(1, 2);    // 编译警告/错误:int 赋给 string
  1. 使用 mixed 关闭特定位置的检查:
varargs mixed query(string key, mixed def) {
    // def 可以是任意类型
    return def;
}

相关 pragma:

指令 说明
#pragma strict_types 强制类型检查
#pragma save_types 保存类型信息供继承者检查
#pragma warnings 启用编译警告
#pragma no_warnings 禁用编译警告
#pragma optimize 启用额外优化

10.10 简化函数指针语法

FluffOS 支持将函数名直接赋值给 function 类型变量,无需 (: :) 包裹。

传统语法:

function f = (: local_func :);
function f = (: write :);
int result = evaluate(f, "hello\n");

简化语法:

function f = local_func;
function f = write;
int result = f("hello\n");       // 直接调用,无需 evaluate()

在数据结构中使用:

// 函数数组
function *ops = ({ add, subtract, multiply });
int r = ops[0](10, 5);           // 调用 add(10, 5)

// 函数 mapping——实现命令分发
mapping cmds = ([
    "attack": do_attack,
    "defend": do_defend,
    "heal":   do_heal,
]);
if (cmds[action]) cmds[action]();

10.11 匿名函数(Lambda)

匿名函数允许直接在表达式中定义函数体,适合回调和一次性使用的逻辑。

// 基本匿名函数
function f = function(int x) {
    int y = x * 2;
    return y + 1;
};
int result = f(5);               // result = 11

// 作为回调使用
int *evens = filter_array(arr, function(int x) {
    return x % 2 == 0;
});

// 排序比较器
sort_array(players, function(object a, object b) {
    return (int)a->query_level() - (int)b->query_level();
});

匿名函数与闭包: 匿名函数可以捕获定义时的外部变量(闭包):

function make_adder(int base) {
    return function(int x) {
        return base + x;        // 捕获了外部的 base
    };
}

function add5 = make_adder(5);
int r = add5(10);               // r = 15

10.12 new() 关键字

new 关键字用于克隆对象和创建 class 实例,是 clone_object() 的替代语法。

克隆对象:

// 基本克隆
object ob = new("/npc/goblin");

// 等价于
object ob = clone_object("/npc/goblin");

// 带参数克隆(参数传给 create())
object ob = new("/npc/goblin", "Goblin King");

创建 class 实例:

class Person {
    string name;
    int level;
    float *scores;
}

// 创建空实例
class Person p = new(class Person);

// 带命名初始化
class Person p = new(class Person, name : "Alice", level : 10);

// 访问成员
p.name = "Bob";
p->name = "Bob";               // 箭头语法同样可用

class 的跨文件共享: 通过 inherit 共享 class 定义,不要用 #include

// /std/person.h 中定义 class
// /std/npc.c 中使用
inherit "/std/person";
class Person npc = new(class Person, name : "Guard");

10.13 综合示例

以下示例综合运用了多种现代特性:

#pragma strict_types

inherit "/std/object";

// Heredoc 定义帮助文本
private string *help_lines = @@HELP
look  - 查看周围
inv   - 查看背包
quit  - 退出游戏
HELP

// 使用匿名函数作为命令处理器
private mapping command_handlers = ([]);

void init_handlers() {
    command_handlers["look"] = function(object player) {
        string desc = environment(player)?.long ?? "一片虚无。";
        tell_object(player, desc);
    };
}

// 使用 ref 参数和展开运算符
varargs void broadcast(string *exclude..., string msg) {
    object *users = users() - exclude;
    foreach (object &u in users) {
        tell_object(u, msg);
    }
}

// 使用可选链和空值合并安全访问数据
string get_player_title(mapping data) {
    return data?.title ?? data?.name ?? "无名者";
}

// 使用模板字面量格式化输出
string format_status(string name, int hp, int max_hp) {
    return `[$[name] $[hp]/$[max_hp]]`;
}

// 使用 ??= 设置默认配置
void ensure_defaults(mapping config) {
    config["max_hp"]    ??= 100;
    config["max_mp"]    ??= 50;
    config["debug"]     ||= FALSE;
    config["name"]      ??= "default";
}

本章介绍的现代特性均基于 FluffOS 驱动源码 src/compiler/internal/grammar.y 中的语法实现。不同版本的 FluffOS 对这些特性的支持程度可能有所差异,建议使用较新版本的 FluffOS 驱动。

京ICP备13031296号-4