- 前端
- 构建工具
【免费下载链接】node-sass
:rainbow: Node.js bindings to libsass
Sass_Value是 libsass C API 中值系统(Sass Values)的核心载体,也是任何语言绑定(包括 node-sass 自身)实现自定义函数、自定义 importer 时必须在宿主语言对象与 C 结构体之间做“marshalling(编组)”的那一层。本文以 libsass 内部结构定义为主线,完整讲解Sass_Value联合体(union)的内存布局、9 种类型标签的存取方式、全套 C API 的签名与内存所有权约定,并结合 node-sass 仓库内 src/sass_types 目录下的真实绑定代码,说明这套 C 结构是如何被包装成 JavaScript 端sass.types.*构造器族系的。读完本文,你将能够安全地在自己的绑定中使用这些 API 而不出错——尤其是内存泄漏与双重释放这两类最常见的问题。
类型标签、分隔符与运算符:值系统的基本枚举
要理解内部结构,先要看清三个枚举定义。它们同时出现在对外头文件 src/libsass/include/sass/values.h 与对外文档 src/libsass/docs/api-value.md 中:
// Type for Sass values enum Sass_Tag { SASS_BOOLEAN, SASS_NUMBER, SASS_COLOR, SASS_STRING, SASS_LIST, SASS_MAP, SASS_NULL, SASS_ERROR, SASS_WARNING }; // Tags for denoting Sass list separators enum Sass_Separator { SASS_COMMA, SASS_SPACE, // only used internally to represent a hash map before evaluation // otherwise we would be too early to check for duplicate keys SASS_HASH }; // Value Operators enum Sass_OP { AND, OR, // logical connectives EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations ADD, SUB, MUL, DIV, MOD, // arithmetic functions NUM_OPS // so we know how big to make the op table };三个要点值得注意:
Sass_Tag共 9 个值,与后文union Sass_Value的 9 个成员一一对应,它是所有类型判断的“第一公民”;SASS_HASH分隔符并不面向普通用户,头文件注释明确说明它“only used internally to represent a hash map before evaluation”(仅在求值前内部表示哈希映射使用),因为在求值之前尚无法检查重复键;Sass_OP覆盖逻辑连接、算术关系、算术函数三类运算符,最后一个枚举值NUM_OPS不参与运算,只用于“so we know how big to make the op table”(确定运算符表的大小)。
Sass_Values在 libsass 中的定位,src/libsass/docs/api-value.md 有总述:Sass 知道多种不同的值类型(包括嵌套数组和哈希映射),实现另一种语言的绑定时,你必须找到一种方式在目标语言与 C 之间对Sass_Value做编组(marshal)。该文档同时指出,Sass_Values目前主要被自定义函数使用。
Sass_Value 联合体的内部内存布局
这是本文的核心。内部结构定义见 src/libsass/docs/api-value-internal.md,它与 src/libsass/include/sass/values.h 共同构成“对外 API + 内部布局”的完整知识体系。内部文档给出的全部结构如下:
struct Sass_Unknown { enum Sass_Tag tag; }; struct Sass_Boolean { enum Sass_Tag tag; bool value; }; struct Sass_Number { enum Sass_Tag tag; double value; char* unit; }; struct Sass_Color { enum Sass_Tag tag; double r; double g; double b; double a; }; struct Sass_String { enum Sass_Tag tag; char* value; }; struct Sass_List { enum Sass_Tag tag; enum Sass_Separator separator; size_t length; // null terminated "array" union Sass_Value** values; }; struct Sass_Map { enum Sass_Tag tag; size_t length; struct Sass_MapPair* pairs; }; struct Sass_Null { enum Sass_Tag tag; }; struct Sass_Error { enum Sass_Tag tag; char* message; }; struct Sass_Warning { enum Sass_Tag tag; char* message; }; union Sass_Value { struct Sass_Unknown unknown; struct Sass_Boolean boolean; struct Sass_Number number; struct Sass_Color color; struct Sass_String string; struct Sass_List list; struct Sass_Map map; struct Sass_Null null; struct Sass_Error error; struct Sass_Warning warning; }; struct Sass_MapPair { union Sass_Value* key; union Sass_Value* value; };这套布局包含三个关键设计:
设计一:tag 永远在偏移 0 处。每个Sass_*结构体的第一个字段都是enum Sass_Tag tag。这保证了通过任意成员(甚至通过Sass_Unknown这个只含 tag 的视图)读取第一个字段,都能拿到正确的类型标签。libsass 实现里也确实如此:src/libsass/src/sass_values.cpp 中sass_value_get_tag的实现就是一行return v->unknown.tag;。这也解释了为何所有sass_value_is_*检查都只是比较 tag:
bool ADDCALL sass_value_is_null(const union Sass_Value* v) { return v->unknown.tag == SASS_NULL; } bool ADDCALL sass_value_is_number(const union Sass_Value* v) { return v->unknown.tag == SASS_NUMBER; } // ...其余类型同理(见 src/libsass/src/sass_values.cpp)
设计二:容器类型持有“堆上指针数组”,形成所有权树。Sass_List的values是一块union Sass_Value**指针数组(注释标注为 "null terminated array"),Sass_Map的pairs是struct Sass_MapPair*数组,而每个Sass_MapPair又是一对指向子Sass_Value的指针。因此一个 map/list 在内存上是一棵树:根节点是联合体本身,字符串、单位串、子节点各自独立分配。这直接决定了析构与克隆都必须递归——后文会看到实现。
设计三:联合体内“同名偏移、不同语义”。同一块内存按不同结构体重解释,例如v->number.unit、v->string.value、v->error.message实际指向同一偏移的char*字段。使用方必须先检查 tag 再访问对应成员,这是对外 API 注释反复强调的:“Check is needed before accessing specific values!”(见 src/libsass/include/sass/values.h)。
与真实源码结构的差异:两处演进
需要指出的是,内部文档的结构体是简化/较早的版本,与 libsass 当前真实头文件 src/libsass/src/sass_values.hpp 存在两处差异,实际写绑定时应以后者为准:
| 结构体 | api-value-internal.md | 实际 src/libsass/src/sass_values.hpp |
|---|---|---|
Sass_String | 仅tag+char* value | 增加了bool quoted字段(L29-L33) |
Sass_List | tag、separator、length、values | 增加了bool is_bracketed字段(L35-L42) |
这两个字段分别支撑了 API 中的sass_string_is_quoted / sass_string_set_quoted与sass_list_get_is_bracketed / sass_list_set_is_bracketed,对应的赋值实现位于 src/libsass/src/sass_values.cpp 与 src/libsass/src/sass_values.cpp。也就是说:带引号/不带引号的字符串,以及带括号(如#()语法产生的括号列表)/不带括号的列表,在 C 层都有独立状态位。
完整 C API 面:创建、检查、存取、销毁、克隆、运算符
对外头文件 src/libsass/include/sass/values.h 声明了全部函数(ADDAPI/ADDCALL是跨平台导出宏,实际签名与 src/libsass/docs/api-value.md 中列出的“Sass Value API”一致)。以下按功能分组完整列出。
各类型的创建函数
// Creator functions for all value types union Sass_Value* sass_make_null (void); union Sass_Value* sass_make_boolean (bool val); union Sass_Value* sass_make_string (const char* val); union Sass_Value* sass_make_qstring (const char* val); union Sass_Value* sass_make_number (double val, const char* unit); union Sass_Value* sass_make_color (double r, double g, double b, double a); union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); union Sass_Value* sass_make_map (size_t len); union Sass_Value* sass_make_error (const char* msg); union Sass_Value* sass_make_warning (const char* msg);注意区分sass_make_string与sass_make_qstring:前者创建未加引号字符串,后者创建加引号字符串,二者唯一区别就是quoted标志(见 src/libsass/src/sass_values.cpp)。
销毁、克隆与通用操作
// Generic destructor function for all types // Will release memory of all associated Sass_Values // Means we will delete recursively for lists and maps void sass_delete_value (union Sass_Value* val); // Make a deep cloned copy of the given sass value union Sass_Value* sass_clone_value (const union Sass_Value* val); // Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) union Sass_Value* sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); // Execute an operation for two Sass_Values and return the result as a Sass_Value too union Sass_Value* sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); // Return the sass tag for a generic sass value // Check is needed before accessing specific values! enum Sass_Tag sass_value_get_tag (const union Sass_Value* v);类型检查
// Check value to be of a specific type // Can also be used before accessing properties! bool sass_value_is_null (const union Sass_Value* v); bool sass_value_is_number (const union Sass_Value* v); bool sass_value_is_string (const union Sass_Value* v); bool sass_value_is_boolean (const union Sass_Value* v); bool sass_value_is_color (const union Sass_Value* v); bool sass_value_is_list (const union Sass_Value* v); bool sass_value_is_map (const union Sass_Value* v); bool sass_value_is_error (const union Sass_Value* v); bool sass_value_is_warning (const union Sass_Value* v);各类型的 getter / setter
// Getters and setters for Sass_Number double sass_number_get_value (const union Sass_Value* v); void sass_number_set_value (union Sass_Value* v, double value); const char* sass_number_get_unit (const union Sass_Value* v); void sass_number_set_unit (union Sass_Value* v, char* unit); // Getters and setters for Sass_String const char* sass_string_get_value (const union Sass_Value* v); void sass_string_set_value (union Sass_Value* v, char* value); bool sass_string_is_quoted(const union Sass_Value* v); void sass_string_set_quoted(union Sass_Value* v, bool quoted); // Getters and setters for Sass_Boolean bool sass_boolean_get_value (const union Sass_Value* v); void sass_boolean_set_value (union Sass_Value* v, bool value); // Getters and setters for Sass_Color double sass_color_get_r (const union Sass_Value* v); void sass_color_set_r (union Sass_Value* v, double r); double sass_color_get_g (const union Sass_Value* v); void sass_color_set_g (union Sass_Value* v, double g); double sass_color_get_b (const union Sass_Value* v); void sass_color_set_b (union Sass_Value* v, double b); double sass_color_get_a (const union Sass_Value* v); void sass_color_set_a (union Sass_Value* v, double a); // Getter for the number of items in list size_t sass_list_get_length (const union Sass_Value* v); // Getters and setters for Sass_List enum Sass_Separator sass_list_get_separator (const union Sass_Value* v); void sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); bool sass_list_get_is_bracketed (const union Sass_Value* v); void sass_list_set_is_bracketed (union Sass_Value* v, bool value); // Getters and setters for Sass_List values union Sass_Value* sass_list_get_value (const union Sass_Value* v, size_t i); void sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); // Getter for the number of items in map size_t sass_map_get_length (const union Sass_Value* v); // Getters and setters for Sass_Map keys and values union Sass_Value* sass_map_get_key (const union Sass_Value* v, size_t i); void sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); union Sass_Value* sass_map_get_value (const union Sass_Value* v, size_t i); void sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); // Getters and setters for Sass_Error char* sass_error_get_message (const union Sass_Value* v); void sass_error_set_message (union Sass_Value* v, char* msg); // Getters and setters for Sass_Warning char* sass_warning_get_message (const union Sass_Value* v); void sass_warning_set_message (union Sass_Value* v, char* msg);所有 getter/setter 在 libsass 中的实现都是对联合体对应成员的直读直写,不做 tag 校验(例如 src/libsass/src/sass_values.cpp 中sass_number_get_value就是return v->number.value;)。类型安全完全依赖调用者先做sass_value_get_tag/sass_value_is_*判断。
创建函数的内存语义:谁负责复制,谁负责释放
阅读 src/libsass/src/sass_values.cpp 中的创建函数实现,可以提炼出一套统一的内存约定:
- 统一以
calloc(1, sizeof(Sass_Value))分配,零初始化后写入tag与数据;分配失败(返回0)时直接返回NULL,因此所有创建函数必须做判空; const char*入参会被深拷贝:sass_make_number对unit、sass_make_string/sass_make_qstring对val、sass_make_error/sass_make_warning对msg,都会调用sass_copy_c_string做复制,并在复制失败时free(v)后返回NULL(例如 sass_make_number)。这意味着你传入的字符串生命周期由自己管理,libsass 持有独立副本;- 容器创建只分配骨架:
sass_make_list分配len个指针的数组(初始为NULL),sass_make_map分配len个Sass_MapPair(src/libsass/src/sass_values.cpp)。元素本身需要调用者再用sass_list_set_value/sass_map_set_key/sass_map_set_value填入,插入后所有权移交给容器; sass_delete_value递归释放整棵树:实现位于 src/libsass/src/sass_values.cpp,按 tag 分支——SASS_NUMBER释放unit,SASS_STRING释放value,SASS_LIST先递归删除每个子值再free(val->list.values),SASS_MAP递归删除每对 key/value 再free(val->map.pairs),SASS_ERROR/SASS_WARNING释放message,最后统一free(val);sass_clone_value是深克隆:实现位于 src/libsass/src/sass_values.cpp,对 list/map 递归克隆子节点;克隆字符串时会保留引号状态——sass_string_is_quoted(val) ? sass_make_qstring(...) : sass_make_string(...)(src/libsass/src/sass_values.cpp)。
由此得到绑定层最重要的三条纪律:
- 谁创建(或 set 进去)谁负责;
sass_delete_value必须且只能调用一次; - 跨语言边界传递值时,用
sass_clone_value把所有权转移给接收方(node-sass 正是这么做的,见后文); - 对
sass_list_get_value/sass_map_get_key返回的指针不要单独sass_delete_value,它们属于容器,随根节点一起销毁。
sass_value_op 与 sass_value_stringify:把运算符和序列化做成纯 C 调用
sass_value_op(src/libsass/src/sass_values.cpp)把两个Sass_Value按Sass_OP做运算并返回新的Sass_Value,内部分派顺序是:
- 关系与逻辑运算符(
EQ/NEQ/GT/GTE/LT/LTE/AND/OR)优先处理,结果一律返回布尔值,交给 C++ 层的Operators::系列函数; - 双
Number走Operators::op_numbers;Number与Color互算走op_number_color/op_color_number;双Color走op_colors(颜色混合); - 其余情况回退到
op_strings,先把两侧值转成字符串再运算; - 全程以
try/catch兜底:任何Exception::InvalidSass、std::bad_alloc(返回"memory exhausted")、一般std::exception或未知异常,都统一转成sass_make_error(...)返回给调用者,而不是抛出 C++ 异常穿越 C ABI。
这意味着自定义函数里做算术(如sass_value_op(MUL, a, b))时,拿到结果必须先判断sass_value_is_error,出错时经sass_error_get_message读取错误文本。
sass_value_stringify(src/libsass/src/sass_values.cpp)则把一个任意Sass_Value序列化为 Sass 源文本,参数compressed选择压缩/嵌套输出样式、precision控制数字精度,返回值是一个quoted 字符串类型的Sass_Value(内部经sass_make_qstring包装),所以调用者仍需用sass_delete_value释放它。
node-sass 实战:Sass_Value 如何变成 JavaScript 的 sass.types.*
node-sass 仓库 src/sass_types 目录就是本文所讲 C API 的一个完整真实消费方,它把每种Sass_Value包成带getValue/setValue等方法的 JS 对象(如sass.types.Number),并暴露types命名空间给 JS 侧。
基类 Value:用“克隆所有权”跨过 C/JS 边界
src/sass_types/value.h 定义了所有类型的公共基类(继承Nan::ObjectWrap):
class Value : public Nan::ObjectWrap { public: virtual v8::Local<v8::Object> get_js_object() =0; Sass_Value* get_sass_value() { return sass_clone_value(this->value); } protected: Sass_Value* value; Value(Sass_Value* v) { this->value = sass_clone_value(v); } ~Value() { sass_delete_value(this->value); } };三个方法恰好对应前文的三条内存纪律:
- 构造函数里
sass_clone_value(v):JS 对象接管一份克隆的所有权,调用方可以继续持有或释放原值; ~Value()里sass_delete_value:JS 对象被 GC 时自动递归释放 C 侧整棵值树;get_sass_value()每次对外吐出值都返回新克隆,防止调用方意外释放基类内部数据。
模板包装器 SassValueWrapper:统一构造入口
src/sass_types/sass_value_wrapper.h 的SassValueWrapper<T>模板处理“JS 对象构造 + 包装 C 值”的全部样板:
get_js_object()(L36-L43)懒创建 V8 对象并Wrap进去;- 静态
NAN_METHOD(New)(L66-L92)同时支持new T(...)与T(...)两种调用形式;以构造调用方式进入时,先调T::construct(args, &value)由子类调用sass_make_*创建 C 值,成功后new T(value)包装——注意基类构造时已克隆,所以这里的原始value随即sass_delete_value(value)释放,避免双重持有;construct返回NULL(表示创建失败)时,则通过sass_error_get_message(value)读取 C 层错误信息并转成 JSError抛出(L81); fail(reason, &out)统一用sass_make_error(reason)表达创建失败(L95-L98)。
以 src/sass_types/number.cpp 的Number::construct为例,它校验第一个参数必须是 JS number、第二个参数(单位)必须是 string,然后调用sass_make_number(value, unit);原型方法getValue/getUnit/setValue/setUnit(src/sass_types/number.cpp)则逐一转发到sass_number_get_value、sass_number_set_unit等 C API——这正是前文 API 表里那四个函数在真实项目中的用法。
Factory:按 tag 分派到正确的 C++ 子类
当 Sass 编译器执行自定义函数、需要把返回的Sass_Value呈现为 JS 对象时,src/sass_types/factory.cpp 的Factory::create就是分发中枢——它先调sass_value_get_tag,再按 9 种 tag 分派:
switch (sass_value_get_tag(v)) { case SASS_NUMBER: return new Number(v); case SASS_STRING: return new String(v); case SASS_COLOR: return new Color(v); case SASS_BOOLEAN: return &Boolean::get_singleton(sass_boolean_get_value(v)); case SASS_LIST: return new List(v); case SASS_MAP: return new Map(v); case SASS_NULL: return &Null::get_singleton(); case SASS_ERROR: return new Error(v); default: // 抛 TypeError 并包装 SASS_ERROR }两个值得注意的实现细节:
SASS_BOOLEAN与SASS_NULL走单例路径(Boolean::get_singleton/Null::get_singleton),因为布尔值只有 true/false 两种,null 只有一个,重复分配毫无意义;这一点由测试 test/types.js 固化:sass.types.Boolean(true)与sass.types.Boolean.TRUE严格是同一对象;- 模块初始化时(
Factory::initExports,src/sass_types/factory.cpp)把Number、String、Color、Boolean、List、Map、Null、Error八个构造器挂到sass.types命名空间下,test/types.js中assert.strictEqual(sass.types.Boolean.name, 'SassBoolean')等断言验证了构造器名与各类型一一对应。
从源码结构看,这一套“tag 判断 → 子类分派 → 克隆持有 → 析构递归释放”的模式,是任何使用Sass_ValueC API 的语言绑定都可以直接照抄的参考实现。
实践要点速查
结合以上源码证据,写绑定或使用自定义函数时可按下表自检:
| 操作 | API | 所有权/语义 |
|---|---|---|
| 创建标量值 | sass_make_*(src/libsass/src/sass_values.cpp) | 内部calloc,字符串入参深拷贝;失败返回NULL |
| 创建容器 | sass_make_list/sass_make_map | 只分配指针/对数组骨架,元素另置 |
| 释放 | sass_delete_value(src/libsass/src/sass_values.cpp) | 递归释放整棵树,每个根只删一次 |
| 跨边界移交 | sass_clone_value(src/libsass/src/sass_values.cpp) | 深克隆,保留字符串引号状态 |
| 类型判断 | sass_value_get_tag/sass_value_is_* | 基于偏移 0 的tag字段,必须先判断后访问 |
| 算术/比较 | sass_value_op(src/libsass/src/sass_values.cpp) | 异常统一转 error 值,返回前必查sass_value_is_error |
| 序列化 | sass_value_stringify | 返回 quoted 字符串型Sass_Value,用完需释放 |
延伸阅读
- src/libsass/docs/api-value.md:对外 C API 的正式文档(含
#include "sass/values.h"用法说明) - src/libsass/docs/api-value-internal.md:本文所基于的内部结构定义
- src/libsass/include/sass/values.h:对外头文件,函数声明权威来源
- src/libsass/src/sass_values.hpp / src/libsass/src/sass_values.cpp:内部结构体真实定义与全部 API 实现
- src/sass_types:node-sass 的
Sass_Value→ JS 对象绑定实现 - test/types.js:
sass.types.*构造器行为与单例语义的测试用例
- 前端
- 构建工具
【免费下载链接】node-sass
:rainbow: Node.js bindings to libsass
相关推荐
node-sass 与 libsass 的 Context API 内部结构剖析:从 C 结构体到编译器状态机
node sass 与 libsass 的 Context API 内部结构剖析:从 C 结构体到编译器状态机 本文以 libsass 的内部设计文档 api
前端构建工具node-sass 底层解析:LibSass C 上下文 API(Sass Context)全解
node sass 底层解析:LibSass C 上下文 API(Sass Context)全解 本文以 LibSass 的 C 上下文接口文档 api con
前端构建工具node-sass 中 libsass C API 的 Sass_Value 运算与序列化:从 api-value-example 拆解 sass_value_op 全链路
node sass 中 libsass C API 的 Sass_Value 运算与序列化:从 api value example 拆解 sass_value_
前端构建工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考