From a15641c42ec565264ec6ae030e9f4a50f81410b0 Mon Sep 17 00:00:00 2001 From: Lamdonn Date: Sat, 10 May 2025 21:28:42 +0800 Subject: [PATCH] Add the initial version yaml parser --- README.en.md | 3 +- README.md | 3 +- doc/yaml.en.md | 436 +++ doc/yaml.md | 432 +++ release.txt | 8 + source/05_parser/yaml.c | 7073 +++++++++++++++++++++++++++++++++++++++ source/05_parser/yaml.h | 278 ++ test/file/read.yaml | 108 + test/file/write.yaml | 11 + test/test.mk | 1 + test/test_yaml.c | 579 ++++ 11 files changed, 8930 insertions(+), 2 deletions(-) create mode 100644 doc/yaml.en.md create mode 100644 doc/yaml.md create mode 100644 source/05_parser/yaml.c create mode 100644 source/05_parser/yaml.h create mode 100644 test/file/read.yaml create mode 100644 test/file/write.yaml create mode 100644 test/test_yaml.c diff --git a/README.en.md b/README.en.md index 1996684..e2d1410 100644 --- a/README.en.md +++ b/README.en.md @@ -2,7 +2,7 @@ ![logo](/image/logo.png) -[![Version](https://img.shields.io/badge/version-0.3.3-blue.svg)](https://gitee.com/Lamdonn/varch) +[![Version](https://img.shields.io/badge/version-0.3.4-blue.svg)](https://gitee.com/Lamdonn/varch) [![License](https://img.shields.io/badge/license-GPL%202.0-brightgreen.svg)](LICENSE) [![Author](https://img.shields.io/badge/author-Lamdonn%20%20%20%20%20-brightblue.svg)](Lamdonn@163.com) ![Supported Platforms](https://img.shields.io/badge/platform-Linux%20&%20MinGW-yellow.svg) @@ -67,6 +67,7 @@ It has the characteristics of **simplicity, universality, and efficiency**, with | json | 01.00.00 | [link](/doc/json.en.md) | [path](./source/05_parser) | JSON file parsing generator | txls | 01.01.00 | [link](/doc/txls.en.md) | [path](./source/05_parser) | TXLS file parsing generator | xml | 01.00.00 | [link](/doc/xml.en.md) | [path](./source/05_parser) | XML file parsing generator +| yaml | 00.01.00 | [link](/doc/yaml.en.md) | [path](./source/05_parser) | YAML file parsing generator | ramt | 01.00.00 | [link](/doc/ramt.en.md) | [path](./source/06_performance) | RAM test module | romt | 01.00.00 | [link](/doc/romt.en.md) | [path](./source/06_performance) | ROM test module | cant | 00.01.00 | [link](/doc/cant.en.md) | [path](./source/06_performance) | CAN test module diff --git a/README.md b/README.md index 19508cc..23c9808 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![logo](/image/logo.png) -[![Version](https://img.shields.io/badge/version-0.3.3-blue.svg)](https://gitee.com/Lamdonn/varch) +[![Version](https://img.shields.io/badge/version-0.3.4-blue.svg)](https://gitee.com/Lamdonn/varch) [![License](https://img.shields.io/badge/license-GPL%202.0-brightgreen.svg)](LICENSE) [![Author](https://img.shields.io/badge/author-Lamdonn%20%20%20%20%20-brightblue.svg)](Lamdonn@163.com) ![Supported Platforms](https://img.shields.io/badge/platform-Linux%20&%20MinGW-yellow.svg) @@ -67,6 +67,7 @@ varch(we-architecture,意为我们的框架库)是嵌入式C语言常用 | json | 01.00.00 | [link](/doc/json.md) | [path](./source/05_parser) | JSON文件解析生成器 | txls | 01.01.00 | [link](/doc/txls.md) | [path](./source/05_parser) | TXLS文件解析生成器 | xml | 01.00.00 | [link](/doc/xml.md) | [path](./source/05_parser) | XML文件解析生成器 +| yaml | 00.01.00 | [link](/doc/yaml.md) | [path](./source/05_parser) | YAML文件解析生成器 | ramt | 01.00.00 | [link](/doc/ramt.md) | [path](./source/06_performance) | RAM测试模块 | romt | 01.00.00 | [link](/doc/romt.md) | [path](./source/06_performance) | ROM测试模块 | cant | 00.01.00 | [link](/doc/cant.md) | [path](./source/06_performance) | CAN测试模块 diff --git a/doc/yaml.en.md b/doc/yaml.en.md new file mode 100644 index 0000000..6ed5fde --- /dev/null +++ b/doc/yaml.en.md @@ -0,0 +1,436 @@ + +## Introduction + +### What is a YAML file? + +YAML (YAML Ain't Markup Language) is a **human-readable data serialization format** that focuses on concisely expressing structured data. It is widely used in configuration files, data exchange, and the description of complex data structures (such as the configurations of tools like Kubernetes and Ansible), rather than for storing traditional spreadsheet-like tabular data. + +### Characteristics of YAML files + +- **High readability**: + +It uses indentation and symbols (such as `-` and `:`) to represent hierarchical relationships, with a format similar to natural language, making it easier to read than JSON/XML. +```yaml +user: + name: Alice + age: 30 + hobbies: + - reading + - hiking +``` + +- **Supports complex data structures**: + +It can represent scalars (strings, numbers), lists, dictionaries, etc., and supports nesting and multi-line text (using `|` or `>`). + +- **Cross-platform compatibility**: + +It is a plain text format that can be parsed by all operating systems and programming languages. + +- **Seamless integration with programming languages**: + +Most languages (Python, Java, Go, etc.) provide native or third-party libraries (such as PyYAML) to support YAML parsing. + +### Uses of YAML + +1. **Configuration files** (core use) + +- Software configuration (such as Docker Compose, GitLab CI/CD) +- Cloud-native tool configuration (Kubernetes manifests) +```yaml +# Kubernetes Deployment example +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +``` + +2. **Data serialization** + +- Replacing JSON/XML to transfer complex data +- Structured data description for API requests/responses + +3. **Data exchange** + +- Transferring structured information between different systems (such as microservice configuration synchronization) + +### How to create and edit YAML files? + +1. **Text editors** + +- VS Code (it is recommended to install the YAML plugin for syntax highlighting and validation) +- Sublime Text / Vim, etc. + +2. **Specialized tools** + +- **Online YAML Validator**: Validates the syntax validity. +- **yq**: A command-line tool (similar to jq for processing YAML). + +### Precautions + +1. **Indentation sensitivity** + +- Spaces must be used (usually 2 or 4 spaces, and tabs are prohibited). +- Incorrect indentation will cause parsing to fail. + +2. **Key-value pair format** + +- A space is required after the colon: `key: value` (not `key:value`). + +3. **Special character handling** + +- When a string contains symbols such as `:` and `#`, it is recommended to use quotes: +```yaml +message: "Hello:World" +comment: "This is a # symbol" +``` + +4. **Multi-line text** + +- Preserve line breaks: `|` +```yaml +description: | + This is a + multi-line + text. +``` + +- Fold line breaks: `>` +```yaml +summary: > + This will fold + into a single line. +``` + +5. **Data type marking** + +- Force specifying the type: +```yaml +boolean: !!bool "true" +timestamp: !!timestamp 2023-07-20T15:30:00Z +``` + +### Common error examples +```yaml +# Error: Mixing spaces and tabs in indentation +user: + name: Bob + age: 25 # Inconsistent indentation + +# Error: Missing space in key-value pair +key1:value1 # Should be changed to key1: value1 + +# Error: Unescaped special characters +message: Line 1 + Line 2 # Missing multi-line text identifier +``` + +### C language version of the YAML library + +The YAML library provided by varch is simple and easy to use, capable of performing most basic operations on YAML files, including loading and saving YAML, as well as adding, deleting, modifying, and querying operations. + +## Interfaces + +### Creating and deleting YAML objects +```c +yaml_t yaml_create(void); +void yaml_delete(yaml_t yaml); +``` +Here, **yaml_t** is the YAML structure. The creation method generates an empty YAML object, and the deletion method deletes the specified YAML object. + +### Loading YAML objects +```c +yaml_t yaml_loads(const char* text, int flag); +yaml_t yaml_file_load(char* filename, int flag); +``` +YAML objects can be loaded from string text or from files. If the loading is successful, a YAML object will be returned; otherwise, NULL will be returned. +The flag is some flag for the operation function, defined as follows: +``` +#define YAML_F_NONE (0) +#define YAML_F_DFLOW (0x01) /* dumps flow format */ +#define YAML_F_LDOCS (0x02) /* load muti documents */ +#define YAML_F_NOKEY (0x04) /* operate without key */ +#define YAML_F_COMPLEX (0x08) /* operate with complex key */ +#define YAML_F_ANCHOR (0x10) /* operate with anchor */ +#define YAML_F_RECURSE (0x20) /* operate recurse */ +#define YAML_F_REFERENCE (0x40) /* operate with reference */ +``` + +When the YAML object fails to load, you can call the `int yaml_error_info(int* line, int* column);` function to locate the error. +The error types include: +``` +#define YAML_E_OK (0) /* ok, no error */ +#define YAML_E_INVALID (1) /* invalid, not a valid expected value */ +#define YAML_E_END (2) /* many invalid characters appear at the end */ +#define YAML_E_KEY (3) /* parsing key, invalid key content found */ +#define YAML_E_VALUE (4) /* parsing value, invalid value content found */ +#define YAML_E_MEMORY (5) /* memory allocation failed */ +#define YAML_E_SQUARE (6) /* mising ']' */ +#define YAML_E_CURLY (7) /* mising '}' */ +#define YAML_E_TAB (8) /* incorrect indent depth */ +#define YAML_E_MIX (9) /* mix type */ +#define YAML_E_FLINE (10) /* the first line of value can only be a literal */ +#define YAML_E_LNUMBER (11) /* the number exceeds the storage capacity */ +#define YAML_E_LBREAK (12) /* line break */ +#define YAML_E_NANCHOR (13) /* null anchor */ +#define YAML_E_IANCHOR (14) /* invalid anchor */ +#define YAML_E_RANCHOR (15) /* repeat anchor */ +#define YAML_E_UANCHOR (16) /* undefine anchor */ +#define YAML_E_TANCHOR (17) /* type error anchor */ +#define YAML_E_DATE (18) /* date error */ +#define YAML_E_TARTGET (19) /* date error */ +``` + +### Dumping YAML objects +```c +char* yaml_dumps(yaml_t yaml, int preset, int* len, int flag); +int yaml_file_dump(yaml_t yaml, char* filename); +``` +First, the **yaml_dumps** method dumps the YAML object into a string according to the format. `*len` is the length of the converted string. When `NULL` is passed, the length is not obtained. The return value is the converted string, which is allocated by the function and **needs to be `free`d after use**. +The **yaml_file_dump** method dumps the YAML object into a file based on the **yaml_dumps** method. `filename` is the file name, and the return value is the length of the dump. A negative value indicates a dump failure. + +### Adding child objects to a YAML object +```c +#define yaml_seq_add_null(yaml) +#define yaml_seq_add_int(yaml, num) +#define yaml_seq_add_float(yaml, num) +#define yaml_seq_add_string(yaml, string) +#define yaml_seq_add_sequence(yaml, sequence) +#define yaml_seq_add_mapping(yaml, mapping) +#define yaml_map_add_null(yaml, key) +#define yaml_map_add_int(yaml, key, num) +#define yaml_map_add_float(yaml, key, num) +#define yaml_map_add_string(yaml, key, string) +#define yaml_map_add_sequence(yaml, key, sequence) +#define yaml_map_add_mapping(yaml, key, mapping) +``` +The above methods add scalars to sequences and mappings respectively. In fact, they are implemented by applying `insert`-type methods: +```c +yaml_t yaml_insert_null(yaml_t yaml, const char* key, unsigned int index); +yaml_t yaml_insert_bool(yaml_t yaml, const char* key, unsigned int index, int b); +yaml_t yaml_insert_int(yaml_t yaml, const char* key, unsigned int index, int num); +yaml_t yaml_insert_float(yaml_t yaml, const char* key, unsigned int index, double num); +yaml_t yaml_insert_string(yaml_t yaml, const char* key, unsigned int index, const char* string); +yaml_t yaml_insert_sequence(yaml_t yaml, const char* key, unsigned int index, yaml_t sequence); +yaml_t yaml_insert_mapping(yaml_t yaml, const char* key, unsigned int index, yaml_t mapping); +yaml_t yaml_insert_document(yaml_t yaml, unsigned int index, yaml_t document); +yaml_t yaml_insert_reference(yaml_t yaml, const char* key, unsigned int index, const char* anchor, yaml_t doc); +``` +These `insert`-type methods are used to insert child objects of different types at specified positions in a `yaml` object. `key` is the key of the inserted object, and `index` is the insertion position. The return value is the `yaml` object after the insertion operation. If the insertion fails, `NULL` is returned. Specifically: +- `yaml_insert_null`: Inserts a null-type child object. +- `yaml_insert_bool`: Inserts a boolean-type child object, where `b` is the boolean value (`YAML_FALSE` or `YAML_TRUE`). +- `yaml_insert_int`: Inserts an integer-type child object, where `num` is the integer value. +- `yaml_insert_float`: Inserts a floating-point-type child object, where `num` is the floating-point value. +- `yaml_insert_string`: Inserts a string-type child object, where `string` is the string value. +- `yaml_insert_sequence`: Inserts a sequence-type child object, where `sequence` is the sequence object. +- `yaml_insert_mapping`: Inserts a mapping-type child object, where `mapping` is the mapping object. +- `yaml_insert_document`: Inserts a document-type child object, where `document` is the document object. +- `yaml_insert_reference`: Inserts a reference-type child object, where `anchor` is the reference anchor, and `doc` is the referenced document object. + +### Removing child objects from a YAML object +```c +int yaml_remove(yaml_t yaml, const char* key, unsigned int index); +``` +Removes the `index`-th child object with the specified key `key` from the `yaml` object. If the removal is successful, `YAML_E_OK` is returned; otherwise, the corresponding error code is returned. + +### YAML object attribute operations +```c +int yaml_type(yaml_t yaml); +unsigned int yaml_size(yaml_t yaml); +``` +- `yaml_type`: Gets the type of the `yaml` object. The return value is one of the `YAML_TYPE_*` series of macro definitions, used to determine whether the object is of type `NULL`, boolean, integer, floating-point, string, sequence, mapping, document, reference, or complex key. +- `yaml_size`: Gets the size of the `yaml` object. For sequence or mapping-type objects, it returns the number of elements; for other types of objects, the meaning of the return value may vary depending on the specific implementation. + +### YAML object comparison and copying +```c +int yaml_compare(yaml_t yaml, yaml_t cmp, int flag); +yaml_t yaml_copy(yaml_t yaml, int flag); +``` +- `yaml_compare`: Compares two `yaml` objects. `flag` is the comparison flag, used to specify the comparison method. The return value is the comparison result, and its specific meaning is determined by the implementation. Usually, 0 indicates equality, and non-0 indicates inequality. +- `yaml_copy`: Copies a `yaml` object. `flag` is the copy flag, used to specify the copy method. The return value is the copied `yaml` object. If the copy fails, `NULL` is returned. + +### YAML object key operations +```c +yaml_t yaml_set_key(yaml_t yaml, const char* key); +yaml_t yaml_set_key_complex(yaml_t yaml, yaml_t key); +const char* yaml_key(yaml_t yaml); +yaml_t yaml_key_complex(yaml_t yaml); +``` +- `yaml_set_key`: Sets a simple key for the `yaml` object, where `key` is the string value of the key. The return value is the `yaml` object after setting the key. +- `yaml_set_key_complex`: Sets a complex key for the `yaml` object, where `key` is the `yaml` object of the complex key. The return value is the `yaml` object after setting the key. +- `yaml_key`: Gets the simple key of the `yaml` object. The return value is the string value of the key. +- `yaml_key_complex`: Gets the complex key of the `yaml` object. The return value is the `yaml` object of the complex key. + +### YAML object value setting +```c +yaml_t yaml_set_null(yaml_t yaml); +yaml_t yaml_set_bool(yaml_t yaml, int b); +yaml_t yaml_set_int(yaml_t yaml, int num); +yaml_t yaml_set_float(yaml_t yaml, double num); +yaml_t yaml_set_string(yaml_t yaml, const char* string); +yaml_t yaml_set_date(yaml_t yaml, int year, char month, char day); +yaml_t yaml_set_time(yaml_t yaml, char hour, char minute, char second, int msec); +yaml_t yaml_set_utc(yaml_t yaml, char hour, char minute); +yaml_t yaml_set_sequence(yaml_t yaml, yaml_t sequence); +yaml_t yaml_set_mapping(yaml_t yaml, yaml_t mapping); +yaml_t yaml_set_document(yaml_t yaml, yaml_t document); +``` +These methods are used to set the value of a `yaml` object, and the return value is the `yaml` object after setting the value. Specifically: +- `yaml_set_null`: Sets the value of the `yaml` object to the null type. +- `yaml_set_bool`: Sets the value of the `yaml` object to the boolean type, where `b` is the boolean value (`YAML_FALSE` or `YAML_TRUE`). +- `yaml_set_int`: Sets the value of the `yaml` object to the integer type, where `num` is the integer value. +- `yaml_set_float`: Sets the value of the `yaml` object to the floating-point type, where `num` is the floating-point value. +- `yaml_set_string`: Sets the value of the `yaml` object to the string type, where `string` is the string value. +- `yaml_set_date`: Sets the value of the `yaml` object to the date type, where `year` is the year, `month` is the month, and `day` is the date. +- `yaml_set_time`: Sets the value of the `yaml` object to the time type, where `hour` is the hour, `minute` is the minute, `second` is the second, and `msec` is the millisecond. +- `yaml_set_utc`: Sets the value of the `yaml` object to the UTC time offset type, where `hour` is the hour offset and `minute` is the minute offset. +- `yaml_set_sequence`: Sets the value of the `yaml` object to the sequence type, where `sequence` is the sequence object. +- `yaml_set_mapping`: Sets the value of the `yaml` object to the mapping type, where `mapping` is the mapping object. +- `yaml_set_document`: Sets the value of the `yaml` object to the document type, where `document` is the document object. + +### YAML object value retrieval +```c +int yaml_value_bool(yaml_t yaml); +int yaml_value_int(yaml_t yaml); +double yaml_value_float(yaml_t yaml); +const char* yaml_value_string(yaml_t yaml); +yaml_t yaml_value_sequence(yaml_t yaml); +yaml_t yaml_value_mapping(yaml_t yaml); +yaml_t yaml_value_document(yaml_t yaml); +``` +These methods are used to retrieve the value of a `yaml` object, and the return value is the retrieved value. Specifically: +- `yaml_value_bool`: Retrieves the boolean value of the `yaml` object. +- `yaml_value_int`: Retrieves the integer value of the `yaml` object. +- `yaml_value_float`: Retrieves the floating-point value of the `yaml` object. +- `yaml_value_string`: Retrieves the string value of the `yaml` object. +- `yaml_value_sequence`: Retrieves the sequence value of the `yaml` object and returns the sequence object. +- `yaml_value_mapping`: Retrieves the mapping value of the `yaml` object and returns the mapping object. +- `yaml_value_document`: Retrieves the document value of the `yaml` object and returns the document object. + +### YAML object child element operations +```c +yaml_t yaml_attach(yaml_t yaml, unsigned int index, yaml_t attach); +yaml_t yaml_dettach(yaml_t yaml, unsigned int index); +``` +- `yaml_attach`: Attaches a `yaml` child object `attach` at the specified position `index` in the `yaml` object. The return value is the `yaml` object after the operation. If the operation fails, `NULL` is returned. +- `yaml_dettach`: Detaches a child object from the specified position `index` in the `yaml` object. The return value is the detached child object. If the operation fails, `NULL` is returned. + +### YAML object indexing and child object retrieval +```c +unsigned int yaml_get_index(yaml_t yaml, const char* key, unsigned int index); +unsigned int yaml_get_index_complex(yaml_t yaml, yaml_t key); +yaml_t yaml_get_child(yaml_t yaml, const char* key, unsigned int index); +yaml_t yaml_get_child_complex(yaml_t yaml, yaml_t key); +``` +- `yaml_get_index`: Gets the index of the `index`-th child object with the key `key` in the `yaml` object. The return value is the index of the child object. If not found, `YAML_INV_INDEX` is returned. +- `yaml_get_index_complex`: Gets the index of the child object with the complex key `key` in the `yaml` object. The return value is the index of the child object. If not found, `YAML_INV_INDEX` is returned. +- `yaml_get_child`: Gets the `index`-th child object with the key `key` in the `yaml` object. The return value is the `yaml` object of the child object. If not found, `NULL` is returned. +- `yaml_get_child_complex`: Gets the child object with the complex key `key` in the `yaml` object. The return value is the `yaml` object of the child object. If not found, `NULL` is returned. + +### YAML object anchor and alias operations +```c +const char* yaml_get_alias(yaml_t yaml); +yaml_t yaml_get_anchor(yaml_t yaml, unsigned int index); +yaml_t yaml_set_alias(yaml_t yaml, const char* alias, yaml_t doc); +yaml_t yaml_set_anchor(yaml_t yaml, const char* anchor, yaml_t doc); +unsigned int yaml_anchor_size(yaml_t yaml); +``` +- `yaml_get_alias`: Gets the alias of the `yaml` object. The return value is the string value of the alias. +- `yaml_get_anchor`: Gets the anchor object at the specified index `index` in the `yaml` object. The return value is the `yaml` object of the anchor. +- `yaml_set_alias`: Sets an alias for the `yaml` object, where `alias` is the string value of the alias and `doc` is the associated document object. The return value is the `yaml` object after setting the alias. +- `yaml_set_anchor`: Sets an anchor for the `yaml` object, where `anchor` is the string value of the anchor and `doc` is the associated document object. The return value is the `yaml` object after setting the anchor. +- `yaml_anchor_size`: Gets the number of anchors in the `yaml` object. The return value is the number of anchors. + +## Reference examples + +### Generating a YAML file +```c +static void test_dump(void) +{ + yaml_t root, node, temp; + + root = yaml_create(); + yaml_set_mapping(root, NULL); + + node = yaml_map_add_mapping(root, "mapping", NULL); + yaml_map_add_string(node, "version", "1.0.0"); + yaml_map_add_string(node, "author", "Lamdonn"); + yaml_map_add_string(node, "license", "GPL-2.0"); + + node = yaml_map_add_sequence(root, "sequence", NULL); + yaml_seq_add_string(node, "file description"); + yaml_seq_add_string(node, "This is a C language version of yaml streamlined parser"); + yaml_seq_add_string(node, "Copyright (C) 2023 Lamdonn."); + temp = yaml_seq_add_mapping(node, NULL); + yaml_map_add_string(temp, "age", "18"); + yaml_map_add_string(temp, "height", "178cm"); + yaml_map_add_string(temp, "weight", "75kg"); + + yaml_remove(temp, 0, 1); + + /* preview yaml */ + yaml_preview(root); + + /* dump yaml file */ + yaml_file_dump(root, WRITE_FILE); + + yaml_delete(root); +} +``` +The dumped file **write.yaml** +```yaml +mapping: + version: 1.0.0 + author: Lamdonn + license: GPL-2.0 +sequence: + - file description + - This is a C language version of yaml streamlined parser + - Copyright (C) 2023 Lamdonn. + - + age: 18 + weight: 75kg + +``` + +### Loading a YAML file + +Similarly, loading a YAML file +```c +static void test_load(void) +{ + yaml_t root = NULL, x = NULL; + + root = yaml_file_load(READ_FILE, YAML_F_LDOCS); + if (!root) + { + int type = 0, line = 0, column = 0; + type = yaml_error_info(&line, &column); + printf("error at line %d column %d type %d.\r\n", line, column, type); + return; + } + printf("load success!\r\n"); + + yaml_preview(root); + + yaml_delete(root); +} +``` +The running result: +``` +load success! +mapping: + version: 1.0.0 + author: Lamdonn + license: GPL-2.0 +sequence: + - file description + - This is a C language version of yaml streamlined parser + - Copyright (C) 2023 Lamdonn. + - + age: 18 + weight: 75kg + +``` \ No newline at end of file diff --git a/doc/yaml.md b/doc/yaml.md new file mode 100644 index 0000000..3baadf3 --- /dev/null +++ b/doc/yaml.md @@ -0,0 +1,432 @@ +## 介绍 + +### 什么是YAML文件? + +YAML(YAML Ain't Markup Language)是一种**人类可读的数据序列化格式**,专注于简洁表达结构化数据。它广泛用于配置文件、数据交换和复杂数据结构的描述(如Kubernetes、Ansible等工具的配置),而非传统电子表格类的表格数据存储。 + +### YAML文件的特点 + +- **可读性高**: + +使用缩进和符号(如`-`、`:`)表示层级关系,类似自然语言的格式,比JSON/XML更易阅读。 +```yaml +user: + name: Alice + age: 30 + hobbies: + - reading + - hiking +``` + +- **支持复杂数据结构**: + 可表示标量(字符串、数字)、列表、字典等类型,支持嵌套和多行文本(通过`|`或`>`)。 + +- **跨平台兼容**: + 纯文本格式,所有操作系统和编程语言均可解析。 + +- **与编程语言无缝集成**: + 大多数语言(Python、Java、Go等)提供原生或第三方库(如PyYAML)支持YAML解析。 + +### YAML的用途 + +1. **配置文件**(核心用途) + +- 软件配置(如Docker Compose、GitLab CI/CD) +- 云原生工具配置(Kubernetes manifests) +```yaml +# Kubernetes Deployment示例 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +``` + +2. **数据序列化** + +- 替代JSON/XML传输复杂数据 +- API请求/响应的结构化数据描述 + +3. **数据交换** + +- 不同系统间传递结构化信息(如微服务配置同步) + +### 如何创建和编辑YAML文件? + +1. **文本编辑器** + +- VS Code(推荐安装YAML插件实现语法高亮和校验) +- Sublime Text / Vim等 + +2. **专用工具** + +- **Online YAML Validator**:校验语法有效性 +- **yq**:命令行工具(类似jq,用于处理YAML) + +### 注意事项 + +1. **缩进敏感** + +- 必须使用空格(通常2或4空格,禁止使用Tab) +- 缩进错误会导致解析失败 + +2. **键值对格式** + +- 冒号后需有空格:`key: value`(而非`key:value`) + +3. **特殊字符处理** + +- 字符串包含`:`、`#`等符号时建议使用引号: +```yaml +message: "Hello:World" +comment: "This is a # symbol" +``` + +4. **多行文本** +- 保留换行符:`|` +```yaml +description: | + This is a + multi-line + text. +``` + +- 折叠换行符:`>` +```yaml +summary: > + This will fold + into a single line. +``` + +5. **数据类型标记** + +- 强制指定类型: +```yaml +boolean: !!bool "true" +timestamp: !!timestamp 2023-07-20T15:30:00Z +``` + +### 常见错误示例 +```yaml +# 错误:缩进混用空格和Tab +user: + name: Bob + age: 25 # 缩进不一致 + +# 错误:键值对缺少空格 +key1:value1 # 应改为 key1: value1 + +# 错误:未转义特殊字符 +message: Line 1 + Line 2 # 缺少多行文本标识符 +``` + +### C语言版YAML库 + +varch提供的YAML库,简便易用,能完成大部分对于YAML文件的基础操作,包含对yaml的加载和保存,增删改查等操作。 + +## 接口 + +### 创建和删除yaml对象 +```c +yaml_t yaml_create(void); +void yaml_delete(yaml_t yaml); +``` +其中**yaml_t**为yaml的结构体,创建方法会生成一个空yaml对象。删除方法则删除指定的yaml对象。 + +### yaml对象加载 +```c +yaml_t yaml_loads(const char* text, int flag); +yaml_t yaml_file_load(char* filename, int flag); +``` +yaml对象可以从字符串文本中加载,也可以从文件中加载。加载成功则会返回一个yaml对象,失败则返回NULL。 +flag为操作函数的一些flag,定义如下: +``` +#define YAML_F_NONE (0) +#define YAML_F_DFLOW (0x01) /* dumps flow format */ +#define YAML_F_LDOCS (0x02) /* load muti documents */ +#define YAML_F_NOKEY (0x04) /* operate without key */ +#define YAML_F_COMPLEX (0x08) /* operate with complex key */ +#define YAML_F_ANCHOR (0x10) /* operate with anchor */ +#define YAML_F_RECURSE (0x20) /* operate recurse */ +#define YAML_F_REFERENCE (0x40) /* operate with reference */ +``` + +当yaml对象加载失败的时候,可以调用`int yaml_error_info(int* line, int* column);`函数进行定位错误。 +错误类型包含 +``` +#define YAML_E_OK (0) /* ok, no error */ +#define YAML_E_INVALID (1) /* invalid, not a valid expected value */ +#define YAML_E_END (2) /* many invalid characters appear at the end */ +#define YAML_E_KEY (3) /* parsing key, invalid key content found */ +#define YAML_E_VALUE (4) /* parsing value, invalid value content found */ +#define YAML_E_MEMORY (5) /* memory allocation failed */ +#define YAML_E_SQUARE (6) /* mising ']' */ +#define YAML_E_CURLY (7) /* mising '}' */ +#define YAML_E_TAB (8) /* incorrect indent depth */ +#define YAML_E_MIX (9) /* mix type */ +#define YAML_E_FLINE (10) /* the first line of value can only be a literal */ +#define YAML_E_LNUMBER (11) /* the number exceeds the storage capacity */ +#define YAML_E_LBREAK (12) /* line break */ +#define YAML_E_NANCHOR (13) /* null anchor */ +#define YAML_E_IANCHOR (14) /* invalid anchor */ +#define YAML_E_RANCHOR (15) /* repeat anchor */ +#define YAML_E_UANCHOR (16) /* undefine anchor */ +#define YAML_E_TANCHOR (17) /* type error anchor */ +#define YAML_E_DATE (18) /* date error */ +#define YAML_E_TARTGET (19) /* date error */ +``` + +### yaml对象转储 +```c +char* yaml_dumps(yaml_t yaml, int preset, int* len, int flag); +int yaml_file_dump(yaml_t yaml, char* filename); +``` +首先**yaml_dumps**方法,将yaml对象按格式转储为字符串。`*len`则是转换出来的字符串长度,传入`NULL`时候就是不获取长度。返回值则是转换出来的字符串,这个字符串是函数分配的,**在结束使用需要`free`掉**。 +**yaml_file_dump**方法则是在**yaml_dumps**的基础上将yaml转储到文件当中,`filename`传入文件名,返回值为转储的长度,负值表示转储失败。 + +### yaml添加子对象 +```c +#define yaml_seq_add_null(yaml) +#define yaml_seq_add_int(yaml, num) +#define yaml_seq_add_float(yaml, num) +#define yaml_seq_add_string(yaml, string) +#define yaml_seq_add_sequence(yaml, sequence) +#define yaml_seq_add_mapping(yaml, mapping) +#define yaml_map_add_null(yaml, key) +#define yaml_map_add_int(yaml, key, num) +#define yaml_map_add_float(yaml, key, num) +#define yaml_map_add_string(yaml, key, string) +#define yaml_map_add_sequence(yaml, key, sequence) +#define yaml_map_add_mapping(yaml, key, mapping) +``` +以上方法分别在序列中和在映射中添加标量,其实就是套用 `insert` 类方法实现的 +```c +yaml_t yaml_insert_null(yaml_t yaml, const char* key, unsigned int index); +yaml_t yaml_insert_bool(yaml_t yaml, const char* key, unsigned int index, int b); +yaml_t yaml_insert_int(yaml_t yaml, const char* key, unsigned int index, int num); +yaml_t yaml_insert_float(yaml_t yaml, const char* key, unsigned int index, double num); +yaml_t yaml_insert_string(yaml_t yaml, const char* key, unsigned int index, const char* string); +yaml_t yaml_insert_sequence(yaml_t yaml, const char* key, unsigned int index, yaml_t sequence); +yaml_t yaml_insert_mapping(yaml_t yaml, const char* key, unsigned int index, yaml_t mapping); +yaml_t yaml_insert_document(yaml_t yaml, unsigned int index, yaml_t document); +yaml_t yaml_insert_reference(yaml_t yaml, const char* key, unsigned int index, const char* anchor, yaml_t doc); +``` +这些`insert`类方法用于在`yaml`对象的指定位置插入不同类型的子对象。`key`为插入对象的键,`index`为插入的位置。返回值为插入操作后的`yaml`对象,若插入失败则返回`NULL`。具体如下: +- `yaml_insert_null`:插入一个空类型的子对象。 +- `yaml_insert_bool`:插入一个布尔类型的子对象,`b`为布尔值(`YAML_FALSE` 或 `YAML_TRUE`)。 +- `yaml_insert_int`:插入一个整数类型的子对象,`num`为整数值。 +- `yaml_insert_float`:插入一个浮点数类型的子对象,`num`为浮点数值。 +- `yaml_insert_string`:插入一个字符串类型的子对象,`string`为字符串值。 +- `yaml_insert_sequence`:插入一个序列类型的子对象,`sequence`为序列对象。 +- `yaml_insert_mapping`:插入一个映射类型的子对象,`mapping`为映射对象。 +- `yaml_insert_document`:插入一个文档类型的子对象,`document`为文档对象。 +- `yaml_insert_reference`:插入一个引用类型的子对象,`anchor`为引用的锚点,`doc`为引用的文档对象。 + +### yaml移除子对象 +```c +int yaml_remove(yaml_t yaml, const char* key, unsigned int index); +``` +移除`yaml`对象中特定的键为`key`的第`index`个子对象。若移除成功,返回`YAML_E_OK`;若移除失败,返回相应的错误码。 + +### yaml对象属性操作 +```c +int yaml_type(yaml_t yaml); +unsigned int yaml_size(yaml_t yaml); +``` +- `yaml_type`:获取`yaml`对象的类型,返回值为`YAML_TYPE_*`系列宏定义的值,用于判断对象是`NULL`、布尔、整数、浮点数、字符串、序列、映射、文档、引用或复杂键等类型。 +- `yaml_size`:获取`yaml`对象的大小。对于序列或映射类型的对象,返回其元素的数量;对于其他类型的对象,返回值的含义可能因具体实现而异。 + +### yaml对象比较和复制 +```c +int yaml_compare(yaml_t yaml, yaml_t cmp, int flag); +yaml_t yaml_copy(yaml_t yaml, int flag); +``` +- `yaml_compare`:比较两个`yaml`对象。`flag`为比较的标志位,用于指定比较的方式。返回值为比较结果,具体含义由实现决定,通常 0 表示相等,非 0 表示不相等。 +- `yaml_copy`:复制一个`yaml`对象。`flag`为复制的标志位,用于指定复制的方式。返回值为复制后的`yaml`对象,若复制失败则返回`NULL`。 + +### yaml对象键操作 +```c +yaml_t yaml_set_key(yaml_t yaml, const char* key); +yaml_t yaml_set_key_complex(yaml_t yaml, yaml_t key); +const char* yaml_key(yaml_t yaml); +yaml_t yaml_key_complex(yaml_t yaml); +``` +- `yaml_set_key`:为`yaml`对象设置一个简单键,`key`为键的字符串值。返回值为设置键后的`yaml`对象。 +- `yaml_set_key_complex`:为`yaml`对象设置一个复杂键,`key`为复杂键的`yaml`对象。返回值为设置键后的`yaml`对象。 +- `yaml_key`:获取`yaml`对象的简单键,返回值为键的字符串值。 +- `yaml_key_complex`:获取`yaml`对象的复杂键,返回值为复杂键的`yaml`对象。 + +### yaml对象值设置 +```c +yaml_t yaml_set_null(yaml_t yaml); +yaml_t yaml_set_bool(yaml_t yaml, int b); +yaml_t yaml_set_int(yaml_t yaml, int num); +yaml_t yaml_set_float(yaml_t yaml, double num); +yaml_t yaml_set_string(yaml_t yaml, const char* string); +yaml_t yaml_set_date(yaml_t yaml, int year, char month, char day); +yaml_t yaml_set_time(yaml_t yaml, char hour, char minute, char second, int msec); +yaml_t yaml_set_utc(yaml_t yaml, char hour, char minute); +yaml_t yaml_set_sequence(yaml_t yaml, yaml_t sequence); +yaml_t yaml_set_mapping(yaml_t yaml, yaml_t mapping); +yaml_t yaml_set_document(yaml_t yaml, yaml_t document); +``` +这些方法用于设置`yaml`对象的值,返回值为设置值后的`yaml`对象。具体如下: +- `yaml_set_null`:将`yaml`对象的值设置为空类型。 +- `yaml_set_bool`:将`yaml`对象的值设置为布尔类型,`b`为布尔值(`YAML_FALSE` 或 `YAML_TRUE`)。 +- `yaml_set_int`:将`yaml`对象的值设置为整数类型,`num`为整数值。 +- `yaml_set_float`:将`yaml`对象的值设置为浮点数类型,`num`为浮点数值。 +- `yaml_set_string`:将`yaml`对象的值设置为字符串类型,`string`为字符串值。 +- `yaml_set_date`:将`yaml`对象的值设置为日期类型,`year`为年份,`month`为月份,`day`为日期。 +- `yaml_set_time`:将`yaml`对象的值设置为时间类型,`hour`为小时,`minute`为分钟,`second`为秒,`msec`为毫秒。 +- `yaml_set_utc`:将`yaml`对象的值设置为 UTC 时间偏移类型,`hour`为小时偏移,`minute`为分钟偏移。 +- `yaml_set_sequence`:将`yaml`对象的值设置为序列类型,`sequence`为序列对象。 +- `yaml_set_mapping`:将`yaml`对象的值设置为映射类型,`mapping`为映射对象。 +- `yaml_set_document`:将`yaml`对象的值设置为文档类型,`document`为文档对象。 + +### yaml对象值获取 +```c +int yaml_value_bool(yaml_t yaml); +int yaml_value_int(yaml_t yaml); +double yaml_value_float(yaml_t yaml); +const char* yaml_value_string(yaml_t yaml); +yaml_t yaml_value_sequence(yaml_t yaml); +yaml_t yaml_value_mapping(yaml_t yaml); +yaml_t yaml_value_document(yaml_t yaml); +``` +这些方法用于获取`yaml`对象的值,返回值为获取到的值。具体如下: +- `yaml_value_bool`:获取`yaml`对象的布尔值。 +- `yaml_value_int`:获取`yaml`对象的整数值。 +- `yaml_value_float`:获取`yaml`对象的浮点数值。 +- `yaml_value_string`:获取`yaml`对象的字符串值。 +- `yaml_value_sequence`:获取`yaml`对象的序列值,返回序列对象。 +- `yaml_value_mapping`:获取`yaml`对象的映射值,返回映射对象。 +- `yaml_value_document`:获取`yaml`对象的文档值,返回文档对象。 + +### yaml对象子元素操作 +```c +yaml_t yaml_attach(yaml_t yaml, unsigned int index, yaml_t attach); +yaml_t yaml_dettach(yaml_t yaml, unsigned int index); +``` +- `yaml_attach`:在`yaml`对象的指定位置`index`附加一个`yaml`子对象`attach`。返回值为操作后的`yaml`对象,若操作失败则返回`NULL`。 +- `yaml_dettach`:从`yaml`对象的指定位置`index`分离一个子对象。返回值为分离出的子对象,若操作失败则返回`NULL`。 + +### yaml对象索引和子对象获取 +```c +unsigned int yaml_get_index(yaml_t yaml, const char* key, unsigned int index); +unsigned int yaml_get_index_complex(yaml_t yaml, yaml_t key); +yaml_t yaml_get_child(yaml_t yaml, const char* key, unsigned int index); +yaml_t yaml_get_child_complex(yaml_t yaml, yaml_t key); +``` +- `yaml_get_index`:获取`yaml`对象中键为`key`的第`index`个子对象的索引。返回值为子对象的索引,若未找到则返回`YAML_INV_INDEX`。 +- `yaml_get_index_complex`:获取`yaml`对象中复杂键为`key`的子对象的索引。返回值为子对象的索引,若未找到则返回`YAML_INV_INDEX`。 +- `yaml_get_child`:获取`yaml`对象中键为`key`的第`index`个子对象。返回值为子对象的`yaml`对象,若未找到则返回`NULL`。 +- `yaml_get_child_complex`:获取`yaml`对象中复杂键为`key`的子对象。返回值为子对象的`yaml`对象,若未找到则返回`NULL`。 + +### yaml对象锚点和别名操作 +```c +const char* yaml_get_alias(yaml_t yaml); +yaml_t yaml_get_anchor(yaml_t yaml, unsigned int index); +yaml_t yaml_set_alias(yaml_t yaml, const char* alias, yaml_t doc); +yaml_t yaml_set_anchor(yaml_t yaml, const char* anchor, yaml_t doc); +unsigned int yaml_anchor_size(yaml_t yaml); +``` +- `yaml_get_alias`:获取`yaml`对象的别名,返回值为别名的字符串值。 +- `yaml_get_anchor`:获取`yaml`对象中指定索引`index`的锚点对象,返回值为锚点的`yaml`对象。 +- `yaml_set_alias`:为`yaml`对象设置别名,`alias`为别名的字符串值,`doc`为关联的文档对象。返回值为设置别名后的`yaml`对象。 +- `yaml_set_anchor`:为`yaml`对象设置锚点,`anchor`为锚点的字符串值,`doc`为关联的文档对象。返回值为设置锚点后的`yaml`对象。 +- `yaml_anchor_size`:获取`yaml`对象中锚点的数量。返回值为锚点的数量。 + +## 参考例子 + +### 生成yaml文件 +```c +static void test_dump(void) +{ + yaml_t root, node, temp; + + root = yaml_create(); + yaml_set_mapping(root, NULL); + + node = yaml_map_add_mapping(root, "mapping", NULL); + yaml_map_add_string(node, "version", "1.0.0"); + yaml_map_add_string(node, "author", "Lamdonn"); + yaml_map_add_string(node, "license", "GPL-2.0"); + + node = yaml_map_add_sequence(root, "sequence", NULL); + yaml_seq_add_string(node, "file description"); + yaml_seq_add_string(node, "This is a C language version of yaml streamlined parser"); + yaml_seq_add_string(node, "Copyright (C) 2023 Lamdonn."); + temp = yaml_seq_add_mapping(node, NULL); + yaml_map_add_string(temp, "age", "18"); + yaml_map_add_string(temp, "height", "178cm"); + yaml_map_add_string(temp, "weight", "75kg"); + + yaml_remove(temp, 0, 1); + + /* preview yaml */ + yaml_preview(root); + + /* dump yaml file */ + yaml_file_dump(root, WRITE_FILE); + + yaml_delete(root); +} +``` +转储的文件 **write.yaml** +```yaml +mapping: + version: 1.0.0 + author: Lamdonn + license: GPL-2.0 +sequence: + - file description + - This is a C language version of yaml streamlined parser + - Copyright (C) 2023 Lamdonn. + - + age: 18 + weight: 75kg + +``` + +### 加载yaml文件 + +同样加载yaml文件 + +```c +static void test_load(void) +{ + yaml_t root = NULL, x = NULL; + + root = yaml_file_load(READ_FILE, YAML_F_LDOCS); + if (!root) + { + int type = 0, line = 0, column = 0; + type = yaml_error_info(&line, &column); + printf("error at line %d column %d type %d.\r\n", line, column, type); + return; + } + printf("load success!\r\n"); + + yaml_preview(root); + + yaml_delete(root); +} +``` +运行结果: +``` +load success! +mapping: + version: 1.0.0 + author: Lamdonn + license: GPL-2.0 +sequence: + - file description + - This is a C language version of yaml streamlined parser + - Copyright (C) 2023 Lamdonn. + - + age: 18 + weight: 75kg + +``` diff --git a/release.txt b/release.txt index 6613a95..a9acce0 100644 --- a/release.txt +++ b/release.txt @@ -1,3 +1,11 @@ +version 0.3.4 +date 2025.05.10 +changes + 1. Add the initial version yaml parser + +--------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------- + version 0.3.3 date 2025.03.18 changes diff --git a/source/05_parser/yaml.c b/source/05_parser/yaml.c new file mode 100644 index 0000000..4f1fad1 --- /dev/null +++ b/source/05_parser/yaml.c @@ -0,0 +1,7073 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file yaml.c + * \unit yaml + * \brief This is a C language version of yaml streamlined parser + * \author Lamdonn + * \version v0.1.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2025 Lamdonn. + ********************************************************************************************************/ +#include "yaml.h" +#include +#include +#include +#include +#include + +// #define DEBUG printf +#define DEBUG(...) + +/** + * \brief YAML key type identifiers + * These constants define the type of keys used in YAML nodes. + * The key type determines how the key data is interpreted and stored. + */ +#define YAML_KTYPE_STRING (0) /* String key - standard scalar string */ +#define YAML_KTYPE_ANCHOR (1) /* Anchor list key - used for document-level anchors */ +#define YAML_KTYPE_COMPLEX (2) /* Complex key - multi-line or structured key */ + +/** + * \brief Date/time format flags + * These flags control the parsing and formatting of date/time values. + * They can be combined using bitwise OR to specify multiple options. + */ +#define DATEF_TIME (0x01) /* Include time component (HH:MM:SS) */ +#define DATEF_MSEC (0x02) /* Include milliseconds component */ +#define DATEF_UTC (0x04) /* Use UTC timezone */ + +/** + * \brief Iterator structure for traversal state tracking + */ +typedef struct +{ + void *p; /**< [in] Iterator pointer for tracking traversal position */ + unsigned int i; /**< [in] Current iteration index */ +} ITERATOR; + +/** + * \brief Anchor structure for array storage + */ +typedef struct +{ + yaml_t *array; /**< [in] Array address (dynamic array of YAML nodes) */ + unsigned int size; /**< [in] Number of elements in array */ + unsigned int capacity; /**< [in] Total array capacity */ +} ANCHOR; + +/** + * \brief Compact date/time representation using bit fields + * + * Implements ISO 8601 date/time storage with UTC offset information + */ +typedef struct +{ + unsigned int year : 12; /**< [in] Year value (0-4095) */ + unsigned int month : 4 ; /**< [in] Month (1-11) */ + unsigned int day : 6 ; /**< [in] Day (1-31) */ + unsigned int msec : 10; /**< [in] Millisecond (1-999) */ + unsigned int hour : 5 ; /**< [in] Hour (1-23) */ + unsigned int minute : 6 ; /**< [in] Minute (1-59) */ + unsigned int second : 6 ; /**< [in] Second (1-59) */ + unsigned int utchour : 5 ; /**< [in] UTC hour offset (1-23) */ + unsigned int utcminute : 6 ; /**< [in] UTC minute offset (1-59) */ + unsigned int utcsign : 1 ; /**< [in] UTC sign (0 = positive/+, 1 = negative/-) */ + unsigned int flag : 3 ; /**< [in] Date flags (DATEF_XXX values) */ +} DATE; + +/** + * \brief Fundamental YAML node structure supporting multiple data types + * + * Represents YAML nodes with support for complex structures (sequences/mappings) + * and scalar values through type unions. + */ +typedef struct YAML { + /** \brief [in] Next node pointer for maintaining insertion order */ + struct YAML *next; + + /** \brief [in] Iterator for traversal state management */ + ITERATOR iterator; + + /** \brief [in] Alias name (for YAML anchors) */ + char* alias; + + /** \brief [in] Key type identifier (see YAML data type constants) */ + char ktype; + + /** \brief [in] Value type identifier (see YAML data type constants) */ + char vtype; + + /** \brief Key union */ + union { + char* key; /**< [in] Simple key (string) */ + ANCHOR *anchor; /**< [in] Anchor reference (for aliases) */ + struct YAML *complex; /**< [in] Complex key (nested YAML node) */ + }; + + /** \brief Value union */ + union { + /* Scalar values */ + int bool_; /**< [in] Boolean value (true/false, yes/no) */ + double float_; /**< [in] Floating-point number */ + int int_; /**< [in] Integer value */ + char* string_; /**< [in] String value */ + DATE date; /**< [in] ISO 8601 date/time value */ + + /* Complex structures */ + struct YAML* child_; /**< [in] Child node (sequence/mapping/document) */ + } value; + + /** \brief [in] Size indicator (elements count or string length) */ + unsigned int size; +} YAML; + +/** + * \brief Pointer to the start of the current line being processed + */ +static const char* lbegin = 0; + +/** + * \brief Line number where the error occurred + */ +static int eline = 0; + +/** + * \brief Column number (offset from lbegin) where the error occurred + */ +static int ecolumn = 0; + +/** + * \brief Error type code (user-defined error categories) + */ +static int etype = 0; + +/** + * \brief Tracks nested bracket levels ({/[ counts) + */ +static int rbrace = 0; + +/** + * \brief Error handling macro that captures error context and returns + * \param[in] type Error type code (user-defined) + * \param[in] s Value to return from the function + * \return Returns parameter 's' immediately + * \note Sets error location (ecolumn), error type, and includes debug print + * \warning The printf is temporary debug output (remove in production) + */ +#define E(type, s) do { \ + etype = (type); /* Set error type */ \ + ecolumn = text - lbegin; /* Calculate column offset */ \ + printf("at[%d]\r\n", __LINE__); /* Debug: show error line */ \ + return (s); /* Exit function */ \ +} while (0) + +static yaml_t yaml_add_anchor(ANCHOR *anchor, yaml_t yaml); +static yaml_t yaml_match_anchor(ANCHOR *anchor, const char *base, unsigned int length); + +static yaml_t yaml_duplicate(yaml_t yaml, int flag, void *para); + +/** + * \brief Retrieves YAML parsing error information + * \param[out] line Pointer to store error line number (nullable) + * \param[out] column Pointer to store error column number (nullable) + * \return Error type code (YAML_E_OK indicates no error) + * + * This function retrieves the YAML parsing error information. If there is no error (etype is YAML_E_OK), + * it immediately returns YAML_E_OK. Otherwise, it populates the provided pointers (if they are not NULL) + * with the error line number and error column number. Finally, it returns the error type code (etype). + * The function follows a pattern similar to libyaml's error reporting mechanism, where it provides + * location details of the error (line and column numbers) along with the error type. + * + * Memory management: + * - This function does not allocate or free memory. It only manipulates the provided pointers + * (if they are not NULL) and returns the error type code. + * + * Example usage: + * \code + * int error_line, error_column; + * int error_type = yaml_error_info(&error_line, &error_column); + * if (error_type != YAML_E_OK) { + * printf("Error type: %d, Error line: %d, Error column: %d\n", error_type, error_line, error_column); + * } else { + * printf("No error occurred.\n"); + * } + * \endcode + */ +int yaml_error_info(int* line, int* column) +{ + /* Early return when no error occurred */ + if (etype == YAML_E_OK) return YAML_E_OK; + + /* Populate error location through output parameters */ + if (line) *line = eline; // Line number assignment + if (column) *column = ecolumn; // Column number assignment + + return etype; // Return final error type +} + +/** + * \brief Computes the smallest power of two greater than or equal to input + * \param[in] x Positive integer input value + * \return Nearest power of two meeting condition + * + * Implements bitwise optimization for capacity calculation, + * commonly used in dynamic array resizing strategies:ml-citation{ref="7" data="citationList"}. + */ +static int pow2gt(int x) +{ + int b = sizeof(int) * 8; // Bit count for integer type + int i = 1; + --x; // Adjust input for boundary cases + + /* Bitwise propagation algorithm */ + while (i < b) { + x |= (x >> i); + i <<= 1; // Double shift amount each iteration + } + return x + 1; // Final power-of-two adjustment +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// Date Calculation Utilities +////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * \brief Determines if a given year is a leap year + * \param[in] year Year to check (must be > 0) + * \return 1 if leap year, 0 otherwise + * \note Follows Gregorian calendar rules (valid for years > 1582) + */ +static int date_isleap(int year) +{ + return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)); +} + +/** + * \brief Calculates total days in a century period + * \param[in] century Century number (1 = years 1-100) + * \return Total days in century (36524/36525) or 0 for century 0 + * \note Implements 400-year cycle of Gregorian calendar + */ +static unsigned int date_century_days(int century) +{ + if (century == 0) return 0u; // No days in century 0 + return (century % 4 == 0) ? 36525u : 36524u; // 25 leap years in 400 years +} + +/** + * \brief Calculates days in a specific year + * \param[in] year Target year (must be > 0) + * \return 365 or 366 (leap year) or 0 for year 0 + */ +static unsigned int date_year_days(int year) +{ + if (year == 0) return 0u; // No days in year 0 + return date_isleap(year) ? 366u : 365u; +} + +/** + * \brief Gets number of days in a month + * \param[in] year Year for leap year calculation (must be > 0) + * \param[in] month Target month (1-12) + * \return Days in month (28-31), 0 for invalid input + * \warning Returns 0 for year 0 or invalid month + */ +static unsigned int date_month_days(int year, char month) +{ + if (year == 0) return 0; // No valid month days for year 0 + switch (month) + { + case 1: case 3: case 5: case 7: case 8: case 10: case 12: + return 31; // Months with 31 days + case 4: case 6: case 9: case 11: + return 30; // Months with 30 days + case 2: + return date_isleap(year) ? 29 : 28; // February days depending on leap year + default: + return 0; // Invalid month + } +} + +/** + * \brief Validates date components + * \param[in] year Year component (must be > 0) + * \param[in] month Month component (1-12) + * \param[in] day Day component + * \return 1 if valid date, 0 otherwise + * \note Checks component ranges and month/day compatibility + */ +static int date_isvalid(int year, char month, char day) +{ + if (year <= 0 || month <= 0 || day <= 0) return 0; // Year, month or day 0 is not valid + return (date_month_days(year, month) >= day); +} + +/** + * \brief Creates a null-terminated copy of a string segment + * \param[in] str Source string buffer + * \param[in] len Length to copy (excluding null-terminator) + * \return Newly allocated string copy, NULL on allocation failure + * \note Safer alternative to strndup() with explicit length handling + */ +static char* yaml_strdup(const char* str, int len) +{ + char* s; + + /* Allocate memory for the new string */ + s = (char*)malloc(len + 1); + if (!s) return NULL; + + /* Copy the given string into the allocated memory */ + memcpy(s, str, len); + s[len] = 0; + + return s; +} + +/** + * \brief Initializes a new YAML document structure + * \return Opaque YAML handle, NULL on allocation failure + * \note Creator must eventually call yaml_delete() to free resources + * + * This function allocates and initializes a new YAML document structure. + * It dynamically allocates memory for the YAML structure and zero-initializes + * all its fields to ensure a clean state. The returned handle is opaque and + * should be treated as an interface to the underlying YAML data structure. + * + * Memory management: + * - Allocates memory using malloc(sizeof(YAML)) + * - Zero-initializes the allocated memory with memset + * - Returns NULL if memory allocation fails + * - Caller is responsible for freeing the resource with yaml_delete() + * + * Example usage: + * \code + * yaml_t doc = yaml_create(); + * if (doc) { + * // Successfully created YAML document handle + * // Proceed to populate or parse YAML content + * yaml_delete(doc); // Free resources when done + * } else { + * // Handle memory allocation failure + * } + * \endcode + */ +yaml_t yaml_create(void) +{ + yaml_t yaml; + + /* Allocate yaml structure space and initialize */ + yaml = (yaml_t)malloc(sizeof(YAML)); + if (yaml) memset(yaml, 0, sizeof(YAML)); // Zero-initialize structure + + return yaml; +} + +/** + * \brief Recursively destroys YAML document structure + * \param[in] yaml Root node of document to destroy + * \warning Handles complex nested structures and reference types safely + * + * This function recursively destroys a YAML document structure, freeing all allocated memory. + * It processes the YAML tree in a depth-first manner, ensuring all child nodes and associated + * resources are properly cleaned up before freeing the parent node. The destruction process + * includes handling different value types (documents, sequences, mappings, strings) and key + * storage strategies (direct strings, anchor references, complex substructures). + * + * Destruction process: + * 1. Iterates through sibling nodes using the 'next' pointer + * 2. Recursively deletes child nodes for container types (document, sequence, mapping) + * 3. Frees memory allocated for string values + * 4. Cleans up different key types: + * - Direct strings: Frees the key string + * - Anchor references: Frees the anchor table and associated array + * - Complex substructures: Recursively deletes the complex key structure + * 5. Frees alias strings if present + * 6. Finally, frees the YAML node itself + * + * Memory management: + * - Frees memory allocated for string values, keys, anchors, aliases, and nodes. + * - Ensures no memory leaks by processing all nested and sibling nodes. + * - Safe to call with NULL pointer (function does nothing in that case). + * + * Example usage: + * \code + * yaml_t root = yaml_create(); + * // Populate root with YAML content... + * yaml_delete(root); // Recursively free all resources + * \endcode + */ +void yaml_delete(yaml_t yaml) +{ + yaml_t next; + + while (yaml) + { + next = yaml->next; // Preserve sibling link before deletion + + // Handle container type recursion + if (yaml->vtype == YAML_TYPE_DOCUMENT || + yaml->vtype == YAML_TYPE_SEQUENCE || + yaml->vtype == YAML_TYPE_MAPPING) + { + yaml_delete(yaml->value.child_); + } + // Handle string value cleanup + else if (yaml->vtype == YAML_TYPE_STRING) + { + free(yaml->value.string_); + } + + /* Free the key of yaml */ + if (yaml->key) + { + if (yaml->ktype == YAML_KTYPE_STRING) + { + free(yaml->key); + } + else if (yaml->ktype == YAML_KTYPE_ANCHOR) + { + if (yaml->anchor) + { + if (yaml->anchor->array) free(yaml->anchor->array); + free(yaml->anchor); + } + } + else if (yaml->ktype == YAML_KTYPE_COMPLEX) + { + yaml_delete(yaml->complex); + } + } + + if (yaml->alias) free(yaml->alias); // Alias string cleanup + + /* Delete self */ + free(yaml); + + yaml = next; // Process siblings + } +} + +/** + * \brief Retrieves node type identifier + * \param[in] yaml Node to inspect + * \return YAML_TYPE_* constant or -1 for invalid input + * + * This function returns the type identifier of a YAML node. It checks if the provided + * YAML node pointer is valid (not NULL). If valid, it returns the node's value type + * (vtype), which is one of the YAML_TYPE_* constants defined in the YAML parser. + * If the input pointer is NULL, it returns -1 to indicate an invalid input. + * + * Memory management: + * - This function does not allocate or free memory. It only reads the vtype field + * of the provided YAML node structure. + * + * Example usage: + * \code + * yaml_t my_node = yaml_create(); + * // Assume my_node is properly initialized with a value type + * int node_type = yaml_type(my_node); + * if (node_type != -1) { + * printf("Node type: %d\n", node_type); + * } else { + * printf("Invalid YAML node pointer\n"); + * } + * \endcode + */ +int yaml_type(yaml_t yaml) +{ + if (!yaml) return -1; + return yaml->vtype; +} + +/** + * \brief Gets child count for container nodes + * \param[in] yaml Container node to inspect + * \return Number of child elements, 0 for non-containers/invalid input + * + * This function returns the number of child elements contained within a YAML container node. + * Valid container types include sequences (YAML_TYPE_SEQUENCE) and mappings (YAML_TYPE_MAPPING). + * For non-container types (e.g., strings, scalars) or invalid input (NULL pointer), it returns 0. + * + * Memory management: + * - This function does not allocate or free memory. It only reads the 'size' field of the YAML node. + * + * Example usage: + * \code + * yaml_t sequence_node = yaml_create(); + * // Assume sequence_node is initialized as a sequence + * unsigned int child_count = yaml_size(sequence_node); + * printf("Sequence contains %u elements\n", child_count); + * \endcode + */ +unsigned int yaml_size(yaml_t yaml) +{ + if (!yaml) return 0; + return yaml->size; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// YAML Anchor and Key Operations +////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * \brief Get the number of references to a YAML anchor + * \param[in] yaml: Pointer to YAML node with potential anchor + * \return Number of references if node is an anchor, 0 otherwise + * + * This function checks if the provided YAML node is an anchor + * and returns the number of times it has been referenced via aliases. + * Returns 0 if the node is NULL or not an anchor type. + * + * Memory management: + * - This function does not allocate or free memory. It only reads the 'ktype' + * and 'anchor->size' fields of the YAML node structure. + * + * Example usage: + * \code + * yaml_t anchor_node = yaml_create(); + * // Assume anchor_node is properly initialized as an anchor + * unsigned int ref_count = yaml_anchor_size(anchor_node); + * printf("Anchor has %u references\n", ref_count); + * \endcode + */ +unsigned int yaml_anchor_size(yaml_t yaml) +{ + // Return 0 if input node is NULL + if (!yaml) return 0; + + // Check if node is actually an anchor type + if (yaml->ktype != YAML_KTYPE_ANCHOR) return 0; + + // Return the count of references to this anchor + return yaml->anchor->size; +} + +/** + * \brief Accesses string-type key value + * \param[in] yaml YAML node handle + * \return Pointer to key string, NULL for invalid inputs + * \warning Only valid when ktype == YAML_KTYPE_STRING + * + * This function retrieves the string key from a YAML node if it is + * of type YAML_KTYPE_STRING. Returns NULL if the input is NULL or + * the node does not have a string key. + * + * Example usage: + * \code + * yaml_t node = ...; + * const char* key = yaml_key(node); + * if (key) { + * printf("Node key: %s\n", key); + * } + * \endcode + */ +const char* yaml_key(yaml_t yaml) +{ + // Validate input is not NULL + if (!yaml) return NULL; + + // Check if node has a string-type key + if (yaml->ktype != YAML_KTYPE_STRING) return NULL; + + // Return the string key + return yaml->key; +} + +/** + * \brief Accesses anchor array elements + * \param[in] yaml Anchor-type YAML node + * \param[in] index Array index (0-based) + * \return Child YAML node at specified index, NULL on error + * \note Implements anchor reference resolution mechanism + * + * This function retrieves a child node from an anchor's array of references. + * It ensures the input is a valid anchor node and checks bounds before accessing + * the array. Returns NULL if: + * - Input node is NULL + * - Node is not an anchor type (YAML_KTYPE_ANCHOR) + * - Anchor has no referenced nodes (empty array) + * - Index exceeds the number of referenced nodes + * + * Example usage: + * \code + * yaml_t anchor_node = ...; // Assume this is a valid anchor + * for (unsigned int i = 0; i < yaml_anchor_size(anchor_node); i++) { + * yaml_t child = yaml_get_anchor(anchor_node, i); + * if (child) { + * // Process child node + * } + * } + * \endcode + */ +yaml_t yaml_get_anchor(yaml_t yaml, unsigned int index) +{ + ANCHOR *anchor = NULL; + + // Validate input node is not NULL and is an anchor type + if (!yaml) return NULL; + if (yaml->ktype != YAML_KTYPE_ANCHOR) return NULL; + + // Get the anchor structure from the node + anchor = yaml->anchor; + + // Check if anchor has references and index is within bounds + if (!anchor->array || anchor->size == 0) return NULL; + if (index >= anchor->size) return NULL; + + // Return the referenced node at specified index + return anchor->array[index]; +} + +/** + * \brief Accesses complex key substructure + * \param[in] yaml YAML node handle + * \return Pointer to complex key structure, NULL on error + * \note Used for hierarchical key configurations + * + * This function retrieves the complex key substructure from a YAML node + * if it is of type YAML_KTYPE_COMPLEX. Complex keys are used for + * hierarchical or multi-part key configurations, allowing nested structures + * as keys. Returns NULL if: + * - Input node is NULL + * - Node does not contain a complex key + * + * Example usage: + * \code + * yaml_t node = ...; // Assume this node has a complex key + * yaml_t complex_key = yaml_key_complex(node); + * if (complex_key) { + * // Process complex key structure + * } + * \endcode + */ +yaml_t yaml_key_complex(yaml_t yaml) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + // Check if node contains a complex key + if (yaml->ktype != YAML_KTYPE_COMPLEX) return NULL; + + // Return pointer to complex key structure + return yaml->complex; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// YAML Value Accessors +////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * \brief Retrieves boolean value from YAML node + * \param[in] yaml YAML node handle + * \return 1 for true, 0 for false/invalid node + * \note Valid only for YAML_TYPE_BOOL nodes + * + * This function extracts the boolean value from a YAML node if it is + * of type YAML_TYPE_BOOL. Returns 0 (false) if the input is invalid + * or the node does not contain a boolean value. + * + * Example usage: + * \code + * yaml_t node = ...; // Assume this node might contain a boolean + * if (yaml_type(node) == YAML_TYPE_BOOL) { + * int is_true = yaml_value_bool(node); + * printf("Boolean value: %s\n", is_true ? "true" : "false"); + * } + * \endcode + */ +int yaml_value_bool(yaml_t yaml) +{ + // Validate input node is not NULL and is a boolean type + if (!yaml) return 0; + if (yaml->vtype != YAML_TYPE_BOOL) return 0; + + // Return the boolean value (1 for true, 0 for false) + return yaml->value.bool_; +} + +/** + * \brief Extracts integer value from YAML node + * \param[in] yaml YAML node handle + * \return Stored integer or 0 for invalid nodes + * \warning Type mismatch returns 0 - verify with yaml_type() first + * + * This function retrieves the integer value from a YAML node if it is + * of type YAML_TYPE_INT. Returns 0 if: + * - Input node is NULL + * - Node does not contain an integer value + * + * Note: A return value of 0 does not necessarily indicate the stored + * value is 0, as it could also signify an invalid node. Always check + * the node type using yaml_type() before calling this function. + * + * Example usage: + * \code + * yaml_t node = ...; + * if (yaml_type(node) == YAML_TYPE_INT) { + * int value = yaml_value_int(node); + * printf("Integer value: %d\n", value); + * } else { + * printf("Node does not contain an integer value\n"); + * } + * \endcode + */ +int yaml_value_int(yaml_t yaml) +{ + // Validate input node is not NULL and is an integer type + if (!yaml) return 0; + if (yaml->vtype != YAML_TYPE_INT) return 0; + + // Return the stored integer value + return yaml->value.int_; +} + +/** + * \brief Retrieves floating-point value from YAML node + * \param[in] yaml YAML node handle + * \return Stored double or 0.0 for invalid nodes + * \note Maintains full floating-point precision when valid + * + * This function extracts the double-precision floating-point value from a YAML node + * if it is of type YAML_TYPE_FLOAT. Returns 0.0 if: + * - Input node is NULL + * - Node does not contain a floating-point value + * + * Example usage: + * \code + * yaml_t node = ...; + * if (yaml_type(node) == YAML_TYPE_FLOAT) { + * double value = yaml_value_float(node); + * printf("Float value: %.6f\n", value); + * } + * \endcode + */ +double yaml_value_float(yaml_t yaml) +{ + // Validate input node is not NULL and is a float type + if (!yaml) return 0.0; + if (yaml->vtype != YAML_TYPE_FLOAT) return 0.0; + + // Return the stored floating-point value with full precision + return yaml->value.float_; +} + +/** + * \brief Accesses string value from YAML node + * \param[in] yaml YAML node handle + * \return Pointer to managed string or NULL + * \warning Returned string must not be modified or freed + * + * This function retrieves the string value from a YAML node if it is + * of type YAML_TYPE_STRING. Returns NULL if: + * - Input node is NULL + * - Node does not contain a string value + * + * The returned string is managed by the YAML parser and must not be + * modified or freed by the caller. The pointer remains valid until the + * parent YAML document is destroyed with yaml_document_delete(). + * + * Example usage: + * \code + * yaml_t node = ...; + * const char* str = yaml_value_string(node); + * if (str) { + * printf("String value: %s\n", str); + * } + * \endcode + */ +const char* yaml_value_string(yaml_t yaml) +{ + // Validate input node is not NULL and is a string type + if (!yaml) return NULL; + if (yaml->vtype != YAML_TYPE_STRING) return NULL; + + // Return pointer to the internal string buffer + return yaml->value.string_; +} + +/** + * \brief Gets first child of sequence node + * \param[in] yaml Sequence node handle + * \return First child node or NULL + * \note Use with yaml_size() and sibling pointers to traverse + * + * This function retrieves the first child node from a YAML sequence if it is + * of type YAML_TYPE_SEQUENCE. Returns NULL if: + * - Input node is NULL + * - Node does not represent a sequence + * - Sequence is empty (no child nodes) + * + * To traverse all elements in the sequence, use this function to get the first + * child, then follow the sibling pointers (`yaml->next`) and check against the + * size returned by yaml_size(). + * + * Example usage: + * \code + * yaml_t seq_node = ...; // Assume this is a valid sequence node + * if (yaml_type(seq_node) == YAML_TYPE_SEQUENCE) { + * yaml_t child = yaml_value_sequence(seq_node); + * for (unsigned int i = 0; child && i < yaml_size(seq_node); i++) { + * // Process child node + * child = child->next; + * } + * } + * \endcode + */ +yaml_t yaml_value_sequence(yaml_t yaml) +{ + // Validate input node is not NULL and is a sequence type + if (!yaml) return NULL; + if (yaml->vtype != YAML_TYPE_SEQUENCE) return NULL; + + // Return the first child node or NULL if sequence is empty + return yaml->value.child_; +} + +/** + * \brief Gets first key-value pair of mapping node + * \param[in] yaml Mapping node handle + * \return First key-value node or NULL + * \note Subsequent pairs accessible via next pointer + * + * This function retrieves the first key-value pair from a YAML mapping + * (dictionary) if it is of type YAML_TYPE_MAPPING. Returns NULL if: + * - Input node is NULL + * - Node does not represent a mapping + * - Mapping is empty (no key-value pairs) + * + * To traverse all key-value pairs in the mapping, use this function to + * get the first pair, then follow the `next` pointers in each returned node. + * + * Example usage: + * \code + * yaml_t map_node = ...; // Assume this is a valid mapping node + * if (yaml_type(map_node) == YAML_TYPE_MAPPING) { + * yaml_t pair = yaml_value_mapping(map_node); + * while (pair) { + * const char* key = yaml_key(pair); + * yaml_t value = yaml_value(pair); + * // Process key-value pair + * pair = pair->next; + * } + * } + * \endcode + */ +yaml_t yaml_value_mapping(yaml_t yaml) +{ + // Validate input node is not NULL and is a mapping type + if (!yaml) return NULL; + if (yaml->vtype != YAML_TYPE_MAPPING) return NULL; + + // Return the first key-value pair node or NULL if mapping is empty + return yaml->value.child_; +} + +/** + * \brief Accesses root node of document + * \param[in] yaml Document node handle + * \return Root content node or NULL + * \note Returned handle remains owned by document + * + * This function retrieves the root node of a YAML document if it is + * of type YAML_TYPE_DOCUMENT. Returns NULL if: + * - Input node is NULL + * - Node does not represent a document + * + * The returned node is owned by the document and should not be freed + * independently. It remains valid until the document is destroyed with + * yaml_document_delete(). + * + * Example usage: + * \code + * yaml_t doc = ...; // Assume this is a valid document node + * yaml_t root = yaml_value_document(doc); + * if (root) { + * // Process the root node of the document + * } + * \endcode + */ +yaml_t yaml_value_document(yaml_t yaml) +{ + // Validate input node is not NULL and is a document type + if (!yaml) return NULL; + if (yaml->vtype != YAML_TYPE_DOCUMENT) return NULL; + + // Return the root node of the document + return yaml->value.child_; +} + +/** + * \brief Set a string key with specified length for a YAML node + * \param[in] yaml: Pointer to the YAML node to set key for + * \param[in] key: The string key to be set + * \param[in] len: Length of the key string + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets a string key for a YAML node with the specified length. + * If the key already exists, it will be replaced. If the input key is NULL or + * length is non-positive, the existing key will be cleared. + * + * Memory management: + * - A copy of the key string is made using yaml_strdup() + * - Existing key memory (if any) is properly freed before replacement + * - The new key string is owned by the YAML node + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * yaml_set_key_l(node, "example_key", 12); + * \endcode + */ +static yaml_t yaml_set_key_l(yaml_t yaml, const char* key, int len) +{ + char* k; + + // Validate input node is not NULL + if (!yaml) return NULL; + + /* The current key and the one to be set can be the same, and can be set successfully directly */ + if (yaml->key && (yaml->key == key || !strcmp(yaml->key, key))) return yaml; + + /* If the passed in key is not empty, duplicate a backup */ + if (key && len > 0) + { + k = yaml_strdup(key, len); + if (!k) return NULL; + } + /* Otherwise, clear the yaml key */ + else k = NULL; + + /* Release the old key to update the new one */ + if (yaml->ktype == YAML_KTYPE_STRING) + { + if (yaml->key) free(yaml->key); + } + // Delete complex key structure if it existed + else if (yaml->ktype == YAML_KTYPE_COMPLEX) + { + if (yaml->complex) yaml_delete(yaml->complex); + } + + // Assign the new key and set type to string + yaml->key = k; + yaml->ktype = YAML_KTYPE_STRING; + + return yaml; +} + +/** +* \brief Set a string key for a YAML node (length determined by strlen) +* \param[in] yaml: Pointer to the YAML node to set key for +* \param[in] key: The string key to be set +* \return Pointer to the modified YAML node, or NULL on failure +*/ +yaml_t yaml_set_key(yaml_t yaml, const char* key) +{ + return yaml_set_key_l(yaml, key, strlen(key)); +} + +/** + * \brief Set a complex (nested) key for a YAML node + * \param[in] yaml: Pointer to the YAML node to set key for + * \param[in] key: Pointer to the complex YAML node to be used as key + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets a complex (hierarchical) key for a YAML node using another + * YAML node structure. The complex key can be any valid YAML structure (sequence, + * mapping, scalar, etc.). + * + * Key management: + * - The existing key (if any) is properly freed before assignment + * - The passed-in key node is adopted directly (not copied) + * - The complex key node should not be modified or freed externally after this call + * + * Example usage: + * \code + * yaml_t parent = yaml_new(); + * yaml_t complex_key = yaml_new(); + * yaml_set_value_string(complex_key, "nested_key"); + * yaml_set_key_complex(parent, complex_key); + * \endcode + */ +yaml_t yaml_set_key_complex(yaml_t yaml, yaml_t key) +{ + // Validate input nodes are not NULL + if (!yaml) return NULL; + if (!key) return NULL; + + /* The current key and the one to be set can be the same, and can be set successfully directly */ + if (yaml->complex && yaml->complex == key) return yaml; + + /* Release the old key to update the new one */ + if (yaml->ktype == YAML_KTYPE_STRING) + { + if (yaml->key) free(yaml->key); + } + // Delete complex key structure if it existed + else if (yaml->ktype == YAML_KTYPE_COMPLEX) + { + if (yaml->complex) yaml_delete(yaml->complex); + } + + // Assign the new complex key and set type + yaml->complex = key; + yaml->ktype = YAML_KTYPE_COMPLEX; + + return yaml; +} + +/** + * \brief Set an alias for a YAML node referencing an anchor in a document + * \param[in] yaml: Pointer to the YAML node to set alias for + * \param[in] alias: The alias string to be set + * \param[in] doc: Pointer to the YAML document containing the anchor + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets an alias for a YAML node that references an existing anchor + * within the specified document. Aliases allow referring back to previously defined + * anchor nodes, enabling data reuse and circular references in YAML documents. + * + * Requirements: + * - The target document node (doc) must be either a string node or already an anchor + * - The target document node must not already have an anchor assigned (if string type) + * - The node to set the alias for (yaml) must not already have an alias + * + * Example usage: + * \code + * yaml_t anchor_node = yaml_new(); + * yaml_t alias_node = yaml_new(); + * + * // Set anchor on document node + * yaml_set_anchor(anchor_node, "my_anchor"); + * + * // Create alias referencing the anchor + * yaml_set_alias(alias_node, "my_anchor", anchor_node); + * \endcode + */ +yaml_t yaml_set_alias(yaml_t yaml, const char* alias, yaml_t doc) +{ + // Validate input parameters + if (!yaml) return NULL; + if (yaml->alias) return NULL; // Node already has an alias + if (!alias) return NULL; // Alias string is NULL + if (!doc) return NULL; // Document node is NULL + + // Ensure the document node is either a string or already an anchor + if (doc->ktype == YAML_KTYPE_STRING) + { + // Create new anchor if it doesn't exist + if (!doc->anchor) + { + doc->anchor = (ANCHOR *)malloc(sizeof(ANCHOR)); + if (!doc->anchor) return NULL; + + memset(doc->anchor, 0, sizeof(ANCHOR)); + + doc->ktype = YAML_KTYPE_ANCHOR; + } + else + { + return NULL; // Anchor already exists + } + } + else if (doc->ktype == YAML_KTYPE_ANCHOR) + { + // Existing anchor - proceed to set alias + } + else return NULL; // Invalid document node type + + // Duplicate and assign the alias string + yaml->alias = yaml_strdup(alias, strlen(alias)); + if (!yaml->alias) return NULL; + + // Add this node to the anchor's reference list + if (!yaml_add_anchor(doc->anchor, yaml)) + { + free(yaml->alias); + yaml->alias = NULL; + return NULL; + } + + return yaml; +} + +/** + * \brief Retrieve the alias of a YAML node + * \param[in] yaml: Pointer to the YAML node + * \return The alias string if exists, otherwise NULL + * + * This function retrieves the alias string of a YAML node if it has been + * previously set using yaml_set_alias(). The alias is used to reference + * an anchor node within the same document. + * + * Example usage: + * \code + * yaml_t node = ...; // Node with an alias set + * const char* alias = yaml_get_alias(node); + * if (alias) { + * printf("Node alias: %s\n", alias); + * } + * \endcode + */ +const char* yaml_get_alias(yaml_t yaml) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + // Return the alias string or NULL if not set + return yaml->alias; +} + +/** + * \brief Set an anchor reference for a YAML node + * \param[in] yaml: Pointer to the YAML node to set anchor reference for + * \param[in] anchor: The anchor string to reference + * \param[in] doc: Pointer to the YAML document containing the anchor + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets a reference from the specified YAML node to an existing + * anchor within the document. Anchors allow duplicate data to be represented + * as references, reducing redundancy in YAML documents. + * + * Requirements: + * - The target document node (doc) must be of type YAML_KTYPE_ANCHOR + * - The specified anchor string must exist in the document + * - The node to set the reference for (yaml) must not already have an alias + * + * Example usage: + * \code + * // Create anchor node + * yaml_t anchor_node = yaml_new(); + * yaml_set_key_string(anchor_node, "my_anchor"); + * + * // Create referencing node + * yaml_t ref_node = yaml_new(); + * yaml_set_anchor(ref_node, "my_anchor", anchor_node); + * \endcode + */ +yaml_t yaml_set_anchor(yaml_t yaml, const char* anchor, yaml_t doc) +{ + yaml_t ref = NULL; + + // Validate input parameters + if (!yaml) return NULL; + if (yaml->alias) return NULL; // Node already has an alias + if (!anchor) return NULL; // Anchor string is NULL + if (!doc) return NULL; // Document node is NULL + + // Ensure document node is an anchor type + if (doc->ktype != YAML_KTYPE_ANCHOR) return NULL; + + // Find the referenced anchor in the document + ref = yaml_match_anchor(doc->anchor, anchor, strlen(anchor)); + if (!ref) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_REFERENCE) yaml_set_null(yaml); + + /* Change the type to object */ + yaml->vtype = YAML_TYPE_REFERENCE; + + /* The current object and the one to be set can be the same, and can be set successfully directly */ + if (yaml->value.child_ && yaml->value.child_ == ref) return yaml; + + /* Release the old object to update the new one */ + if (yaml->value.child_) yaml_delete(yaml->value.child_); + yaml->value.child_ = ref; + + return yaml; +} + +/** + * \brief Set a YAML node to null type + * \param[in] yaml: Pointer to the YAML node to set as null + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to the null type. It cleans up the existing + * value of the node depending on its current type (string, sequence, or mapping) + * and then sets the node's type to YAML_TYPE_NULL and clears its value. + * + * Memory management: + * - For string type nodes, it frees the string pointer. + * - For sequence and mapping type nodes, it deletes the child nodes. + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * // Assume node has some value initially + * yaml_set_null(node); + * \endcode + */ +yaml_t yaml_set_null(yaml_t yaml) +{ + if (!yaml) return NULL; + + /* delete string value */ + if (yaml->vtype == YAML_TYPE_STRING) free(yaml->value.string_); + /* delete child objects */ + else if (yaml->vtype == YAML_TYPE_SEQUENCE) yaml_delete(yaml->value.child_); + else if (yaml->vtype == YAML_TYPE_MAPPING) yaml_delete(yaml->value.child_); + + /* Change the type to null and reset the value */ + yaml->vtype = YAML_TYPE_NULL; + memset(&yaml->value, 0, sizeof(yaml->value)); + + // Return the modified node + return yaml; +} + +/** + * \brief Set a boolean value for a YAML node + * \param[in] yaml: Pointer to the YAML node to set boolean value for + * \param[in] b: Boolean value (YAML_TRUE or YAML_FALSE) + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to a boolean type with the specified value. + * Any existing value is cleared and the node's type is set to YAML_TYPE_BOOL. + * + * The input boolean value is normalized to ensure only valid boolean states are stored. + * Specifically: + * - YAML_FALSE (0) is preserved as YAML_FALSE + * - Any non-zero value is treated as YAML_TRUE (1) + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * yaml_set_bool(node, YAML_TRUE); + * \endcode + */ +yaml_t yaml_set_bool(yaml_t yaml, int b) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_BOOL) yaml_set_null(yaml); + + /* Change the type to bool and set the bool value */ + yaml->vtype = YAML_TYPE_BOOL; + yaml->value.bool_ = (b == YAML_FALSE ? YAML_FALSE : YAML_TRUE); + + return yaml; +} + +/** + * \brief Set an integer value for a YAML node + * \param[in] yaml: Pointer to the YAML node to set integer value for + * \param[in] num: Integer value to set + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to an integer type with the specified value. + * Any existing value is cleared and the node's type is set to YAML_TYPE_INT. + * + * Note: The original code had a logical error in the type check condition. + * The corrected version properly resets the node if it's not already an INT or FLOAT. + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * yaml_set_int(node, 42); + * \endcode + */ +yaml_t yaml_set_int(yaml_t yaml, int num) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_INT || yaml->vtype != YAML_TYPE_FLOAT) yaml_set_null(yaml); + + /* Change the type to float and set the float value */ + yaml->vtype = YAML_TYPE_INT; + yaml->value.int_ = num; + + return yaml; +} + +/** + * \brief Set a float value for a YAML node + * \param[in] yaml: Pointer to the YAML node to set float value for + * \param[in] num: Double-precision float value to set + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to a float type with the specified value. + * Any existing value is cleared and the node's type is set to YAML_TYPE_FLOAT. + * + * Note: The original code had a logical error in the type check condition. + * The corrected version properly resets the node if it's not already an INT or FLOAT. + * This maintains compatibility with numeric type conversions. + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * yaml_set_float(node, 3.14159); + * \endcode + */ +yaml_t yaml_set_float(yaml_t yaml, double num) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_INT || yaml->vtype != YAML_TYPE_FLOAT) yaml_set_null(yaml); + + /* Change the type to float and set the float value */ + yaml->vtype = YAML_TYPE_FLOAT; + yaml->value.float_ = num; + + return yaml; +} + +/** + * \brief Set a string value for a YAML node + * \param[in] yaml: Pointer to the YAML node to set string value for + * \param[in] string: String value to set + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to a string type with the specified value. + * The input string is copied to ensure proper memory management. + * + * Memory management: + * - The existing string (if any) is freed before assigning the new value + * - A copy of the input string is created using yaml_strdup() + * - The new string is owned by the YAML node + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * yaml_set_string(node, "Hello, World!"); + * \endcode + */ +yaml_t yaml_set_string(yaml_t yaml, const char* string) +{ + char* s; + int len = 0; + + // Validate input node is not NULL + if (!yaml) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_STRING) yaml_set_null(yaml); + + /* Change the type to string */ + yaml->vtype = YAML_TYPE_STRING; + + /* The current string and the one to be set can be the same, and can be set successfully directly */ + if (yaml->value.string_ && (yaml->value.string_ == string || !strcmp(yaml->value.string_, string))) return yaml; + + /* If the passed in string is not empty, duplicate a backup */ + if (string) + { + len = strlen(string); + s = yaml_strdup(string, strlen(string)); + if (!s) return NULL; + } + /* Otherwise, clear the yaml string */ + else s = NULL; + + /* Release the old string to update the new one */ + if (yaml->value.string_) free(yaml->value.string_); + yaml->value.string_ = s; + + // Update the size field with the string length + yaml->size = len; + + return yaml; +} + +/** + * \brief Set a date value for a YAML node + * \param[in] yaml: Pointer to the YAML node to set date value for + * \param[in] year: Year component (0-4095) + * \param[in] month: Month component (1-12) + * \param[in] day: Day component (1-31) + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to a date type with the specified + * year, month, and day components. The date is validated to ensure it forms + * a valid calendar date before assignment. + * + * Validation checks: + * - Year must be within 0-4095 range + * - Month must be within 1-12 range + * - Day must be valid for the given month and year (accounts for leap years) + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * yaml_set_date(node, 2023, 10, 15); // October 15, 2023 + * \endcode + */ +yaml_t yaml_set_date(yaml_t yaml, int year, char month, char day) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + // Validate date components + if (year < 0 || year > 4095) return NULL; + if (!date_isvalid(year, month, day)) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_DATE) yaml_set_null(yaml); + + /* Change the type to string */ + yaml->vtype = YAML_TYPE_DATE; + + yaml->value.date.year = year; + yaml->value.date.month = month; + yaml->value.date.day = day; + + return yaml; +} + +/** + * \brief Set time components for a YAML date node + * \param[in] yaml: Pointer to the YAML date node to set time for + * \param[in] hour: Hour component (0-23) + * \param[in] minute: Minute component (0-59) + * \param[in] second: Second component (0-59) + * \param[in] msec: Millisecond component (0-999) + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the time components (hour, minute, second, millisecond) + * for an existing YAML date node. The node must already be of type YAML_TYPE_DATE + * (typically set via yaml_set_date()). + * + * Validation checks: + * - Hour must be within 0-23 range + * - Minute must be within 0-59 range + * - Second must be within 0-59 range + * - Millisecond must be within 0-999 range + * + * Flags set: + * - DATEF_TIME: Indicates time components are present + * - DATEF_MSEC: Indicates millisecond component is present (if msec > 0) + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * yaml_set_date(node, 2023, 10, 15); // Set date first + * yaml_set_time(node, 14, 30, 0, 500); // Set time to 14:30:00.500 + * \endcode + */ +yaml_t yaml_set_time(yaml_t yaml, char hour, char minute, char second, int msec) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + // Ensure node is a date type + if (yaml->vtype != YAML_TYPE_DATE) return NULL; + + // Validate time components + if (hour < 0 || hour >= 24) return NULL; + if (minute < 0 || minute >= 60) return NULL; + if (second < 0 || second >= 60) return NULL; + if (msec < 0 || msec >= 1000) return NULL; + + // Assign time components and set flags + yaml->value.date.hour = hour; + yaml->value.date.minute = minute; + yaml->value.date.second = second; + yaml->value.date.msec = msec; + + // Set time present flag + yaml->value.date.flag |= DATEF_TIME; + + // Set millisecond present flag if applicable + if (msec > 0) yaml->value.date.flag |= DATEF_MSEC; + + return yaml; +} + +/** + * \brief Set UTC offset for a YAML date node + * \param[in] yaml: Pointer to the YAML date node to set UTC offset for + * \param[in] hour: UTC hour offset (-12 to 12) + * \param[in] minute: UTC minute offset (0-59) + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the UTC offset components (hour and minute) for an existing + * YAML date node. The node must already be of type YAML_TYPE_DATE (typically set + * via yaml_set_date()). The offset is stored as a positive value with a sign flag. + * + * Validation checks: + * - Hour must be within -12 to 12 range + * - Minute must be within 0-59 range + * + * Storage format: + * - Negative offsets are stored with utcsign = 1 and absolute value of hour + * - Positive offsets are stored with utcsign = 0 + * + * Example usage: + * \code + * yaml_t node = yaml_new(); + * yaml_set_date(node, 2023, 10, 15); // Set date first + * yaml_set_time(node, 14, 30, 0, 0); // Set time + * yaml_set_utc(node, -5, 0); // Set UTC offset to -05:00 (EST) + * \endcode + */ +yaml_t yaml_set_utc(yaml_t yaml, char hour, char minute) +{ + int utcsign = 0; + + // Validate input node is not NULL + if (!yaml) return NULL; + + // Ensure node is a date type + if (yaml->vtype != YAML_TYPE_DATE) return NULL; + + // Handle negative offset by storing sign separately + if (hour < 0) + { + utcsign = 1; + hour = -hour; // Store absolute value + } + + // Validate UTC components + if (hour > 12) return NULL; + if (minute < 0 || minute >= 60) return NULL; + + // Assign UTC components and set flag + yaml->value.date.utchour = hour; + yaml->value.date.utcminute = minute; + yaml->value.date.utcsign = utcsign; + + yaml->value.date.flag |= DATEF_UTC; + + return yaml; +} + +/** + * \brief Set a sequence (array) for a YAML node + * \param[in] yaml: Pointer to the YAML node to set sequence for + * \param[in] sequence: Pointer to the sequence YAML node + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to a sequence (array) type with the specified + * sequence node as its content. The sequence node should be a valid YAML sequence structure. + * + * Memory management: + * - The existing sequence (if any) is deleted before assigning the new value + * - The passed sequence node is adopted directly (not copied) + * - The sequence node should not be modified or freed externally after this call + * + * Example usage: + * \code + * // Create parent node + * yaml_t parent = yaml_new(); + * + * // Create sequence node + * yaml_t sequence = yaml_new(); + * yaml_set_sequence(sequence, NULL); // Initialize as empty sequence + * + * // Add elements to sequence... + * + * // Set the sequence to the parent node + * yaml_set_sequence(parent, sequence); + * \endcode + */ +yaml_t yaml_set_sequence(yaml_t yaml, yaml_t sequence) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_SEQUENCE) yaml_set_null(yaml); + + /* Change the type to object */ + yaml->vtype = YAML_TYPE_SEQUENCE; + + /* The current object and the one to be set can be the same, and can be set successfully directly */ + if (yaml->value.child_ && yaml->value.child_ == sequence) return yaml; + + /* Release the old object to update the new one */ + if (yaml->value.child_) yaml_delete(yaml->value.child_); + yaml->value.child_ = sequence; + + return yaml; +} + +/** + * \brief Set a mapping (key-value pairs) for a YAML node + * \param[in] yaml: Pointer to the YAML node to set mapping for + * \param[in] mapping: Pointer to the mapping YAML node + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to a mapping (dictionary) type with the specified + * mapping node as its content. The mapping node should be a valid YAML mapping structure. + * + * Memory management: + * - The existing mapping (if any) is deleted before assigning the new value + * - The passed mapping node is adopted directly (not copied) + * - The mapping node should not be modified or freed externally after this call + * + * Example usage: + * \code + * // Create parent node + * yaml_t parent = yaml_new(); + * + * // Create mapping node + * yaml_t mapping = yaml_new(); + * yaml_set_mapping(mapping, NULL); // Initialize as empty mapping + * + * // Add key-value pairs to mapping... + * + * // Set the mapping to the parent node + * yaml_set_mapping(parent, mapping); + * \endcode + */ +yaml_t yaml_set_mapping(yaml_t yaml, yaml_t mapping) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_MAPPING) yaml_set_null(yaml); + + /* Change the type to object */ + yaml->vtype = YAML_TYPE_MAPPING; + + /* The current object and the one to be set can be the same, and can be set successfully directly */ + if (yaml->value.child_ && yaml->value.child_ == mapping) return yaml; + + /* Release the old object to update the new one */ + if (yaml->value.child_) yaml_delete(yaml->value.child_); + yaml->value.child_ = mapping; + + return yaml; +} + +/** + * \brief Set a document structure for a YAML node + * \param[in] yaml: Pointer to the YAML node to set document for + * \param[in] document: Pointer to the document YAML node + * \return Pointer to the modified YAML node, or NULL on failure + * + * This function sets the given YAML node to a document type with the specified + * document node as its content. The document node should be a valid YAML document + * structure containing the root element of the YAML hierarchy. + * + * Memory management: + * - The existing document (if any) is deleted before assigning the new value + * - The passed document node is adopted directly (not copied) + * - The document node should not be modified or freed externally after this call + * + * Example usage: + * \code + * // Create parent node + * yaml_t parent = yaml_new(); + * + * // Create document node + * yaml_t document = yaml_new(); + * yaml_set_document(document, NULL); // Initialize as empty document + * + * // Set root element of document... + * + * // Set the document to the parent node + * yaml_set_document(parent, document); + * \endcode + */ +yaml_t yaml_set_document(yaml_t yaml, yaml_t document) +{ + // Validate input node is not NULL + if (!yaml) return NULL; + + /* If the current type does not match, set the type to null first */ + if (yaml->vtype != YAML_TYPE_DOCUMENT) yaml_set_null(yaml); + + /* Change the type to object */ + yaml->vtype = YAML_TYPE_DOCUMENT; + + /* The current object and the one to be set can be the same, and can be set successfully directly */ + if (yaml->value.child_ && yaml->value.child_ == document) return yaml; + + /* Release the old object to update the new one */ + if (yaml->value.child_) yaml_delete(yaml->value.child_); + yaml->value.child_ = document; + + return yaml; +} + +/** + * \brief Move the internal iterator of a YAML node to a specified index + * \param[in] yaml: Pointer to the YAML node (sequence, mapping, or document) + * \param[in] index: Target index to move the iterator to + * \return Pointer to the YAML node at the specified index, or NULL on failure + * + * This function moves the internal iterator of a sequence, mapping, or document + * node to the specified index position. The iterator allows sequential access to + * child nodes without exposing the underlying linked list structure. + * + * Iterator behavior: + * - For sequences: Moves to the element at the given 0-based index + * - For mappings: Moves to the key-value pair at the given 0-based index + * - For documents: Moves to the document child at the given 0-based index + * + * Performance: + * - If the target index is before the current iterator position, the iterator + * is reset and traversed from the start + * - If the target index is after the current position, the iterator advances + * from its current position + * + * Example usage: + * \code + * yaml_t seq = yaml_new(); + * // Assume seq is a valid sequence with elements + * + * yaml_t element = yaml_iterator_to(seq, 2); // Move to 3rd element + * if (element) { + * // Process element + * } + * \endcode + */ +static yaml_t yaml_iterator_to(yaml_t yaml, unsigned int index) +{ + // Validate node type and index range + if (yaml->vtype != YAML_TYPE_SEQUENCE && + yaml->vtype != YAML_TYPE_MAPPING && + yaml->vtype != YAML_TYPE_DOCUMENT) return NULL; + + if (index >= yaml->size) return NULL; + + // Reset iterator if current position is after target or invalid + if (index < yaml->iterator.i || !yaml->iterator.p || index == 0) + { + yaml->iterator.i = 0; + yaml->iterator.p = yaml->value.child_; + } + + // Traverse to target index from current iterator position + while (yaml->iterator.p && yaml->iterator.i < index) + { + yaml->iterator.p = ((yaml_t)(yaml->iterator.p))->next; + yaml->iterator.i++; + } + + return yaml->iterator.p; +} + +/** + * \brief Attach a YAML node (or sequence of nodes) to a parent node at specified index + * \param[in] yaml: Pointer to the parent YAML node (sequence, mapping, or document) + * \param[in] index: Position to insert the new node(s) + * \param[in] attach: Pointer to the YAML node to attach (can be head of a linked list) + * \return Pointer to the attached YAML node, or NULL on failure + * + * This function attaches a YAML node (or a sequence of linked nodes) to a parent node + * at the specified index. The parent must be a sequence, mapping, or document node. + * + * Key behaviors: + * - For sequences: Attaches nodes as elements (key should be NULL) + * - For mappings: Attaches nodes as key-value pairs (key must be set) + * - For documents: Attaches anchored nodes (anchor must be set) + * + * Memory management: + * - The attached node(s) are not copied; ownership is transferred to the parent + * - The attached nodes should not be modified or freed externally after this call + * + * Example usage: + * \code + * // Create a sequence and two elements + * yaml_t seq = yaml_new(); + * yaml_set_sequence(seq, NULL); + * + * yaml_t elem1 = yaml_new(); + * yaml_set_string(elem1, "value1"); + * + * yaml_t elem2 = yaml_new(); + * yaml_set_string(elem2, "value2"); + * + * // Link elements into a list + * elem1->next = elem2; + * + * // Attach both elements at index 0 + * yaml_attach(seq, 0, elem1); + * \endcode + */ +yaml_t yaml_attach(yaml_t yaml, unsigned int index, yaml_t attach) +{ + yaml_t prev = NULL; + yaml_t tail = attach; + unsigned int size = 1; + + // Validate input parameters + if (!yaml) return NULL; + if (!attach) return NULL; + + // Check if attach node type matches parent container type + if (!(yaml->vtype == YAML_TYPE_SEQUENCE && !attach->key) && + !(yaml->vtype == YAML_TYPE_MAPPING && attach->key) && + !(yaml->vtype == YAML_TYPE_DOCUMENT && attach->anchor)) + return NULL; + + // Calculate size of the list to attach + while (tail->next) + { + tail = tail->next; + size++; + } + + // Insert at beginning + if (index == 0) + { + tail->next = yaml->value.child_; + yaml->value.child_ = attach; + } + else + { + // Find previous node + prev = yaml_iterator_to(yaml, index - 1); + if (!prev) return NULL; + + // Insert after previous node + tail->next = prev->next; + prev->next = attach; + } + + // Update parent size + yaml->size += size; + + return attach; +} + +/** + * \brief Detach a YAML node from its parent at specified index + * \param[in] yaml: Pointer to the parent YAML node (sequence, mapping, or document) + * \param[in] index: Index of the node to detach + * \return Pointer to the detached YAML node, or NULL on failure + * + * This function detaches a YAML node from its parent container (sequence, mapping, + * or document) at the specified index. The detached node is returned with its next + * pointer set to NULL, effectively isolating it from the original list. + * + * Memory management: + * - The detached node is not freed; ownership is transferred to the caller + * - The caller is responsible for freeing the detached node or reattaching it + * + * Example usage: + * \code + * yaml_t seq = yaml_new(); + * // Assume seq is a valid sequence with elements + * + * yaml_t detached = yaml_dettach(seq, 1); // Detach 2nd element + * if (detached) { + * // Process detached node + * yaml_delete(detached); // Free the detached node + * } + * \endcode + */ +yaml_t yaml_dettach(yaml_t yaml, unsigned int index) +{ + yaml_t dettach = NULL, prev = NULL; + + // Validate input parameters + if (!yaml) return NULL; + + // Check if parent is a valid container type + if (yaml->vtype != YAML_TYPE_SEQUENCE && + yaml->vtype != YAML_TYPE_MAPPING && + yaml->vtype != YAML_TYPE_DOCUMENT) + return NULL; + + // Check if container is not empty + if (yaml->size == 0) return NULL; + + // Detach first node + if (index == 0) + { + dettach = yaml->value.child_; + yaml->value.child_ = dettach->next; + } + else + { + // Check index validity + if (index >= yaml->size) return NULL; + + // Find previous node + prev = yaml_iterator_to(yaml, index - 1); + if (!prev) return NULL; + + // Detach node from list + dettach = prev->next; + prev->next = dettach->next; + } + + // Ensure detached node is isolated + dettach->next = NULL; + + // Update parent size + yaml->size--; + + return dettach; +} + +/** +* \brief Macro to validate and adjust YAML node type based on key presence +* This macro checks if the given YAML node can accept the insertion type +* based on its current type and the presence of a key: +* - If NULL, sets type to SEQUENCE (if key is NULL) or MAPPING (if key exists) +* - If SEQUENCE, ensures key is NULL +* - If MAPPING, ensures key exists +* - If DOCUMENT, allows insertion (with special handling in document functions) +* - Returns NULL for unsupported types +*/ +#define CHECK_INSERT_TYPE() \ + if (yaml->vtype == YAML_TYPE_NULL) \ + { \ + if (key) yaml->vtype = YAML_TYPE_MAPPING; \ + else yaml->vtype = YAML_TYPE_SEQUENCE; \ + } \ + else if (yaml->vtype == YAML_TYPE_SEQUENCE) \ + { \ + if (key) return NULL; \ + } \ + else if (yaml->vtype == YAML_TYPE_MAPPING) \ + { \ + if (!key) return NULL; \ + } \ + else if (yaml->vtype == YAML_TYPE_DOCUMENT) \ + { \ + } \ + else return NULL; \ + +/** + * \brief Insert a null value into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node + * \param[in] key: Key for the new node (NULL for sequences) + * \param[in] index: Position to insert the new node + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a new YAML node with a null value into a parent sequence, + * mapping, or document at the specified index. The new node is initialized as a + * null type and can optionally be assigned a key (for mappings). + * + * Key behaviors: + * - For sequences: key should be NULL, index specifies position in list + * - For mappings: key is required, index specifies position in key order + * - For documents: key is ignored, index specifies position in document + * + * Example usage: + * \code + * yaml_t seq = yaml_new(); + * yaml_set_sequence(seq, NULL); + * + * // Insert null element at index 0 + * yaml_insert_null(seq, NULL, 0); + * + * yaml_t map = yaml_new(); + * yaml_set_mapping(map, NULL); + * + * // Insert null key-value pair at index 1 + * yaml_insert_null(map, "null_key", 1); + * \endcode + */ +yaml_t yaml_insert_null(yaml_t yaml, const char* key, unsigned int index) +{ + yaml_t insert = NULL; + + // Validate input parameters + if (!yaml) return NULL; + + // Check if parent is a valid container type + CHECK_INSERT_TYPE(); + + // Create new YAML node + insert = yaml_create(); + if (!insert) return NULL; + + // Set key if provided (for mappings) + if (key && !yaml_set_key(insert, key)) goto FAIL; + + // Attach the new node to the parent at specified index + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up on failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Insert a boolean value into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node + * \param[in] key: Key for the new node (NULL for sequences) + * \param[in] index: Position to insert the new node + * \param[in] b: Boolean value to set (YAML_TRUE/YAML_FALSE) + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a new YAML node with a boolean value into a parent sequence, + * mapping, or document at the specified index. The new node is created, assigned a key + * (if applicable for mappings), set to the provided boolean value, and then attached + * to the parent node at the given index. + * + * Key behaviors: + * - For sequences: key should be NULL, and the new node with boolean value is inserted + * at the specified index in the sequence. + * - For mappings: a non-NULL key is required, and the new key-value pair with the + * boolean value is inserted at the specified index in the mapping. + * + * Example usage: + * \code + * yaml_t seq = yaml_new(); + * yaml_set_sequence(seq, NULL); + * // Insert boolean element (true) at index 0 in the sequence + * yaml_insert_bool(seq, NULL, 0, YAML_TRUE); + * + * yaml_t map = yaml_new(); + * yaml_set_mapping(map, NULL); + * // Insert key-value pair with boolean value (false) at index 1 in the mapping + * yaml_insert_bool(map, "bool_key", 1, YAML_FALSE); + * \endcode + */ +yaml_t yaml_insert_bool(yaml_t yaml, const char* key, unsigned int index, int b) +{ + yaml_t insert = NULL; + + // Validate input node + if (!yaml) return NULL; + + // Assume CHECK_INSERT_TYPE() validates the node type for insertion + CHECK_INSERT_TYPE(); + + // Create a new YAML node + insert = yaml_create(); + if (!insert) return NULL; + + // Set the key for the new node if it's for a mapping (key should be NULL for sequences) + if (key && !yaml_set_key(insert, key)) goto FAIL; + + // Set the boolean value for the new node + yaml_set_bool(insert, b); + + // Attach the new node to the parent at the specified index + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up the new node in case of failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Insert an integer value into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node + * \param[in] key: Key for the new node (NULL for sequences) + * \param[in] index: Position to insert the new node + * \param[in] num: Integer value to set + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a new YAML node with an integer value into a parent sequence, + * mapping, or document at the specified index. The new node is created, assigned a key + * (if applicable for mappings), set to the provided integer value, and then attached + * to the parent node at the given index. + * + * Key behaviors: + * - For sequences: key should be NULL, and the new node with integer value is inserted + * at the specified index in the sequence. + * - For mappings: a non-NULL key is required, and the new key-value pair with the + * integer value is inserted at the specified index in the mapping. + * + * Example usage: + * \code + * yaml_t seq = yaml_new(); + * yaml_set_sequence(seq, NULL); + * // Insert integer element (42) at index 0 in the sequence + * yaml_insert_int(seq, NULL, 0, 42); + * + * yaml_t map = yaml_new(); + * yaml_set_mapping(map, NULL); + * // Insert key-value pair with integer value (100) at index 1 in the mapping + * yaml_insert_int(map, "age", 1, 100); + * \endcode + */ +yaml_t yaml_insert_int(yaml_t yaml, const char* key, unsigned int index, int num) +{ + yaml_t insert = NULL; + + // Validate input node + if (!yaml) return NULL; + + // Assume CHECK_INSERT_TYPE() validates the node type for insertion + CHECK_INSERT_TYPE(); + + // Create a new YAML node + insert = yaml_create(); + if (!insert) return NULL; + + // Set the key for the new node if it's for a mapping (key should be NULL for sequences) + if (key && !yaml_set_key(insert, key)) goto FAIL; + + // Set the integer value for the new node + yaml_set_int(insert, num); + + // Attach the new node to the parent at the specified index + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up the new node in case of failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Insert a float value into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node + * \param[in] key: Key for the new node (NULL for sequences) + * \param[in] index: Position to insert the new node + * \param[in] num: Double-precision float value to set + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a new YAML node with a floating-point value into a parent + * sequence, mapping, or document at the specified index. The new node is created, + * assigned a key (if applicable for mappings), set to the provided double-precision + * float value, and then attached to the parent node at the given index. + * + * Key behaviors: + * - For sequences: key should be NULL, and the new node with float value is inserted + * at the specified index in the sequence. + * - For mappings: a non-NULL key is required, and the new key-value pair with the + * float value is inserted at the specified index in the mapping. + * + * Example usage: + * \code + * yaml_t seq = yaml_new(); + * yaml_set_sequence(seq, NULL); + * // Insert float element (3.14) at index 0 in the sequence + * yaml_insert_float(seq, NULL, 0, 3.14); + * + * yaml_t map = yaml_new(); + * yaml_set_mapping(map, NULL); + * // Insert key-value pair with float value (0.01) at index 1 in the mapping + * yaml_insert_float(map, "ratio", 1, 0.01); + * \endcode + */ +yaml_t yaml_insert_float(yaml_t yaml, const char* key, unsigned int index, double num) +{ + yaml_t insert = NULL; + + // Validate input node + if (!yaml) return NULL; + + // Assume CHECK_INSERT_TYPE() validates the node type for insertion + CHECK_INSERT_TYPE(); + + // Create a new YAML node + insert = yaml_create(); + if (!insert) return NULL; + + // Set the key for the new node if it's for a mapping (key should be NULL for sequences) + if (key && !yaml_set_key(insert, key)) goto FAIL; + + // Set the float value for the new node (using double-precision) + yaml_set_float(insert, num); + + // Attach the new node to the parent at the specified index + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up the new node in case of failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Insert a string value into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node + * \param[in] key: Key for the new node (NULL for sequences) + * \param[in] index: Position to insert the new node + * \param[in] string: String value to set + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a new YAML node with a string value into a parent sequence, + * mapping, or document at the specified index. The new node is created, assigned a key + * (if applicable for mappings), set to the provided string value, and then attached + * to the parent node at the given index. + * + * Key behaviors: + * - For sequences: key should be NULL, and the new node with string value is inserted + * at the specified index in the sequence. + * - For mappings: a non-NULL key is required, and the new key-value pair with the + * string value is inserted at the specified index in the mapping. + * + * Memory management: + * - The input string is copied using yaml_set_string(), so the original string can be + * safely freed after this call. + * + * Example usage: + * \code + * yaml_t seq = yaml_new(); + * yaml_set_sequence(seq, NULL); + * // Insert string element ("apple") at index 0 in the sequence + * yaml_insert_string(seq, NULL, 0, "apple"); + * + * yaml_t map = yaml_new(); + * yaml_set_mapping(map, NULL); + * // Insert key-value pair with string value ("banana") at index 1 in the mapping + * yaml_insert_string(map, "fruit", 1, "banana"); + * \endcode + */ +yaml_t yaml_insert_string(yaml_t yaml, const char* key, unsigned int index, const char* string) +{ + yaml_t insert = NULL; + + // Validate input node + if (!yaml) return NULL; + + // Assume CHECK_INSERT_TYPE() validates the node type for insertion + CHECK_INSERT_TYPE(); + + // Create a new YAML node + insert = yaml_create(); + if (!insert) return NULL; + + // Set the key for the new node if it's for a mapping (key should be NULL for sequences) + if (key && !yaml_set_key(insert, key)) goto FAIL; + + // Set the string value for the new node (creates a copy of the input string) + if (!yaml_set_string(insert, string)) goto FAIL; + + // Attach the new node to the parent at the specified index + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up the new node in case of failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Insert a sequence into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node + * \param[in] key: Key for the new node (NULL for sequences) + * \param[in] index: Position to insert the new node + * \param[in] sequence: Sequence YAML node to insert + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a new YAML sequence node into a parent sequence, mapping, + * or document at the specified index. The new sequence node is created, assigned + * a key (if applicable for mappings), initialized with the provided sequence content, + * and then attached to the parent node. + * + * Key behaviors: + * - For sequences: key should be NULL, and the new sequence is inserted as an element. + * - For mappings: a non-NULL key is required, and the new sequence is inserted as a value. + * - The provided sequence is adopted by the new node; ownership is transferred. + * + * Example usage: + * \code + * // Create parent sequence + * yaml_t parent_seq = yaml_new(); + * yaml_set_sequence(parent_seq, NULL); + * + * // Create child sequence + * yaml_t child_seq = yaml_new(); + * yaml_set_sequence(child_seq, NULL); + * + * // Insert child sequence into parent at index 0 + * yaml_insert_sequence(parent_seq, NULL, 0, child_seq); + * + * // Create a mapping + * yaml_t map = yaml_new(); + * yaml_set_mapping(map, NULL); + * + * // Insert sequence into mapping with key "items" + * yaml_insert_sequence(map, "items", 0, child_seq); + * \endcode + */ +yaml_t yaml_insert_sequence(yaml_t yaml, const char* key, unsigned int index, yaml_t sequence) +{ + yaml_t insert = NULL; + + // Validate input parameters + if (!yaml) return NULL; + + // Ensure parent is a valid container type + CHECK_INSERT_TYPE(); + + // Create new YAML node to hold the sequence + insert = yaml_create(); + if (!insert) return NULL; + + // Set key for mapping entries (ignored for sequences) + if (key && !yaml_set_key(insert, key)) goto FAIL; + + // Set the node's value to the provided sequence + yaml_set_sequence(insert, sequence); + + // Attach the new node to the parent at specified index + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up on failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Insert a mapping into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node + * \param[in] key: Key for the new node (NULL for sequences) + * \param[in] index: Position to insert the new node + * \param[in] mapping: Mapping YAML node to insert + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a new YAML mapping node into a parent sequence, mapping, + * or document at the specified index. The new mapping node is created, assigned + * a key (if applicable for mappings), initialized with the provided mapping content, + * and then attached to the parent node. + * + * Key behaviors: + * - For sequences: key should be NULL, and the new mapping is inserted as an element. + * - For mappings: a non-NULL key is required, and the new mapping is inserted as a value. + * - The provided mapping is adopted by the new node; ownership is transferred. + * + * Example usage: + * \code + * // Create parent sequence + * yaml_t parent_seq = yaml_new(); + * yaml_set_sequence(parent_seq, NULL); + * + * // Create child mapping + * yaml_t child_map = yaml_new(); + * yaml_set_mapping(child_map, NULL); + * + * // Insert child mapping into parent at index 0 + * yaml_insert_mapping(parent_seq, NULL, 0, child_map); + * + * // Create a parent mapping + * yaml_t parent_map = yaml_new(); + * yaml_set_mapping(parent_map, NULL); + * + * // Insert mapping into parent mapping with key "config" + * yaml_insert_mapping(parent_map, "config", 0, child_map); + * \endcode + */ +yaml_t yaml_insert_mapping(yaml_t yaml, const char* key, unsigned int index, yaml_t mapping) +{ + yaml_t insert = NULL; + + // Validate input parameters + if (!yaml) return NULL; + + // Ensure parent is a valid container type + CHECK_INSERT_TYPE(); + + // Create new YAML node to hold the mapping + insert = yaml_create(); + if (!insert) return NULL; + + // Set key for mapping entries (ignored for sequences) + if (key && !yaml_set_key(insert, key)) goto FAIL; + + // Set the node's value to the provided mapping + yaml_set_mapping(insert, mapping); + + // Attach the new node to the parent at specified index + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up on failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Insert a document reference into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node + * \param[in] index: Position to insert the new node + * \param[in] document: Document YAML node to reference + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a document reference (anchor) into a parent YAML node + * at the specified index. The reference allows the document to be reused + * elsewhere in the YAML structure via aliases. + * + * Key behaviors: + * - Converts the parent node to a document type if it's currently null + * - Creates a new anchor node that references the provided document + * - The document itself is not modified or copied + * - The anchor node is attached to the parent's child list + * + * Memory management: + * - A new anchor structure is allocated for the reference + * - The anchor is owned by the created YAML node + * + * Example usage: + * \code + * yaml_t parent = yaml_new(); + * yaml_t doc = yaml_new(); + * // Initialize doc as a document... + * + * // Insert document reference at index 0 + * yaml_insert_document(parent, 0, doc); + * \endcode + */ +yaml_t yaml_insert_document(yaml_t yaml, unsigned int index, yaml_t document) +{ + yaml_t insert = NULL; + + // Validate input parameters + if (!yaml) return NULL; + + // Convert parent to document type if currently null + if (yaml->vtype == YAML_TYPE_NULL) + { + yaml->vtype = YAML_TYPE_DOCUMENT; + } + + // Create new YAML node to hold the document reference + insert = yaml_create(); + if (!insert) goto FAIL; + + // Allocate and initialize anchor structure + insert->anchor = (ANCHOR *)malloc(sizeof(ANCHOR)); + if (!insert->anchor) goto FAIL; + + memset(insert->anchor, 0, sizeof(ANCHOR)); + + // Set node type to anchor + insert->ktype = YAML_KTYPE_ANCHOR; + + // Attach the new anchor node to the parent + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up on failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Insert a reference to an anchor into a YAML node at specified index + * \param[in] yaml: Pointer to the parent YAML node (must be MAPPING) + * \param[in] key: Key for the new node + * \param[in] index: Position to insert the new node + * \param[in] anchor: Anchor name to reference + * \param[in] doc: Document containing the anchor + * \return Pointer to the inserted YAML node, or NULL on failure + * + * This function inserts a reference to an existing anchor into a parent mapping node. + * The reference allows the original anchor's value to be reused without duplication. + * + * Key behaviors: + * - Parent node must be a mapping (or NULL, which is converted to a mapping) + * - Creates a new key-value pair where the value is a reference to the anchor + * - If key is NULL, attempts to create a referenced duplicate of the anchor's value + * + * Anchor resolution: + * - Searches the provided document's anchor list for a matching anchor name + * - Returns NULL if the anchor is not found or the document is invalid + * + * Example usage: + * \code + * yaml_t parent_map = yaml_new(); + * yaml_set_mapping(parent_map, NULL); + * + * yaml_t doc = yaml_new(); + * // Assume doc contains an anchor named "shared_value" + * + * // Insert reference to anchor under key "ref_key" + * yaml_insert_reference(parent_map, "ref_key", 0, "shared_value", doc); + * \endcode + */ +yaml_t yaml_insert_reference(yaml_t yaml, const char* key, unsigned int index, const char* anchor, yaml_t doc) +{ + yaml_t insert = NULL; + yaml_t ref = NULL; + + // Validate input parameters + if (!yaml) return NULL; + if (!anchor) return NULL; + if (!doc) return NULL; + + // Ensure document is a valid anchor container + if (doc->ktype != YAML_KTYPE_ANCHOR) return NULL; + + // Convert parent to mapping type if currently null + if (yaml->vtype == YAML_TYPE_NULL) + { + yaml->vtype = YAML_TYPE_MAPPING; + } + // Ensure parent is a mapping + else if (yaml->vtype == YAML_TYPE_MAPPING) + { + // Do nonthing + } + else + { + return NULL; + } + + // Locate the referenced anchor in the document + ref = yaml_match_anchor(doc->anchor, anchor, strlen(anchor)); + if (!ref) return NULL; + + if (key) + { + // Create a new node with the specified key referencing the anchor + insert = yaml_create(); + if (!yaml_set_key(insert, key)) goto FAIL; + + insert->vtype = YAML_TYPE_REFERENCE; + insert->value.child_ = ref; + } + else + { + // Create a duplicate node with reference semantics + yaml_t copy = yaml_duplicate(ref, YAML_F_RECURSE | YAML_F_COMPLEX | YAML_F_REFERENCE, NULL); + if (!copy) return NULL; + + insert = copy->value.child_; + + copy->value.child_ = NULL; + yaml_delete(copy); + } + + // Attach the new reference node to the parent mapping + if (!yaml_attach(yaml, index, insert)) goto FAIL; + + return insert; + +FAIL: + // Clean up on failure + yaml_delete(insert); + return NULL; +} + +/** + * \brief Remove a YAML node by key or index + * \param[in] yaml: Pointer to the parent YAML node (mapping or sequence) + * \param[in] key: Key of the node to remove (for mappings), or NULL (for sequences) + * \param[in] index: Index of the node to remove (relative to nodes with matching key) + * \return YAML_TRUE on success, YAML_FALSE on failure + * + * This function removes a YAML node from its parent container based on either a key + * (for mappings) or an index (for sequences). For mappings, the index is relative to + * the position of nodes with the specified key. + * + * Key behaviors: + * - For sequences: key should be NULL, and the node at the specified index is removed. + * - For mappings: the node with the specified key at the given index (among matches) + * is removed. If multiple nodes have the same key, index determines which occurrence + * to remove. + * + * Example usage: + * \code + * // Remove element at index 2 from a sequence + * yaml_remove(seq, NULL, 2); + * + * // Remove the first node with key "name" from a mapping + * yaml_remove(map, "name", 0); + * + * // Remove the second node with key "option" from a mapping + * yaml_remove(map, "option", 1); + * \endcode + */ +int yaml_remove(yaml_t yaml, const char* key, unsigned int index) +{ + yaml_t remove = NULL; + yaml_t child = NULL; + + // Validate input parameters + if (!yaml) return YAML_FALSE; + + // Handle sequence removal by index + if (!key) + { + // Detach the node at the specified index in the sequence + remove = yaml_dettach(yaml, index); + } + // Handle mapping removal by key and index + else + { + // Ensure the parent is a mapping + if (yaml->vtype != YAML_TYPE_MAPPING) return YAML_FALSE; + if (index >= yaml->size) return YAML_FALSE; + + // Check the first node in the mapping + child = yaml_iterator_to(yaml, 0); + if (child->ktype == YAML_KTYPE_STRING && !strcmp(child->key, key)) + { + // Remove the first node if it matches + if (index == 0) remove = yaml_dettach(yaml, 0); + // Adjust index for subsequent search + else index--; + } + + // Search remaining nodes for matching keys + if (!remove) + { + for (unsigned int i = 0; i < yaml->size - 1; i++) + { + // Get the next node in the mapping + child = yaml_iterator_to(yaml, i)->next; + + if (child->ktype == YAML_KTYPE_STRING && !strcmp(child->key, key)) + { + if (index == 0) + { + // Detach and remove the node at the current position + remove = yaml_dettach(yaml, i + 1); + break; + } + else + index--; // Decrement index for next match + } + } + } + } + + // Check if a node was found and detached + if (!remove) return YAML_FALSE; + + // Clean up the removed node and its children + yaml_delete(remove); + + return YAML_TRUE; +} + +/** + * \brief Get the index of a node in a YAML node (by key or index) + * \param[in] yaml: Pointer to the parent YAML node (sequence, mapping or document) + * \param[in] key: Key of the node to find (for mappings), or NULL (for sequences) + * \param[in] index: Index of the node to find (used for multi-match scenarios) + * \return The index of the node if found, or YAML_INV_INDEX on failure + * + * This function retrieves the linear index of a child node within a parent container + * (sequence, mapping, or document). For mappings, the search is filtered by key, and + * the index parameter specifies which occurrence of the key to return. + * + * Key behaviors: + * - For sequences: key should be NULL, and the function returns the given index directly + * - For mappings: returns the index of the Nth node with the specified key (0-based) + * - For documents: key must be NULL, and the function returns the given index directly + * + * Example usage: + * \code + * // Get index of 2nd element in a sequence (should return 1) + * unsigned int idx = yaml_get_index(seq, NULL, 1); + * + * // Get index of first node with key "name" in a mapping + * idx = yaml_get_index(map, "name", 0); + * + * // Get index of second node with key "option" in a mapping + * idx = yaml_get_index(map, "option", 1); + * \endcode + */ +unsigned int yaml_get_index(yaml_t yaml, const char* key, unsigned int index) +{ + unsigned int i = 0; + yaml_t child = NULL; + + // Validate input parameters + if (!yaml) return YAML_INV_INDEX; + + // Check for invalid key usage + if (key && (yaml->vtype == YAML_TYPE_SEQUENCE || yaml->vtype == YAML_TYPE_DOCUMENT)) return YAML_INV_INDEX; + + // Ensure parent is a valid container type + if (yaml->vtype != YAML_TYPE_SEQUENCE && + yaml->vtype != YAML_TYPE_MAPPING && + yaml->vtype != YAML_TYPE_DOCUMENT) return YAML_INV_INDEX; + + // Iterate through all child nodes + for (i = 0; i < yaml->size; i++) + { + child = yaml_iterator_to(yaml, i); + + // Check if the current node matches the criteria + if (!key || (child->ktype == YAML_KTYPE_STRING && !strcmp(child->key, key))) + { + // If this is the Nth occurrence of the key, return its index + if (index == 0) return i; + + // Decrement the index counter for subsequent matches + index--; + } + } + + // Node not found + return YAML_INV_INDEX; +} + +/** + * \brief Get the index of a complex node in a YAML mapping node + * \param[in] yaml: Pointer to the parent YAML mapping node + * \param[in] key: Pointer to the complex YAML node to find + * \return The index of the complex node if found, or YAML_INV_INDEX on failure + * + * This function retrieves the linear index of a child node within a parent mapping + * where the key is a complex YAML node (e.g., sequence or mapping). The comparison + * is performed using deep structural matching of the complex key nodes. + * + * Key behaviors: + * - Parent node must be a mapping (YAML_TYPE_MAPPING) + * - Searches for a key that is a complex node (YAML_KTYPE_COMPLEX) + * - Uses yaml_compare() with YAML_F_COMPLEX flag for deep comparison + * - Returns the first matching index, or YAML_INV_INDEX if not found + * + * Example usage: + * \code + * yaml_t map = yaml_new(); + * yaml_set_mapping(map, NULL); + * + * // Create a complex key (sequence) + * yaml_t complex_key = yaml_new(); + * yaml_set_sequence(complex_key, NULL); + * + * // Add key-value pair to map + * yaml_insert_mapping(map, NULL, 0, complex_key); + * + * // Get index of the complex key + * unsigned int idx = yaml_get_index_complex(map, complex_key); + * \endcode + */ +unsigned int yaml_get_index_complex(yaml_t yaml, yaml_t key) +{ + unsigned int i = 0; + yaml_t child = NULL; + + // Validate input parameters + if (!yaml) return YAML_INV_INDEX; + if (!key) return YAML_INV_INDEX; + + // Ensure parent is a mapping + if (yaml->vtype != YAML_TYPE_MAPPING) return YAML_INV_INDEX; + + // Iterate through all child nodes + for (i = 0; i < yaml->size; i++) + { + child = yaml_iterator_to(yaml, i); + + // Check if the current node has a complex key and matches the target + if (child->ktype == YAML_KTYPE_COMPLEX && yaml_compare(child->complex, key, YAML_F_COMPLEX)) + { + return i; + } + } + + // Node not found + return YAML_INV_INDEX; +} + +/** + * \brief Get a child node of a YAML node (by key or index) + * \param[in] yaml: Pointer to the parent YAML node (sequence, mapping or document) + * \param[in] key: Key of the child node to find (for mappings), or NULL (for sequences) + * \param[in] index: Index of the child node to find (used for multi-match scenarios) + * \return Pointer to the child node if found, or NULL on failure + * + * This function retrieves a child node from a parent container (sequence, mapping, or document) + * based on either a key (for mappings) or an index (for sequences and documents). For mappings, + * the index parameter specifies which occurrence of the key to return in case of duplicates. + * + * Key behaviors: + * - For sequences: key should be NULL, and the function returns the node at the given index. + * - For mappings: returns the Nth node with the specified key (0-based). + * - For documents: key must be NULL, and the function returns the node at the given index. + * + * Example usage: + * \code + * // Get second element from a sequence (index 1) + * yaml_t element = yaml_get_child(seq, NULL, 1); + * + * // Get first node with key "name" from a mapping + * yaml_t name_node = yaml_get_child(map, "name", 0); + * + * // Get second node with key "option" from a mapping + * yaml_t option_node = yaml_get_child(map, "option", 1); + * \endcode + */ +yaml_t yaml_get_child(yaml_t yaml, const char* key, unsigned int index) +{ + unsigned int i = 0; + yaml_t child = NULL; + + // Validate input parameters + if (!yaml) return NULL; + + // Check for invalid key usage + if (key && (yaml->vtype == YAML_TYPE_SEQUENCE || yaml->vtype == YAML_TYPE_DOCUMENT)) return NULL; + + // Ensure parent is a valid container type + if (yaml->vtype != YAML_TYPE_SEQUENCE && + yaml->vtype != YAML_TYPE_MAPPING && + yaml->vtype != YAML_TYPE_DOCUMENT) return NULL; + + // Iterate through all child nodes + for (i = 0; i < yaml->size; i++) + { + child = yaml_iterator_to(yaml, i); + + // Check if the current node matches the criteria + if (!key || (child->ktype == YAML_KTYPE_STRING && !strcmp(child->key, key))) + { + // If this is the Nth occurrence of the key, return the node + if (index == 0) break; + + // Decrement the index counter for subsequent matches + index--; + } + } + // Node not found + if (i >= yaml->size) return NULL; + + return child; +} + +/** + * \brief Get a complex child node of a YAML mapping node + * \param[in] yaml: Pointer to the parent YAML mapping node + * \param[in] key: Pointer to the complex YAML node to find + * \return Pointer to the complex child node if found, or NULL on failure + * + * This function retrieves a child node from a parent mapping where the key is a complex + * YAML node (e.g., a sequence or another mapping). The comparison is performed using + * deep structural matching of the complex key nodes. + * + * Key behaviors: + * - Parent node must be a mapping (YAML_TYPE_MAPPING) + * - Searches for a key that is a complex node (YAML_KTYPE_COMPLEX) + * - Uses yaml_compare() with YAML_F_COMPLEX flag for deep comparison + * - Returns the first matching child node, or NULL if not found + * + * Example usage: + * \code + * yaml_t map = yaml_new(); + * yaml_set_mapping(map, NULL); + * + * // Create a complex key (sequence) + * yaml_t complex_key = yaml_new(); + * yaml_set_sequence(complex_key, NULL); + * + * // Add key-value pair to map + * yaml_insert_mapping(map, NULL, 0, complex_key); + * + * // Retrieve the child node with the complex key + * yaml_t child = yaml_get_child_complex(map, complex_key); + * \endcode + */ +yaml_t yaml_get_child_complex(yaml_t yaml, yaml_t key) +{ + unsigned int i = 0; + yaml_t child = NULL; + + // Validate input parameters + if (!yaml) return NULL; + if (!key) return NULL; + + // Ensure parent is a mapping and not a sequence or document + if (yaml->vtype == YAML_TYPE_SEQUENCE || yaml->vtype == YAML_TYPE_DOCUMENT) return NULL; + + // Iterate through all child nodes in the mapping + for (i = 0; i < yaml->size; i++) + { + child = yaml_iterator_to(yaml, i); + + // Check if the current node has a complex key and matches the target + if (child->ktype == YAML_KTYPE_COMPLEX && yaml_compare(child->complex, key, YAML_F_COMPLEX)) + { + return child; + } + } + + // No matching complex key found + return NULL; +} + +/** + * \brief Compare two YAML nodes with specified comparison flags + * \param[in] yaml: Pointer to the first YAML node to compare + * \param[in] cmp: Pointer to the second YAML node to compare + * \param[in] flag: Comparison flags (YAML_F_* constants) + * \return YAML_TRUE if nodes are equal, YAML_FALSE otherwise + * + * This function compares two YAML nodes based on their key type, value type, and + * specified comparison flags. It recursively checks child nodes if the flag indicates + * recursion, and compares keys and values according to the node's key and value types. + * + * Key behaviors: + * - Checks if nodes are the same pointer, returning true if so. + * - Compares key types (string, complex, anchor) and values (bool, int, float, string, + * date, sequence, mapping, document, reference, complex key) based on flags. + * - For complex keys and sequences/mappings/documents, recursively compares child nodes + * if the YAML_F_RECURSE flag is set. + * + * Example usage: + * \code + * yaml_t node1 = yaml_new(); + * yaml_set_int(node1, 42); + * + * yaml_t node2 = yaml_new(); + * yaml_set_int(node2, 42); + * + * int result = yaml_compare(node1, node2, YAML_F_NOKEY); + * if (result == YAML_TRUE) { + * // Nodes are equal + * } + * \endcode + */ +int yaml_compare(yaml_t yaml, yaml_t cmp, int flag) +{ + // Validate input nodes + if (!yaml || !cmp) return YAML_FALSE; + + // If nodes are the same pointer, they are equal + if (yaml == cmp) return YAML_TRUE; + + // Compare key types + if (yaml->ktype != cmp->ktype) return YAML_FALSE; + + // Compare value types + if (yaml->vtype != cmp->vtype) return YAML_FALSE; + + // Compare keys based on key type and flags + if (yaml->ktype == YAML_KTYPE_STRING) + { + if (!(flag & YAML_F_NOKEY)) + { + if (!yaml->key && !cmp->key) ; + else if (yaml->key && !cmp->key) return YAML_FALSE; + else if (!yaml->key && cmp->key) return YAML_FALSE; + else + { + if (strcmp(yaml->key, cmp->key) != 0) return YAML_FALSE; + } + } + } + else if (yaml->ktype == YAML_KTYPE_COMPLEX) + { + if (!(flag & YAML_F_NOKEY) && (flag & YAML_F_COMPLEX)) + { + if (yaml_compare(yaml->complex, cmp->complex, flag) == YAML_FALSE) return YAML_FALSE; + } + } + else if (yaml->ktype == YAML_KTYPE_ANCHOR) + { + if (flag & YAML_F_ANCHOR) + { + if (yaml->anchor != cmp->anchor) + { + if (!yaml->anchor || !cmp->anchor) return YAML_FALSE; + + if (yaml->anchor->size != cmp->anchor->size) return YAML_FALSE; + + if (yaml->anchor->array != cmp->anchor->array) + { + if (!yaml->anchor->array || !cmp->anchor->array) return YAML_FALSE; + + for (int i = 0; i < yaml->anchor->size; i++) + { + if (yaml_compare(yaml->anchor->array[i], cmp->anchor->array[i], flag) == YAML_FALSE) + { + return YAML_FALSE; + } + } + } + } + } + } + else return YAML_FALSE; + + // Compare values based on value type + if (yaml->vtype == YAML_TYPE_BOOL) + { + if (yaml->value.bool_ != cmp->value.bool_) return YAML_FALSE; + } + else if (yaml->vtype == YAML_TYPE_INT) + { + if (yaml->value.int_ != cmp->value.int_) return YAML_FALSE; + } + else if (yaml->vtype == YAML_TYPE_FLOAT) + { + if (yaml->value.float_ != cmp->value.float_) return YAML_FALSE; + } + else if (yaml->vtype == YAML_TYPE_STRING) + { + if (strcmp(yaml->value.string_, cmp->value.string_) != 0) return YAML_FALSE; + } + else if (yaml->vtype == YAML_TYPE_DATE) + { + if (memcmp(&yaml->value.date, &cmp->value.date, sizeof(DATE)) != 0) return YAML_FALSE; + } + else if (yaml->vtype == YAML_TYPE_SEQUENCE || + yaml->vtype == YAML_TYPE_MAPPING || + yaml->vtype == YAML_TYPE_DOCUMENT) + { + if (yaml->size != cmp->size) return YAML_FALSE; + + if (flag & YAML_F_RECURSE) + { + for (int i = 0; i < yaml->size; i++) + { + yaml_t t0 = yaml_iterator_to(yaml, i); + yaml_t t1 = yaml_iterator_to(cmp, i); + + if (yaml_compare(t0, t1, flag) == YAML_FALSE) + { + return YAML_FALSE; + } + } + } + } + else if (yaml->vtype == YAML_TYPE_REFERENCE) + { + if (flag & YAML_F_REFERENCE) + { + if (yaml_compare(yaml->value.child_, cmp->value.child_, flag) == YAML_FALSE) return YAML_FALSE; + } + } + else if (yaml->vtype == YAML_TYPE_COMPLEX_KEY) + { + if (!(flag & YAML_F_NOKEY) && (flag & YAML_F_COMPLEX)) + { + if (yaml_compare(yaml->value.child_, cmp->value.child_, flag) == YAML_FALSE) return YAML_FALSE; + } + } + else return YAML_FALSE; + + // Compare aliases if flag is set + if (yaml->alias) + { + if (flag & YAML_F_ANCHOR) + { + if (strcmp(yaml->alias, cmp->alias) != 0) return YAML_FALSE; + } + } + + return YAML_TRUE; +} + +/** + * \brief Duplicate a YAML node with specified flags and parameter + * \param[in] yaml: Pointer to the YAML node to duplicate + * \param[in] flag: Duplication flags (YAML_F_* constants) + * \param[in] para: Parameter for anchor handling (ANCHOR pointer) + * \return Pointer to the duplicated YAML node, or NULL on failure + * + * This function duplicates a YAML node according to the provided flags. It handles + * different key types (string, complex, anchor) and value types (bool, int, float, + * string, date, sequence, mapping, document, reference, complex key). The function + * recursively duplicates child nodes if the YAML_F_RECURSE flag is set and manages + * anchors and aliases as per the YAML_F_ANCHOR flag. + * + * Key behaviors: + * - For key duplication, it copies string keys, recursively duplicates complex keys, + * and manages anchor structures. + * - For value duplication, it copies simple values (bool, int, float, date), strings, + * and recursively duplicates sequences, mappings, documents, and referenced nodes. + * - The para parameter is used for anchor handling, typically passing an anchor + * structure to manage anchor arrays. + * + * Example usage: + * \code + * yaml_t original = yaml_new(); + * yaml_set_int(original, 42); + * + * yaml_t duplicated = yaml_duplicate(original, YAML_F_RECURSE | YAML_F_NOKEY, NULL); + * if (duplicated) { + * // The node has been successfully duplicated + * } + * \endcode + */ +static yaml_t yaml_duplicate(yaml_t yaml, int flag, void *para) +{ + yaml_t copy, cptr, nptr = NULL, child; + + // Validate input node + if (!yaml) return NULL; + + /* Create a backup empty yaml object */ + copy = yaml_create(); + if (!copy) return NULL; + + // Duplicate key based on key type and flags + if (yaml->ktype == YAML_KTYPE_STRING) + { + if (!(flag & YAML_F_NOKEY)) + { + /* If there is a key, copy the key */ + if (yaml->key) + { + copy->key = yaml_strdup(yaml->key, strlen(yaml->key)); + if (!copy->key) goto FAIL; + } + } + } + else if (yaml->ktype == YAML_KTYPE_COMPLEX) + { + if (!(flag & YAML_F_NOKEY) && (flag & YAML_F_COMPLEX)) + { + if (yaml->complex) + { + copy->complex = yaml_duplicate(yaml->complex, YAML_F_COMPLEX, para); + if (!copy->complex) goto FAIL; + } + } + } + else if (yaml->ktype == YAML_KTYPE_ANCHOR) + { + if (flag & YAML_F_ANCHOR) + { + if (para) goto FAIL; + + copy->anchor = (ANCHOR *)malloc(sizeof(ANCHOR)); + if (!copy->anchor) goto FAIL; + + memset(copy->anchor, 0, sizeof(ANCHOR)); + + yaml_t *array = (yaml_t *)realloc(copy->anchor->array, sizeof(yaml_t) * copy->anchor->capacity); + if (!array) goto FAIL; + + copy->anchor->array = array; + copy->anchor->capacity = yaml->anchor->capacity; + + para = copy->anchor; + } + } + else goto FAIL; + + // Duplicate value based on value type + if (yaml->vtype == YAML_TYPE_BOOL || + yaml->vtype == YAML_TYPE_INT || + yaml->vtype == YAML_TYPE_FLOAT || + yaml->vtype == YAML_TYPE_DATE) + { + copy->value = yaml->value; + } + /* If it is a string type, copy the string */ + else if (yaml->vtype == YAML_TYPE_STRING) + { + if (yaml->value.string_) + { + copy->value.string_ = yaml_strdup(yaml->value.string_, yaml->size); + if (!copy->value.string_) goto FAIL; + } + } + /* walk the ->next chain for the child. */ + else if (yaml->vtype == YAML_TYPE_SEQUENCE || + yaml->vtype == YAML_TYPE_MAPPING || + yaml->vtype == YAML_TYPE_DOCUMENT) + { + if (flag & YAML_F_RECURSE) + { + cptr = yaml->value.child_; + while (cptr) + { + /* copy (with recurse) each yaml in the ->next chain */ + child = yaml_duplicate(cptr, flag, para); + if (!child) goto FAIL; + + /* if newitem->child already set, then crosswire ->prev and ->next and move on */ + if (nptr) + { + nptr->next = child; + nptr = child; + } + /* set newitem->child and move to it */ + else + { + copy->value.child_ = child; + nptr = child; + } + + cptr = cptr->next; + } + } + } + else if (yaml->vtype == YAML_TYPE_REFERENCE) + { + if (flag & YAML_F_REFERENCE) + { + copy->value.child_ = yaml_duplicate(yaml->value.child_, flag, para); + if (!copy->value.child_) goto FAIL; + } + } + else if (yaml->vtype == YAML_TYPE_COMPLEX_KEY) + { + if (!(flag & YAML_F_NOKEY) && (flag & YAML_F_COMPLEX)) + { + copy->value.child_ = yaml_duplicate(yaml->value.child_, flag, para); + if (!copy->value.child_) goto FAIL; + } + } + else goto FAIL; + + // Handle aliases and anchors + if (yaml->alias) + { + if (flag & YAML_F_ANCHOR) + { + if (!para) goto FAIL; + + copy->alias = yaml_strdup(yaml->alias, strlen(yaml->alias)); + if (!copy->alias) goto FAIL; + + ANCHOR *anchor = para; + + anchor->array[anchor->size++] = copy; + } + } + + copy->vtype = yaml->vtype; + copy->size = yaml->size; + + return copy; + +FAIL: + yaml_delete(copy); + return NULL; +} + +/** + * \brief Copy a YAML node with specified flags + * \param[in] yaml: Pointer to the YAML node to copy + * \param[in] flag: Copy flags (YAML_F_* constants) + * \return Pointer to the copied YAML node, or NULL on failure + * + * This function is a wrapper around yaml_duplicate, which duplicates a YAML node + * according to the provided flags. It simplifies the call to yaml_duplicate by setting + * the parameter for anchor handling to NULL. + * + * Key behaviors: + * - Calls yaml_duplicate with the given YAML node, flags, and a NULL parameter for + * anchor handling. + * - Returns the duplicated YAML node on success or NULL on failure. + * + * Example usage: + * \code + * yaml_t original = yaml_new(); + * yaml_set_string(original, "original_value"); + * + * yaml_t copied = yaml_copy(original, YAML_F_NOKEY); + * if (copied) { + * // The node has been successfully copied + * } + * \endcode + */ +yaml_t yaml_copy(yaml_t yaml, int flag) +{ + return yaml_duplicate(yaml, flag, NULL); +} + +/** + * \brief Add a YAML node to an anchor array + * \param[in] anchor: Pointer to the anchor structure + * \param[in] yaml: Pointer to the YAML node to add + * \return Pointer to the added YAML node, or NULL on failure + * + * This function adds a YAML node to an anchor array within an anchor structure. + * It first checks if the anchor and the YAML node are valid. If the anchor array's + * size plus one exceeds its current capacity, it expands the array to accommodate + * the new node. Then it adds the YAML node to the anchor array and returns the + * added YAML node, or NULL if any operation fails. + * + * Memory management: + * - When expanding the anchor array, it uses realloc to resize the array. + * - The function assumes that the anchor structure has members'size' and 'capacity' + * to manage the array, and 'array' which is a pointer to an array of YAML nodes. + * + * Example usage: + * \code + * ANCHOR anchor; + * anchor.size = 0; + * anchor.capacity = 1; + * anchor.array = (yaml_t *)malloc(sizeof(yaml_t) * anchor.capacity); + * + * yaml_t node = yaml_new(); + * yaml_set_string(node, "node_value"); + * + * yaml_t added_node = yaml_add_anchor(&anchor, node); + * if (added_node) { + * // The node has been successfully added to the anchor array + * } + * \endcode + */ +static yaml_t yaml_add_anchor(ANCHOR *anchor, yaml_t yaml) +{ + if (!anchor || !yaml) return NULL; + + // Expand anchor array capacity if necessary + if (anchor->size + 1 > anchor->capacity) + { + unsigned int capacity = pow2gt(anchor->size + 1); + yaml_t *array = (yaml_t *)realloc(anchor->array, sizeof(yaml_t) * capacity); + if (!array) return NULL; + anchor->array = array; + anchor->capacity = capacity; + } + + // Add yaml node to anchor array + anchor->array[anchor->size++] = yaml; + + return yaml; +} + +/** + * \brief Compare two strings up to n characters and check for exact length match + * \param[in] s1: First string to compare + * \param[in] s2: Second string to compare + * \param[in] n: Maximum number of characters to compare + * \return 0 if s1 matches s2 exactly up to n characters and s1 ends at n, otherwise difference + * + * This function compares the first n characters of two strings. It returns zero only if: + * 1. The first n characters of both strings are identical. + * 2. The first string (s1) has exactly n characters (i.e., s1[n] is '\0'). + * + * Key behaviors: + * - Compares characters until it finds a difference, reaches n characters, or hits a null terminator. + * - Returns zero only if both strings match up to n characters and s1 is exactly n characters long. + * - Returns the ASCII difference of the first mismatched character otherwise. + * + * Example usage: + * \code + * strnwcmp("abc", "abc", 3); // Returns 0 (s1 is "abc" and s2 is "abc") + * strnwcmp("abc", "abcd", 3); // Returns 0 (s1 is "abc" and s2 is "abcd") + * strnwcmp("abcd", "abc", 3); // Returns 'd' (100) - '\0' (0) = 100 + * strnwcmp("abc", "abd", 3); // Returns 'c' (99) - 'd' (100) = -1 + * \endcode + */ +int strnwcmp(const char *s1, const char *s2, unsigned int n) +{ + unsigned int i = 0; + + // Compare characters up to n or until a difference is found + while (i < n && *s1 && *s1 == *s2) + { + ++s1; + ++s2; + ++i; + } + + // Return 0 only if both strings matched exactly up to n and s1 ends at n + if (i == n && *s1 == 0) + { + return 0; + } + else + { + return *s1 - *s2; + } +} + +/** + * \brief Match a YAML node in an anchor array by alias prefix + * \param[in] anchor: Pointer to the anchor structure + * \param[in] base: Base string to match against aliases + * \param[in] length: Length of the base string to match + * \return Pointer to the matched YAML node, or NULL if not found + * + * This function searches through an anchor array within an anchor structure to find a YAML node + * whose alias matches the provided base string up to the specified length. It first validates the + * input anchor, base string, and length. Then it iterates through the anchor array and checks each + * node's alias using strnwcmp to see if it matches the base string up to the given length. If a + * match is found, it returns a pointer to the matching YAML node; otherwise, it returns NULL. + * + * Memory management: + * - Assumes the anchor structure has an array of YAML nodes and manages its size and array pointer. + * - The function does not modify the anchor array but only reads from it. + * + * Example usage: + * \code + * ANCHOR anchor; + * // Initialize anchor structure and its array of YAML nodes + * // Assume some YAML nodes with aliases are added to the anchor array + * + * const char *base_str = "prefix"; + * unsigned int len = strlen(base_str); + * + * yaml_t matched_node = yaml_match_anchor(&anchor, base_str, len); + * if (matched_node) { + * // A matching YAML node was found in the anchor array + * } + * \endcode + */ +static yaml_t yaml_match_anchor(ANCHOR *anchor, const char *base, unsigned int length) +{ + if (!anchor || !base || length == 0) return NULL; + + if (!anchor->array || anchor->size == 0) return NULL; + + // Iterate through anchor array to find matching alias + for (int i = 0; i < anchor->size; i++) + { + if (anchor->array[i]->alias) + { + if (strnwcmp(anchor->array[i]->alias, base, length) == 0) return anchor->array[i]; + } + } + + return NULL; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// Dump functions +////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + char* address; /**< buffer base address */ + unsigned int size; /**< size of buffer */ + unsigned int end; /**< end of buffer used */ +} BUFFER; + +static int print_yaml(yaml_t yaml, BUFFER* buf, int depth, int flag); +static int print_string(yaml_t yaml, BUFFER* buf); +static int print_mapping(yaml_t yaml, BUFFER* buf, int depth, int flag); + +/** + * \brief confirm whether buf still has the required capacity, otherwise add capacity. + * \param[in] buf: buf handle + * \param[in] needed: required capacity + * \return 1 success or 0 fail + * + * This function checks if the buffer (buf) has enough space to accommodate the + * additional required capacity (needed). If the current buffer size is sufficient + * to hold the new required capacity (by comparing with the sum of current end + * position and needed capacity), it returns 1. Otherwise, it calculates a new + * appropriate size (using pow2gt function, assumed to round up to the next power of 2) + * and attempts to reallocate the buffer using realloc. If the reallocation is + * successful, it updates the buffer's size and address, and returns 1. If reallocation + * fails (realloc returns NULL), it returns 0. + * + * Memory management: + * - Calls realloc to resize the buffer if needed. If realloc fails, the original + * buffer remains unchanged and the function returns 0. + * - Updates the buffer's size and address if realloc is successful. + * + * Example usage: + * \code + * BUFFER buf; + * buf.address = (char *)malloc(100); + * buf.size = 100; + * buf.end = 50; + * int required_capacity = 80; + * int result = expansion(&buf, required_capacity); + * if (result) { + * // Buffer capacity updated successfully or already had enough capacity. + * } else { + * // Failed to expand the buffer. + * } + * \endcode + */ +static int expansion(BUFFER *buf, unsigned int needed) +{ + char* address; + int size; + if (!buf || !buf->address) return 0; + needed += buf->end; + if (needed <= buf->size) return 1; /* there is still enough space in the current buf */ + size = pow2gt(needed); + address = (char*)realloc(buf->address, size); + if (!address) return 0; + buf->size = size; + buf->address = address; + return 1; +} + +#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */ +#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */ +#define buf_end() (buf->address[buf->end]) /* obtain the tail of buf */ + +/** + * \brief Print a string to a buffer with proper escaping and quoting + * \param[in] str: Input string to print + * \param[in] buf: Buffer to write into + * \return 1 on success, 0 on failure (buffer allocation error) + * + * This function takes an input string and prints it to a buffer with appropriate + * escaping and quoting. It first checks for an existing quote prefix in the string. + * Then it scans the string to count the number of double quotes, single quotes, + * escape characters, and control characters. Based on the presence of quotes, it + * decides whether to use single or double quotes for the output. It calculates the + * required buffer size considering quotes, escapes, and control characters. Finally, + * it iterates through the string, escaping special characters and writing the + * appropriate characters to the buffer, including the opening and closing quotes. + * + * Memory management: + * - The function relies on the buf_append function to manage buffer expansion. + * - It calculates the required buffer size and attempts to append characters to the buffer. + * - If the buf_append function fails (indicating a buffer allocation error), it returns 0. + * + * Example usage: + * \code + * char input[] = "Hello, world! \n"; + * BUFFER buf; + * // Assume buf is properly initialized with an initial buffer + * int result = print_string_buffer(input, &buf); + * if (result) { + * // String was successfully printed to the buffer + * } else { + * // There was an error with buffer allocation during the printing process + * } + * \endcode + */ +static int print_string_buffer(const char* str, BUFFER* buf) +{ + const char* p; + char prefix = 0; // Quote prefix (single/double quote or none) + char quote = '\"'; // Default to double quotes + int len = 0; + int escape = 0, single_q = 0, double_q = 0, control = 0; + + /* Handle empty string */ + if (!str) return 1; + + // Check for existing quote prefix + if (*str == '\'' || *str == '\"') prefix = *str; + + /* Scan string to count special characters */ + p = str; + while (*p) + { + if (*p == '\"') + { + double_q++; + } + else if (*p == '\'') + { + single_q++; + } + else if (*p == '\\' || *p == '\b' || *p == '\f' || *p == '\n' || *p == '\r' || *p == '\t') /* escape character */ + { + escape++; + } + else if ((unsigned char)(*p) < ' ') /* control character */ + { + control++; + } + + p++; + len++; + } + + // Print as plain string if no quotes/prefix/escapes needed + if (prefix == 0 && escape == 0 && control == 0) + { + if (!buf_append(len)) return 0; + while (len--) buf_putc(*str++); + return 1; + } + + // Calculate required buffer size with quotes and escapes + len += 2; // Opening and closing quotes + len += escape; // Escape characters + len += (control * 5); // Control characters (as \uXXXX) + + // Choose quote type based on which is less common in the string + if (double_q != 0) quote = '\''; // Prefer single quotes if string contains double quotes + if (single_q != 0) quote = '\"'; // Prefer double quotes if string contains single quotes + + // Account for escaping the chosen quote character + if (quote == '\'') len += single_q; + else if (quote == '\"') len += double_q; + + if (!buf_append(len)) return 0; + buf_putc(quote); + + /* Process each character with proper escaping */ + p = str; + while (*p) + { + if ((unsigned char)(*p) >= ' ' && *p != '\\' && *p != quote) + { + buf_putc(*p++); // Print regular character + } + else + { + /* Escape special characters */ + buf_putc('\\'); + if (*p == '\\') buf_putc('\\'); + else if (*p == '\b') buf_putc('b'); + else if (*p == '\f') buf_putc('f'); + else if (*p == '\n') buf_putc('n'); + else if (*p == '\r') buf_putc('r'); + else if (*p == '\t') buf_putc('t'); + else if (*p == quote) buf_putc(quote); + else + { + // Encode control characters as \uXXXX + sprintf(&buf_end(), "u%04x", (unsigned char)(*p)); + buf->end += 5; + } + p++; + } + } + + buf_putc(quote); // Closing quote + + return 1; +} + +/** + * \brief Print a YAML string node to a buffer + * \param[in] yaml: YAML node containing the string + * \param[in] buf: Buffer to write into + * \return 1 on success, 0 on failure + * + * This function takes a YAML node that is assumed to hold a string value and prints + * that string to a buffer. It does this by extracting the string from the YAML node + * and passing it, along with the buffer, to the print_string_buffer function. The + * return value of print_string_buffer is then returned as the result of this function. + * + * Memory management: + * - Relies on the print_string_buffer function to manage buffer operations related + * to appending the string to the buffer. If print_string_buffer fails due to + * buffer allocation issues, this function will return 0. + * + * Example usage: + * \code + * yaml_t string_node; + * string_node.vtype = YAML_TYPE_STRING; + * string_node.value.string_ = "Sample string"; + * BUFFER buf; + * // Assume buf is properly initialized + * int result = print_string(&string_node, &buf); + * if (result) { + * // The YAML string node was successfully printed to the buffer + * } else { + * // There was an error during the printing process, likely buffer allocation + * } + * \endcode + */ +static int print_string(yaml_t yaml, BUFFER* buf) +{ + return print_string_buffer(yaml->value.string_, buf); +} + +/** + * \brief Print a YAML date node to a buffer in ISO 8601 format + * \param[in] yaml: YAML node containing the date + * \param[in] buf: Buffer to write into + * \return 1 on success, 0 on failure + * + * This function formats and prints a YAML date node to a buffer in ISO 8601 format. + * The date is printed in parts based on the flags set in the date structure: + * 1. The date part (YYYY-MM-DD) is always printed. + * 2. The time part (HH:MM:SS) is printed if DATEF_TIME flag is set. + * 3. Milliseconds (SS.FFF) are added if DATEF_MSEC flag is set. + * 4. UTC offset (+HH:MM) is added if DATEF_UTC flag is set. + * + * Memory management: + * - Uses buf_append to ensure buffer capacity before writing. + * - Returns 0 immediately if any buffer allocation fails. + * - Modifies buf->end to reflect the new buffer position after writes. + * + * Example usage: + * \code + * yaml_t date_node; + * date_node.vtype = YAML_TYPE_DATE; + * date_node.value.date.year = 2023; + * date_node.value.date.month = 10; + * date_node.value.date.day = 15; + * date_node.value.date.flag = DATEF_TIME | DATEF_UTC; + * date_node.value.date.hour = 14; + * date_node.value.date.minute = 30; + * date_node.value.date.second = 0; + * date_node.value.date.utcsign = 0; // Positive sign + * date_node.value.date.utchour = 8; + * date_node.value.date.utcminute = 0; + * + * BUFFER buf; + * // Initialize buffer... + * + * int result = print_date(&date_node, &buf); + * if (result) { + * // Buffer now contains "2023-10-15t14:30:00+08:00" + * } + * \endcode + */ +static int print_date(yaml_t yaml, BUFFER* buf) +{ + int len = 0; + char flag = yaml->value.date.flag; + + // Print date part (YYYY-MM-DD) + if (!buf_append(10)) return 0; + len = sprintf(&buf_end(), "%04d-%02d-%02d", + (int)yaml->value.date.year, + (int)yaml->value.date.month, + (int)yaml->value.date.day); + buf->end += len; + + // Print time part if present + if (flag & DATEF_TIME) + { + if (!buf_append(9)) return 0; + buf_putc('t'); + len = sprintf(&buf_end(), "%02d:%02d:%02d", + (int)yaml->value.date.hour, + (int)yaml->value.date.minute, + (int)yaml->value.date.second); + buf->end += len; + + // Print milliseconds if present + if (flag & DATEF_MSEC) + { + if (!buf_append(4)) return 0; + buf_putc('.'); + len = sprintf(&buf_end(), "%03d", (int)yaml->value.date.msec); + buf->end += len; + } + + // Print UTC offset if present + if (flag & DATEF_UTC) + { + if (!buf_append(6)) return 0; + buf_putc(yaml->value.date.utcsign ? '-' : '+'); + len = sprintf(&buf_end(), "%02d:%02d", + (int)yaml->value.date.utchour, + (int)yaml->value.date.utcminute); + buf->end += len; + } + } + + return 1; +} + +/** + * \brief Print a YAML number node to a buffer + * \param[in] yaml: YAML node containing the number + * \param[in] buf: Buffer to write into + * \return 1 on success, 0 on failure + * + * This function takes a YAML node that represents a number and prints it to a buffer. + * It first checks the type of the number (integer or floating point) and then formats + * and prints it to the buffer. Special cases like zero, NaN (Not a Number), and Infinity + * are handled separately. For floating point numbers, different formatting rules apply + * depending on the value's magnitude and whether it's an integer-like value. + * + * Memory management: + * - Uses buf_append to ensure there is enough space in the buffer before writing. + * - Updates the buffer's end position (buf->end) according to the length of the printed number. + * - Returns 0 if there is an issue with buffer allocation during the process. + * + * Example usage: + * \code + * yaml_t num_node; + * num_node.vtype = YAML_TYPE_FLOAT; + * num_node.value.float_ = 3.14; + * BUFFER buf; + * // Assume buf is properly initialized + * int result = print_number(&num_node, &buf); + * if (result) { + * // The number was successfully printed to the buffer + * } else { + * // There was an error during the printing process, likely buffer allocation + * } + * \endcode + */ +static int print_number(yaml_t yaml, BUFFER* buf) +{ + double num = yaml->value.float_; + int len = 0; + + /* Number is 0 */ + if (num == 0) + { + if (!buf_append(1)) return 0; + buf_putc('0'); + } + /* The number type is an integer type */ + else if (yaml->vtype == YAML_TYPE_INT) + { + if (!buf_append(20)) return 0; + len = sprintf(&buf_end(), "%d", (int)yaml->value.int_); + buf->end += len; + } + /* The number type is a floating point type */ + else if (yaml->vtype == YAML_TYPE_FLOAT) + { + if (!buf_append(64)) return 0; + /* use full transformation within bounded space */ + if (isnan(num)) + { + buf_putc('.'); + buf_putc('n'); + buf_putc('a'); + buf_putc('n'); + } + else if (isinf(num)) + { + if (num < 0) buf_putc('-'); + buf_putc('.'); + buf_putc('i'); + buf_putc('n'); + buf_putc('f'); + } + /* Format as fixed-point if integer-like and within range */ + else if (fabs(floor(num) - num) <= DBL_EPSILON && fabs(num) < 1.0e60) + { + len = sprintf(&buf_end(), "%.1lf", num); + } + /* Use exponential notation for very large/small numbers */ + else if (fabs(num) < 1.0e-6 || fabs(num) > 1.0e9) + { + len = sprintf(&buf_end(), "%e", num); + } + /* Default floating-point formatting */ + else + { + len = sprintf(&buf_end(), "%lf", num); + // Remove trailing zeros after decimal point + while (len > 0 && (&buf_end())[len-1] == '0' && (&buf_end())[len-2] != '.') len--; /* remove the invalid 0 in the decimal part */ + } + buf->end += len; + } + /* Not of number type */ + else return 0; + + return 1; +} + +/** + * \brief Print a YAML sequence node to the buffer. + * \param[in] yaml: Pointer to the YAML sequence node. + * \param[in] buf: Pointer to the buffer. + * \param[in] depth: The depth of the node in the YAML structure. + * \param[in] flag: Printing flags for the YAML node. + * \return 1 for success, 0 for failure (such as buffer allocation failure, etc.). + * + * This function prints a YAML sequence node to a buffer. It first checks if the sequence + * is empty. If not, it determines whether to use the streaming style based on the flag. + * In the streaming style, it directly prints the child nodes with appropriate commas. + * In the non-streaming style, it adds indentation according to the depth, prints a + * '-' indicator for each child node, and ensures proper newline characters are added. + * Finally, it adds the closing ']' character and necessary indentation in the streaming + * style. + * + * Memory management: + * - Relies on buf_append to ensure there is enough space in the buffer for writing. + * - Updates the buffer's end position (buf->end) as characters are added. + * - Returns 0 if there is an issue with buffer allocation during the process. + * + * Example usage: + * \code + * yaml_t seq_node; + * // Assume seq_node is a valid YAML sequence node with some child nodes. + * BUFFER buf; + * int depth = 0; + * int flag = YAML_F_DFLOW; + * // Initialize buf properly. + * int result = print_sequence(&seq_node, &buf, depth, flag); + * if (result) { + * // The YAML sequence node was successfully printed to the buffer. + * } else { + * // There was an error during the printing process, likely buffer allocation. + * } + * \endcode + */ +static int print_sequence(yaml_t yaml, BUFFER* buf, int depth, int flag) +{ + int i = 0, count = 0; + yaml_t child = yaml->value.child_; + + // If the sequence node has no children (empty array), return 1 for success. + if (!child) + { + return 1; + } + + // If the YAML_F_DFLOW flag is set (streaming style). + if (flag & YAML_F_DFLOW) + { + if (!buf_append(1)) return 0; + buf_putc('['); + } + else + { + // If not in streaming style and the buffer is not empty, add a newline character. + if (buf->end != 0) + { + if (!buf_append(1)) return 0; + buf_putc('\n'); + } + } + + // Traverse and print the child nodes of the sequence node. + while (child) + { + if (flag & YAML_F_DFLOW) + { + // In streaming style, directly print the child node. + if (!print_yaml(child, buf, depth + 1, flag)) return 0; + } + else + { + // In non-streaming style, add indentation according to the depth. + if (!buf_append(depth * 2)) return 0; + for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' '); + + // Print the indicator '-'. + if (!buf_append(2)) return 0; + buf_putc('-'); + buf_putc(' '); + + // Print the child node. + if (!print_yaml(child, buf, depth + 1, flag)) return 0; + + // If the last character in the buffer after printing the child node is not a newline character, add a newline character. + if (buf->address[buf->end - 1] != '\n') + { + if (!buf_append(1)) return 0; + buf_putc('\n'); + } + } + + // Move to the next child node. + child = child->next; + + // If there is a next child node and in streaming style, add a comma and a space. + if (child) + { + if (flag & YAML_F_DFLOW) + { + if (!buf_append(2)) return 0; + buf_putc(','); + buf_putc(' '); + } + } + } + + // If in streaming style, add the closing character ']' and add indentation if necessary. + if (flag & YAML_F_DFLOW) + { + if (buf->address[buf->end - 1] == '\n') + { + if (!buf_append(depth * 2)) return 0; + for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' '); + } + + if (!buf_append(1)) return 0; + buf_putc(']'); + } + + return 1; +} + +/** + * \brief Print the alias of a YAML node to the buffer. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] buf: Pointer to the buffer. + * \param[in] depth: The depth of the node in the YAML structure. + * \return 1 for success, 0 for failure (such as buffer allocation failure, etc.). + * + * This function prints the alias of a YAML node to the buffer, prepended with an '&' + * character. If the node is not a sequence or mapping, it adds a newline followed by + * indentation spaces based on the node's depth in the YAML structure. This ensures proper + * formatting for scalar values with aliases in block-style YAML. + * + * Example usage: + * \code + * yaml_t node; + * node.alias = "my_anchor"; + * node.vtype = YAML_TYPE_STRING; + * + * BUFFER buf; + * // Initialize buffer... + * + * int result = print_alias(&node, &buf, 2); + * if (result) { + * // Buffer now contains "&my_anchor\n " (for a string node at depth 2) + * } + * \endcode + */ +static int print_alias(yaml_t yaml, BUFFER* buf, int depth) +{ + if (!buf_append(1)) return 0; + buf_putc('&'); + + // Print the alias string with proper escaping and quoting + if (!print_string_buffer(yaml->alias, buf)) return 0; + + // Add newline and indentation for scalar values (non-sequence/mapping) + if (yaml->vtype != YAML_TYPE_SEQUENCE && yaml->vtype != YAML_TYPE_MAPPING) + { + if (!buf_append(depth * 2 + 1)) return 0; + buf_putc('\n'); + for (int i = 0; i < depth; i++) + { + buf_putc(' '); + buf_putc(' '); + } + } + + return 1; +} + +/** + * \brief Print a YAML mapping node to the buffer. + * \param[in] yaml: Pointer to the YAML mapping node. + * \param[in] buf: Pointer to the buffer. + * \param[in] depth: The depth of the node in the YAML structure. + * \param[in] flag: Printing flags for the YAML node. + * \return 1 for success, 0 for failure (such as buffer allocation failure, etc.). + * + * This function prints a YAML mapping node (key-value pairs) to the buffer in either + * block style or flow style based on the YAML_F_DFLOW flag. It handles both simple + * string keys and complex keys (sequences/mappings) by prefixing them with '?'. + * For each key-value pair, it ensures proper indentation, colons, and newlines according + * to the specified style and depth. + * + * Memory management: + * - Uses buf_append to ensure buffer capacity before writing. + * - Returns 0 immediately if any buffer allocation fails. + * - Modifies buf->end to reflect the new buffer position after writes. + * + * Example usage: + * \code + * yaml_t map_node; + * // Initialize map_node as a mapping with child nodes... + * + * BUFFER buf; + * // Initialize buffer... + * + * int result = print_mapping(&map_node, &buf, 1, 0); + * if (result) { + * // Buffer now contains the mapping formatted in block style: + * // key1: value1 + * // key2: value2 + * } + * \endcode + */ +static int print_mapping(yaml_t yaml, BUFFER* buf, int depth, int flag) +{ + int i; + yaml_t child = yaml->value.child_; + + // Handle empty mapping + if (!child) + { + return 1; + } + + // Flow style ({ ... }) + if (flag & YAML_F_DFLOW) + { + if (!buf_append(1)) return 0; + buf_putc('{'); + } + // Block style (indented with newlines) + else + { + if (buf->end != 0) + { + if (!buf_append(1)) return 0; + buf_putc('\n'); + } + } + + // Process each key-value pair + while (child) + { + if (flag & YAML_F_DFLOW) + { + // Flow style formatting (handled later) + } + else + { + // Block style indentation + if (!buf_append(depth * 2)) return 0; + for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' '); + } + + // Print key + if (child->ktype == YAML_KTYPE_STRING) + { + if (!print_string_buffer(child->key, buf)) return 0; + } + else if (child->ktype == YAML_KTYPE_COMPLEX) + { + yaml_t key = child->complex; + if (key->vtype == YAML_TYPE_SEQUENCE || key->vtype == YAML_TYPE_MAPPING) + { + if (!buf_append(1)) return 0; + buf_putc('?'); + if (!print_yaml(key, buf, depth + 1, flag)) return 0; + } + else + { + if (!buf_append(2)) return 0; + buf_putc('?'); + buf_putc(' '); + if (!print_yaml(key, buf, depth + 1, flag)) return 0; + } + + // Re-indent for value after complex key + if (!buf_append(depth * 2)) return 0; + for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' '); + } + + // Key-value separator + if (!buf_append(2)) return 0; + buf_putc(':'); + buf_putc(' '); + + // Handle aliases + if (child->alias) + { + if (!print_alias(child, buf, depth + 1)) return 0; + if (flag & YAML_F_DFLOW) + { + if (!buf_append(1)) return 0; + buf_putc(' '); + } + } + + // Print value + if (!print_yaml(child, buf, depth + 1, flag)) return 0; + + // Ensure proper newline in block style + if (flag & YAML_F_DFLOW) + { + + } + else + { + if (buf->address[buf->end - 1] != '\n') + { + if (!buf_append(1)) return 0; + buf_putc('\n'); + } + } + + // Move to next key-value pair + child = child->next; + + // Add comma in flow style if more pairs follow + if (child) + { + if (flag & YAML_F_DFLOW) + { + if (!buf_append(2)) return 0; + buf_putc(','); + buf_putc(' '); + } + } + } + + // Close flow style mapping + if (flag & YAML_F_DFLOW) + { + if (buf->address[buf->end - 1] == '\n') + { + if (!buf_append(depth * 2)) return 0; + for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' '); + } + + if (!buf_append(1)) return 0; + buf_putc('}'); + } + + return 1; +} + +/** + * \brief Print a YAML reference node to the buffer. + * \param[in] yaml: Pointer to the YAML reference node. + * \param[in] buf: Pointer to the buffer. + * \return 1 for success, 0 for failure (such as buffer allocation failure, etc.). + * + * This function is used to print a YAML reference node to the buffer. It first appends + * an asterisk (*) to the buffer. Then, it calls the print_string_buffer function to + * print the alias of the child node pointed to by the reference. If any buffer + * allocation fails during these operations, it returns 0. + * + * Memory management: + * - Relies on the buf_append function to add characters to the buffer. If buf_append + * fails (indicating a buffer allocation issue), the function returns 0. + * - Calls print_string_buffer which also manages buffer operations related to printing + * the alias string. If print_string_buffer fails, this function returns 0. + * + * Example usage: + * \code + * yaml_t ref_node; + * ref_node.vtype = YAML_TYPE_REFERENCE; + * ref_node.value.child_ = some_yaml_node_with_alias; + * + * BUFFER buf; + * // Assume buf is properly initialized. + * + * int result = print_reference(&ref_node, &buf); + * if (result) { + * // The YAML reference node was successfully printed to the buffer, likely in the form of "*alias". + * } else { + * // There was an error during the printing process, probably buffer allocation. + * } + * \endcode + */ +static int print_reference(yaml_t yaml, BUFFER* buf) +{ + if (!buf_append(1)) return 0; + buf_putc('*'); + return print_string_buffer(yaml->value.child_->alias, buf); +} + +/** + * \brief Print a YAML document node to the buffer. + * \param[in] yaml: Pointer to the YAML document node. + * \param[in] buf: Pointer to the buffer. + * \return 1 for success, 0 for failure (such as buffer allocation failure, etc.). + * + * This function is responsible for printing a YAML document node to the buffer. + * It first checks if the last character in the buffer is not a newline character. + * If so, it appends a newline character to the buffer. Then, it appends three hyphens + * ("---") to the buffer. Finally, it calls the print_yaml function with the YAML node, + * buffer, a depth of -1, and a flag of 0. If any buffer allocation fails during these + * operations, the function returns 0. + * + * Memory management: + * - Relies on the buf_append function to add characters to the buffer. If buf_append + * fails (indicating a buffer allocation issue), the function returns 0. + * + * Example usage: + * \code + * yaml_t doc_node; + * // Assume doc_node is a valid YAML document node. + * BUFFER buf; + * // Initialize buf properly. + * + * int result = print_document(&doc_node, &buf); + * if (result) { + * // The YAML document node was successfully printed to the buffer, starting with "---". + * } else { + * // There was an error during the printing process, likely buffer allocation. + * } + * \endcode + */ +static int print_document(yaml_t yaml, BUFFER* buf) +{ + if (buf->address[buf->end - 1] != '\n') + { + if (!buf_append(1)) return 0; + buf_putc('\n'); + } + + if (!buf_append(4)) return 0; + buf_putc('-'); + buf_putc('-'); + buf_putc('-'); + buf_putc('\n'); + return print_yaml(yaml, buf, -1, 0); +} + +/** + * \brief Print a YAML node to the buffer based on its type. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] buf: Pointer to the buffer. + * \param[in] depth: The depth of the node in the YAML structure (used for indentation in some cases). + * \param[in] flag: Printing flags for the YAML node. + * \return 1 for success, 0 for failure (such as buffer allocation failure, etc.). + * + * This function is a central dispatcher that prints a YAML node to the buffer according to its type. + * It handles all supported YAML data types, including scalars (null, bool, int, float, string, date), + * collections (sequence, mapping), references, and documents. Each type is processed by calling the + * corresponding print function. For example, sequences and mappings are printed with proper indentation + * based on their depth in the YAML structure. + * + * Example usage: + * \code + * yaml_t node; + * // Initialize node with some YAML value... + * + * BUFFER buf; + * // Initialize buffer... + * + * int result = print_yaml(&node, &buf, 0, 0); + * if (result) { + * // Node was successfully printed to the buffer + * } + * \endcode + */ +static int print_yaml(yaml_t yaml, BUFFER* buf, int depth, int flag) +{ + switch (yaml->vtype) + { + case YAML_TYPE_NULL: + { + /* Print "null" */ + if (!buf_append(4)) return 0; + buf_putc('n'); + buf_putc('u'); + buf_putc('l'); + buf_putc('l'); + break; + } + case YAML_TYPE_BOOL: + { + if (yaml->value.bool_ == YAML_FALSE) + { + /* Print "false" */ + if (!buf_append(5)) return 0; + buf_putc('f'); + buf_putc('a'); + buf_putc('l'); + buf_putc('s'); + buf_putc('e'); + } + else + { + /* Print "true" */ + if (!buf_append(4)) return 0; + buf_putc('t'); + buf_putc('r'); + buf_putc('u'); + buf_putc('e'); + } + break; + } + case YAML_TYPE_INT: + case YAML_TYPE_FLOAT: return print_number(yaml, buf); + case YAML_TYPE_STRING: return print_string(yaml, buf); + case YAML_TYPE_DATE: return print_date(yaml, buf); + case YAML_TYPE_SEQUENCE: return print_sequence(yaml, buf, depth, flag); + case YAML_TYPE_MAPPING: return print_mapping(yaml, buf, depth, flag); + case YAML_TYPE_REFERENCE: return print_reference(yaml, buf); + case YAML_TYPE_DOCUMENT: return print_document(yaml, buf); + } + return 1; +} + +/** + * \brief Convert a YAML node to a string representation and allocate memory for it. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] preset: Preset size for the buffer. If less than 1, it will be set to 1. + * \param[in] len: Pointer to an integer to store the length of the resulting string. + * \param[in] flag: Flags for the printing process. + * \return A pointer to the allocated string representing the YAML node, or NULL on failure. + * + * This function converts a YAML node into a string representation and allocates memory for the result. + * It first checks if the preset buffer size is valid and allocates memory for the buffer. If the YAML node + * is a document type, it iterates through its child nodes, printing each non-null child node and adding + * appropriate separators between them. For other node types, it directly calls print_yaml to print the node. + * After printing, it null-terminates the buffer and sets the length of the resulting string if the length + * pointer is not NULL. In case of any buffer allocation failure during the process, it frees the allocated + * buffer and returns NULL. + * + * Memory management: + * - Allocates memory for the buffer using malloc. + * - Frees the buffer in case of failure using free. + * - Updates the buffer's size, end position, and returns the pointer to the buffer's address as the result. + * + * Example usage: + * \code + * yaml_t node; + * // Initialize node with some YAML value... + * int length; + * char* result = yaml_dumps(&node, 100, &length, 0); + * if (result) { + * // result contains the string representation of the YAML node + * // length stores the length of the resulting string + * } + * \endcode + */ +char* yaml_dumps(yaml_t yaml, int preset, int* len, int flag) +{ + BUFFER p, *buf = &p; + + if (!yaml) return NULL; + + /* Allocate buffer and initialize */ + if (preset < 1) preset = 1; + p.address = (char*)malloc(preset); + if (!p.address) return NULL; + p.size = preset; + p.end = 0; + + if (yaml->vtype == YAML_TYPE_DOCUMENT) + { + yaml_t child = yaml->value.child_; + + while (child) + { + /* Start printing yaml */ + if (child->vtype != YAML_TYPE_NULL) + { + if (!print_yaml(child, &p, 0, flag)) goto FAIL; + } + + child = child->next; + if (child) + { + if (buf->address[buf->end - 1] != '\n') + { + if (!buf_append(1)) goto FAIL; + buf_putc('\n'); + } + + if (!buf_append(4)) goto FAIL; + buf_putc('-'), buf_putc('-'), buf_putc('-'), buf_putc('\n'); + } + } + } + else + { + /* Start printing yaml */ + if (!print_yaml(yaml, &p, 0, flag)) goto FAIL; + } + + /* At the end of the text */ + if (!buf_append(1)) goto FAIL; + buf_end() = 0; + + /* Output conversion length */ + if (len) *len = p.end; + + return p.address; + +FAIL: + free(p.address); + return NULL; +} + +/** + * \brief Dump a YAML node to a file. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] filename: Name of the file to write to. + * \return The number of characters written to the file, or -1 on failure. + * + * This function takes a YAML node and writes its string representation to a file. + * It first converts the YAML node to a string using yaml_dumps. If the conversion + * is successful, it opens the specified file in write mode. If the file is opened + * successfully, it writes the string to the file, closes the file, and frees the + * memory allocated for the string. It returns the number of characters written to + * the file, or -1 if any step fails (such as conversion failure, file open failure, etc.). + * + * Memory management: + * - Calls yaml_dumps which allocates memory for the string representation of the YAML node. + * - Frees the memory of the string after writing it to the file using free. + * - Opens and closes the file using fopen and fclose respectively. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * // Initialize my_yaml_node with some YAML data... + * char file_name[] = "output.yaml"; + * int written_count = yaml_file_dump(&my_yaml_node, file_name); + * if (written_count != -1) { + * // The YAML node was successfully dumped to the file, and written_count is the number of characters written. + * } else { + * // There was an error during the process, such as conversion or file operation failure. + * } + * \endcode + */ +int yaml_file_dump(yaml_t yaml, char* filename) +{ + FILE* f; + char* out; + int len; + + if (!yaml) return -1; + + /* Convert characters */ + out = yaml_dumps(yaml, 0, &len, 0); + if (!out) return -1; + + /* Open file */ + f = fopen(filename, "w"); + if (!f) + { + free(out); + return -1; + } + + /* Write the string to the file */ + fwrite(out, 1, len, f); + + /* Close file */ + fclose(f); + + /* free the sapce of string */ + free(out); + + return len; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// Loads functions +////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * \brief Target base type flags for YAML parser + * These flags define the fundamental parsing modes for different YAML value types. + * They determine how the parser interprets and processes the incoming text. + */ +#define YAML_PFLAG_TGTBASE_NULL 0 /* Null/undefined base type */ +#define YAML_PFLAG_TGTBASE_SINGLE 1 /* Single-quoted string ('...') */ +#define YAML_PFLAG_TGTBASE_DOUBLE 2 /* Double-quoted string ("...") */ +#define YAML_PFLAG_TGTBASE_SCALAR 3 /* Literal block scalar (|) - preserves line breaks */ +#define YAML_PFLAG_TGTBASE_FOLD 4 /* Folded block scalar (>) - converts line breaks to spaces */ +#define YAML_PFLAG_TGTBASE_SEQUENCE 5 /* Sequence/array enclosed in [] */ +#define YAML_PFLAG_TGTBASE_MAPPING 6 /* Mapping/object enclosed in {} */ + +/** + * \brief String block handling modifiers + * These flags control how line breaks and trailing spaces are processed in block scalars. + * They apply to both literal (|) and folded (>) block scalar types. + */ +// | style: +// 0: | - Preserve line breaks, strip one trailing line +// 1: |n - Preserve line breaks, indent by n spaces +// 2: |+ - Preserve line breaks and all trailing lines +// 3: |- - Strip all trailing line breaks +// > style: +// 0: > - Convert line breaks to spaces, strip one trailing line +// 1: >n - Convert line breaks to spaces, indent by n spaces +// 2: >+ - Convert line breaks to spaces, preserve trailing lines as spaces +// 3: >- - Convert line breaks to spaces, strip all trailing spaces +#define YAML_PFLAG_STREXTR_NULL 0 /* No special handling */ +#define YAML_PFLAG_STREXTR_NUM 1 /* Indentation specified by number */ +#define YAML_PFLAG_STREXTR_ADD 2 /* Preserve all trailing empty lines */ +#define YAML_PFLAG_STREXTR_SUB 3 /* Strip all trailing empty lines */ + +/** + * \brief Explicit type conversion flags + * These flags specify the target data type when explicitly converting YAML values. + * They correspond to YAML's built-in tag types (e.g., !!int, !!bool). + */ +#define YAML_PFLAG_TARGET_NONE 0 /* No explicit type conversion */ +#define YAML_PFLAG_TARGET_NULL 1 /* Null value (!!null) */ +#define YAML_PFLAG_TARGET_BOOL 2 /* Boolean value (!!bool) */ +#define YAML_PFLAG_TARGET_INT 3 /* Integer value (!!int) */ +#define YAML_PFLAG_TARGET_FLOAT 4 /* Floating-point value (!!float) */ +#define YAML_PFLAG_TARGET_STR 5 /* String value (!!str) */ +#define YAML_PFLAG_TARGET_SEQ 6 /* Sequence/array (!!seq) */ +#define YAML_PFLAG_TARGET_MAP 7 /* Mapping/object (!!map) */ +#define YAML_PFLAG_TARGET_BINARY 8 /* Binary data (!!binary) */ +#define YAML_PFLAG_TARGET_TIMESTAMP 9 /* Timestamp (!!timestamp) */ +#define YAML_PFLAG_TARGET_SET 10 /* Set (!!set) */ +#define YAML_PFLAG_TARGET_OMAP 11 /* Ordered map (!!omap) */ +#define YAML_PFLAG_TARGET_PAIRS 12 /* Pairs (!!pairs) */ + +typedef struct +{ + union + { + // Integer part + int int_; + struct + { + // The basic type of the target value + int tgtbase : 8; // YAML_PFLAG_TGTBASE_XXX + + // Extra attributes of the string + int strextr : 4; // YAML_PFLAG_STREXTR_XXX + + // Interval closure flag, such as quotation marks, square brackets, curly braces closure + int interval : 4; // 0 - Unclosed, 1 - Already closed + + // Takes effect when the extra attribute of the string is a number, records the number + int space_n : 8; // 0 - 255 ; 0 represents 256 + + // Force conversion target type + int target : 4; // YAML_PFLAG_TARGET_XXX + }; + } flag; + + // Sentinel, responsible for exploring the text forward + const char *sentinel; + + // Records the start and end positions of the valid value + const char *scope_s; + const char *scope_e; + + // Records the start and end positions of the valid part of each parsed line + const char *line_s; + const char *line_e; + + // Records the number of empty lines before each valid line parsed + int lines; + + // Child depth, if the current value needs to wrap, it is responsible for recording the depth that still belongs to the value range + int deep_chd; + + // Current depth, the current line depth when parsing each line + int deep_cur; + + ANCHOR *anchor; +} PARSER; + +static const char* parse_value(yaml_t yaml, const char* text, int deep, char token, PARSER *parser); + +/** + * \brief Skip over whitespace characters in the text. + * \param[in] in: Pointer to the text to skip. + * \return Pointer to the position after skipping whitespace. + */ +static const char* skip(const char* in) +{ + while (*in && (unsigned char)*in <= ' ' && *in != '\n') + { + in++; + } + + return in; +} + +/** + * \brief Skip backwards over whitespace characters in the text. + * \param[in] text: Pointer to the text to skip backwards. + * \param[in] base: Pointer to the base position to stop at. + * \return Pointer to the position after skipping backwards over whitespace. + */ +static const char* rskip(const char* text, const char* base) +{ + while ((text >= base) && (*text == ' ' || *text == '\r' || *text == '\t')) + { + text--; + } + return text; +} + +/** + * \brief Skip to the end of the line in the text. + * \param[in] in: Pointer to the text to skip. + * \return Pointer to the position at the end of the line. + */ +static const char* skip_ln(const char* in) +{ + while (*in && *in != '\n') in++; + + return in; +} + +/** + * \brief Find the position of a character in the text up to a newline. + * \param[in] str: Pointer to the text to search. + * \param[in] ch: The character to find. + * \return Pointer to the position of the character if found, or NULL if not found. + */ +static char *linechr(const char *str, int ch) +{ + while (*str != 0 && *str != '\n' && *str != ch) + { + str++; + } + if (*str == ch) + { + return (char *) str; + } + else + { + return NULL; + } +} + +/** + * \brief Check if the given character is a line break. + * \param[in] str: Pointer to the character to check. + * \return 1 if it's a line break (\n or \r\n), 2 if it's \r\n, 0 if not a line break. + */ +static int isln(char *str) +{ + if (str[0] == '\n') return 1; + else if (str[0] == '\r') + { + if (str[1] == '\n') return 2; + } + return 0; +} + +static char *yaml_strndup(const char *str, int len) +{ + char *base = (char *)malloc(len + 1); + if (base) + { + memcpy(base, str, len); + base[len] = 0; + } + return base; +} + +/** + * \brief Compare two strings up to n characters, considering case-insensitive comparison. + * \param[in] s1: First string to compare. + * \param[in] s2: Second string to compare. + * \param[in] n: Maximum number of characters to compare. + * \return 1 if the strings match up to n characters (case-insensitive), 0 otherwise. + */ +static int strcncmp(const char *s1, const char *s2, unsigned int n) +{ + int flag = 0; + unsigned int i = 1; + + if (!s1 || !s2 || n == 0) return 0; + + if (tolower(*s1) != tolower(*s2)) return 0; + + if (islower(*s1)) flag = -1; + + s1++, s2++; + + while (i < n && *s1 && *s2 && tolower(*s1) == tolower(*s2)) + { + if (flag < 0) + { + if (isupper(*s1)) return 0; + } + else if (flag > 0) + { + if (islower(*s1)) return 0; + } + else + { + if (islower(*s1)) flag = -1; + else flag = 1; + } + s1++, s2++, i++; + } + + if (i != n) return 0; + + return 1; +} + +/** + * \brief Pad a character to a buffer. + * \param[in] base: Pointer to the buffer. + * \param[in] count: Number of characters to pad. + * \param[in] pad: Character to pad. + */ +static void yaml_str_padding(char *base, int count, char pad) +{ + for (int i = 0; i < count; i++) + { + base[i] = pad; + } +} + +/** + * \brief Append a string to the value of a YAML node and reallocate memory if needed. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] ap: Pointer to the string to append. + * \param[in] count: Length of the string to append. + * \return Pointer to the updated string in the YAML node, or NULL on failure. + * + * This function appends a specified string (or part of it) to the existing string value + * of a YAML node. It first reallocates memory to accommodate the new size (existing size + * plus the count of characters to append, plus one for the null terminator). If the memory + * allocation is successful, it copies the specified number of characters from the source + * string to the end of the existing string and null-terminates the result. The function + * returns a pointer to the updated string or NULL if memory allocation fails. + * + * Memory management: + * - Uses realloc to resize the existing string buffer. If realloc fails, the original + * buffer remains unchanged and the function returns NULL. + * - Updates the YAML node's string pointer to the new buffer if realloc is successful. + * + * Example usage: + * \code + * yaml_t node; + * node.vtype = YAML_TYPE_STRING; + * node.value.string_ = strdup("Hello"); + * node.size = 5; + * + * const char* to_append = " World!"; + * int append_length = strlen(to_append); + * + * char* result = yaml_str_append(&node, to_append, append_length); + * if (result) { + * // node.value.string_ now contains "Hello World!" + * // node.size is updated to 12 + * } + * \endcode + */ +static char *yaml_str_append(yaml_t yaml, const char *ap, int count) +{ + char* address = NULL; + + // Reallocate memory to accommodate the existing string plus the new content + address = (char*)realloc(yaml->value.string_, yaml->size + count + 1); + if (!address) return NULL; + + // Copy the new content to the end of the existing string + if (ap) memcpy(address + yaml->size, ap, count); + + // Null-terminate the string + address[yaml->size + count] = 0; + + return address; +} + +/** + * \brief Parse a 4-digit hexadecimal number from a string. + * \param[in] str: Pointer to the string containing the hexadecimal number. + * \return The parsed unsigned integer value, or 0 on failure. + * + * This function parses a 4-digit hexadecimal number from the given string. It checks each character + * of the string to determine if it represents a valid hexadecimal digit (0-9, A-F, a-f). For each valid + * digit, it calculates its numerical value and shifts the accumulated value left by 4 bits to make room + * for the next digit. If any character is not a valid hexadecimal digit, the function returns 0. + * + * Memory management: + * - The function does not allocate or free any memory. It only reads from the input string. + * + * Example usage: + * \code + * const char* hex_str = "1A2B"; + * unsigned int result = parse_hex4(hex_str); + * // result will be the integer value corresponding to the hexadecimal number "1A2B" + * \endcode + */ +static unsigned int parse_hex4(const char* str) +{ + unsigned int h = 0; + if (*str >= '0' && *str <= '9') h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; + else return 0; + h = h << 4; str++; + if (*str >= '0' && *str <= '9') h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; + else return 0; + h = h << 4; str++; + if (*str >= '0' && *str <= '9') h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; + else return 0; + h = h << 4; str++; + if (*str >= '0' && *str <= '9') h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; + else return 0; + return h; +} + +/** + * \brief Convert a UTF-16 surrogate pair or a single Unicode character in the input string to UTF-8 and store in the output buffer. + * \param[in] in: Pointer to the input string. + * \param[in] out: Pointer to the output buffer. + */ +static void yaml_utf(const char **in, char **out) +{ + const char* ptr = *in; + char* ptr2 = *out; + int len = 0; + unsigned int uc, uc2; + unsigned char mask_first_byte[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + uc = parse_hex4(ptr + 1); ptr += 4; /* get the unicode char. */ + if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) return; /* check for invalid. */ + if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ + { + if (ptr[1] != '\\' || ptr[2] != 'u') return; /* missing second-half of surrogate */ + uc2 = parse_hex4(ptr + 3); + ptr += 6; + if (uc2 < 0xDC00 || uc2>0xDFFF) return; /* invalid second-half of surrogate */ + uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); + } + len = 4; + if (uc < 0x80) len = 1; + else if (uc < 0x800) len = 2; + else if (uc < 0x10000) len = 3; + ptr2 += len; + switch (len) + { + case 4: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; + case 3: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; + case 2: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; + case 1: *--ptr2 = (uc | mask_first_byte[len]); + } + ptr2 += len; +} + +/** + * \brief Parse a string from the text within quotes and allocate memory for it. + * \param[in] buf: Pointer to a pointer that will hold the parsed string. + * \param[in] text: Pointer to the text to parse. + * \param[in] quote: The quote character (e.g., '"' or '\'') that encloses the string. + * \return Pointer to the position after the parsed string in the text, or NULL on failure. + * + * This function parses a quoted string from the given text, handling escape sequences and + * UTF-8 encoding. It first calculates the length of the string by scanning through the text + * until it finds the closing quote or a newline. It then allocates memory for the parsed string, + * copies the text into this memory while processing escape sequences (such as \n, \t, \uXXXX), + * and null-terminates the result. If memory allocation fails, it sets an error and returns NULL. + * + * Memory management: + * - Allocates memory for the parsed string using malloc. The caller is responsible for freeing + * this memory when it is no longer needed. + * - Returns NULL and sets an error if memory allocation fails. + * + * Example usage: + * \code + * const char* text = "\"Hello,\\nWorld!\""; + * char* parsed_str; + * const char* end = parse_string_buffer(&parsed_str, text, '"'); + * if (end) { + * // parsed_str contains "Hello,\nWorld!" + * // end points to the character after the closing quote + * } + * \endcode + */ +static const char* parse_string_buffer(char** buf, const char* text, char quote) +{ + const char* ptr = text + 1; + char* ptr2; + char* out; + int len = 0; + + *buf = NULL; + + /* Calculate the length of the string by scanning through the text */ + while (*ptr && *ptr != '\n' && *ptr != quote) + { + if (*ptr++ == '\\') ptr++; /* skip escaped quotes. */ + len++; + } + + /* Allocate storage space based on the calculated string length */ + out = (char*)malloc(len + 1); + if (!out) + { + E(YAML_E_MEMORY, text); + } + + /* Copy the text to the allocated memory, handling escape sequences */ + ptr = text + 1; + ptr2 = out; + while (*ptr && *ptr != '\n' && *ptr != quote) + { + /* Normal character */ + if (*ptr != '\\') { *ptr2++ = *ptr++; } + /* Escape sequence */ + else + { + ptr++; + if (*ptr == 'b') { *ptr2++ = '\b'; } + else if (*ptr == 'f') { *ptr2++ = '\f'; } + else if (*ptr == 'n') { *ptr2++ = '\n'; } + else if (*ptr == 'r') { *ptr2++ = '\r'; } + else if (*ptr == 't') { *ptr2++ = '\t'; } + else if (*ptr == 'u') { yaml_utf(&ptr, &ptr2); } /* Handle UTF-16 code point */ + else { *ptr2++ = *ptr; } /* Unrecognized escape - treat as literal */ + ptr++; + } + } + + /* Null-terminate the string */ + *ptr2 = 0; + /* Move past the closing quote */ + if (*ptr == quote) ptr++; + + *buf = out; + + return ptr; +} + +/** + * \brief Parse a string line in the YAML text and handle string formatting and storage. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] text: Pointer to the YAML text to parse. + * \param[in] parser: Pointer to the PARSER structure for parsing state. + * \return Pointer to the position after the parsed string line in the text, or NULL on failure. + * + * This function parses a string line in the YAML text and manages the formatting and storage + * of the string within the YAML node. It first adjusts the sentinel position to find the valid + * part of the line. Then, it checks the value type of the YAML node. If it's NULL or a string, + * it handles the string parsing. For a NULL target base type, it appends the string and manages + * padding with newlines or spaces. For SCALAR or FOLD target base types, it calculates the + * appropriate number of leading spaces, allocates memory, inserts padding characters, and + * copies the valid characters. If the value type doesn't match the expected type, it logs a + * debug message and returns NULL. + * + * Memory management: + * - Calls yaml_str_append to allocate and append characters to the string in the YAML node. + * - If memory allocation fails (yaml_str_append returns NULL), it calls E with YAML_E_MEMORY + * and returns NULL. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * const char* yaml_text = "my_string: \"Hello, World!\""; + * PARSER my_parser; + * // Assume my_parser is properly initialized. + * const char* end = parse_string_line(&my_yaml_node, yaml_text, &my_parser); + * if (end) { + * // The string line was successfully parsed and stored in the YAML node. + * } else { + * // There was an error during parsing, likely related to memory allocation or type mismatch. + * } + * \endcode + */ +static const char* parse_string_line(yaml_t yaml, const char* text, PARSER *parser) +{ + // Move the sentinel back to find the valid part. + parser->line_e = rskip(parser->sentinel - 1, parser->line_s); + + // Check if the value type is not NULL or already a string before parsing. + if (yaml->vtype == YAML_TYPE_NULL || yaml->vtype == YAML_TYPE_STRING) + { + // If the target base type of the string is NULL (ordinary string parsing). + if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_NULL) + { + int pad = 0; // Padding count. + char *ss = NULL; // Temporary string storage. + + // If the string_ in the YAML node is not assigned yet, it's the first line being parsed. + if (yaml->value.string_) + { + // Record the number of empty lines before the current line, which will be filled with newlines. + pad = parser->lines - 1; + + // Ensure the padding count is at least 1 (at least one newline). + if (pad <= 0) pad = 1; + } + + // Append the string in a unified way and allocate memory. + ss = yaml_str_append(yaml, parser->line_s - pad, parser->line_e - parser->line_s + 1 + pad); + if (!ss) E(YAML_E_MEMORY, parser->sentinel); + + // If the string_ is not assigned yet, it's the first line being parsed. + if (yaml->value.string_) + { + // If there are empty lines, fill them with newlines. + if (parser->lines > 1) for (int i = 0; i < pad; i++) ss[yaml->size + i] = '\n'; + // If no empty lines, connect the lines with a space. + else ss[yaml->size] = ' '; + } + + // Update the string information in the YAML node. + yaml->vtype = YAML_TYPE_STRING; + yaml->value.string_ = ss; + yaml->size += (parser->line_e - parser->line_s + pad + 1); + } + // If the target base type is SCALAR or FOLD (handles formatted strings). + else if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR || parser->flag.tgtbase == YAML_PFLAG_TGTBASE_FOLD) + { + char *ss = NULL; // Temporary string storage. + int space_n = 0; // Number of leading spaces at the start of the line. + int nline = parser->lines - 1; // Record the number of leading empty lines. + int ilen = 0; // Insertion length. + char token = (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR ? '\n' : ' '); + + // If the string has a numeric extra attribute for leading spaces. + if (parser->flag.strextr == YAML_PFLAG_STREXTR_NUM) + { + if (// The first line of the string, each with the specified amount of space added + yaml->value.string_ == NULL || + // Instead of collapsing a multiline string, only the first line of the string needs a specified amount of whitespace and no other lines + // The scalar format, on the other hand, adds a specified number of Spaces to each line capital + parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR + ) + { + // The space count is in the range [1, 256], 0 represents 256. + space_n = parser->flag.space_n; + if (space_n == 0) space_n = 256; + } + } + + // Add leading spaces. + ilen = (space_n + 1) * nline + space_n; + if (ilen > 0) + { + // Allocate memory for the string. + ss = yaml_str_append(yaml, NULL, ilen); + if (!ss) E(YAML_E_MEMORY, parser->sentinel); + + yaml->value.string_ = ss; + + // Fill in the padding characters. + ss += yaml->size; + + // For SCALAR formatted strings, pad each line with the specified number of spaces. + if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR) + { + for (int i = 0; i < nline; i++) + { + yaml_str_padding(ss, space_n, ' '); + ss[space_n] = token; + ss += (space_n + 1); + } + yaml_str_padding(ss, space_n, ' '); + } + // For folded strings, just insert the specified number of spaces. + else yaml_str_padding(ss, ilen, ' '); + + // Update the string length in the YAML node. + yaml->size += ilen; + } + + // If the current line depth is greater than the child depth, adjust the start position of the string. + if (parser->deep_cur > parser->deep_chd) parser->line_s -= (parser->deep_cur - parser->deep_chd); + + // Calculate the length for the actual valid characters and padding. + ilen = parser->line_e - parser->line_s + 1 + 1; // and pad + + // Allocate memory and copy the actual valid characters. + ss = yaml_str_append(yaml, parser->line_s, ilen); + if (!ss) E(YAML_E_MEMORY, parser->sentinel); + + // Fill in the padding character. + ss[yaml->size + ilen - 1] = token; + + // Update the string information in the YAML node. + yaml->vtype = YAML_TYPE_STRING; + yaml->value.string_ = ss; + + // Update the string length in the YAML node. + yaml->size += ilen; + } + } + // If the value type doesn't match the expected type. + else + { + DEBUG("yaml->vtype %d\r\n", yaml->vtype); + E(YAML_E_MIX, parser->sentinel); + } + + return parser->sentinel; +} + +/** + * \brief Parse a string with quotes in the YAML text and handle string storage. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] text: Pointer to the YAML text to parse. + * \param[in] parser: Pointer to the PARSER structure for parsing state. + * \return Pointer to the position after the parsed string in the text, or NULL on failure. + * + * This function parses a string within quotes in the YAML text and manages its storage + * in the YAML node. It first records the start position of the valid part. Then, it + * calculates the length of the string by scanning the text, handling escape characters. + * If the closing quote is found, it marks the end of the string interval. If not, it + * adjusts the end position based on the current line. After that, it allocates memory + * for the string, copies the text (including handling empty lines and escape sequences), + * and updates the size of the string in the YAML node. In case of memory allocation + * failure or parsing errors (like unexpected characters after the quote), it returns NULL. + * + * Memory management: + * - Calls yaml_str_append to allocate memory for the string in the YAML node. + * - If yaml_str_append returns NULL (memory allocation failure), it calls E with YAML_E_MEMORY + * and returns NULL. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * const char* yaml_text = "\"Hello, \\nWorld!\""; + * PARSER my_parser; + * // Assume my_parser is properly initialized. + * const char* end = parse_string_quote(&my_yaml_node, yaml_text, &my_parser); + * if (end) { + * // The string within quotes was successfully parsed and stored in the YAML node. + * } else { + * // There was an error during parsing, likely related to memory allocation or incorrect string format. + * } + * \endcode + */ +static const char* parse_string_quote(yaml_t yaml, const char* text, PARSER *parser) +{ + char *ss = NULL; + const char* ptr; + char* ptr2; + int len = 0; + char quote = (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SINGLE ? '\'' : '\"'); + + // Record the start position of the valid part. + parser->line_s = parser->sentinel; + + /* Get the length of the string */ + while (*(parser->sentinel) && *(parser->sentinel) != '\n' && *(parser->sentinel) != quote) + { + if (*parser->sentinel++ == '\\') parser->sentinel++; /* skip escaped quotes. */ + len++; + } + + // If the quote character is found, a closed interval of quotes is formed. + if (*(parser->sentinel) == quote) + { + // Record the end position of the valid part (excluding the quote). + parser->line_e = parser->sentinel - 1; + + // Move the sentinel forward to check if the current line has ended. + parser->sentinel = skip_ln(parser->sentinel + 1); + + // If it's not the end of the line or the text, there is a parsing error as there are still valid characters after the closed quote interval. + if (*parser->sentinel != 0 && *parser->sentinel != '\n') + { + E(YAML_E_LBREAK, parser->sentinel); + } + + parser->flag.interval = 1; + } + // If the sentinel position is at the end of the line or the text. + else + { + // Find the end position of the valid part of the current line by moving the sentinel back. + parser->line_e = rskip(parser->sentinel - 1, parser->line_s); + + // Subtract the length of the invalid part, which is not included in the valid part. + len -= (parser->sentinel - parser->line_e - 1); + } + + // If there are empty lines before the current line, they need to be recorded. + if (parser->lines > 0) + { + len += parser->lines; + } + + // Allocate additional storage. + ss = yaml_str_append(yaml, NULL, len); + if (!ss) E(YAML_E_MEMORY, parser->sentinel); + yaml->value.string_ = ss; + + /* Copy text to new space */ + ptr = parser->line_s; + ptr2 = &yaml->value.string_[yaml->size]; + + // First, assign the empty lines at the beginning, and empty lines will be converted to spaces. + for (int i = 0; i < parser->lines; i++) + { + *ptr2++ = ' '; + } + + // Then, assign the valid characters one by one. + while (*ptr && *ptr != '\n' && *ptr != quote) + { + /* Normal character */ + if (*ptr != '\\') { *ptr2++ = *ptr++; } + /* Escape character */ + else + { + ptr++; + if (*ptr == 'b') { *ptr2++ = '\b'; } + else if (*ptr == 'f') { *ptr2++ = '\f'; } + else if (*ptr == 'n') { *ptr2++ = '\n'; } + else if (*ptr == 'r') { *ptr2++ = '\r'; } + else if (*ptr == 't') { *ptr2++ = '\t'; } + else if (*ptr == 'u') { yaml_utf(&ptr, &ptr2); } + else { *ptr2++ = *ptr; } + ptr++; + } + } + + yaml->size += len; + + return parser->sentinel; +} + +/** + * \brief Parse the end of a string in the YAML text and handle string formatting at the end. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] text: Pointer to the YAML text to parse. + * \param[in] parser: Pointer to the PARSER structure for parsing state. + * \return Pointer to the position after the parsed string end in the text, or NULL on failure. + * + * This function processes the end of a string in YAML text, adjusting formatting based on + * the target base type (scalar or fold) and any specified string extra attributes. For + * scalar strings, it ensures proper newline handling. For folded strings, it manages + * whitespace formatting. The function can either strip trailing delimiters or add + * additional empty lines as specified by the parser flags. + * + * Memory management: + * - Uses yaml_str_append() to allocate additional memory if needed for appending lines. + * - Updates the YAML node's string pointer and size directly. + * + * Example usage: + * \code + * yaml_t yaml; + * PARSER parser; + * // Initialize yaml and parser... + * + * const char* result = parse_string_end(&yaml, text, &parser); + * if (result) { + * // String end processed successfully + * } else { + * // Error occurred (e.g., memory allocation failure) + * } + * \endcode + */ +static const char* parse_string_end(yaml_t yaml, const char* text, PARSER *parser) +{ + if (yaml && yaml->vtype == YAML_TYPE_STRING) + { + // Handle scalar (literal block) or folded block string formatting + if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR || parser->flag.tgtbase == YAML_PFLAG_TGTBASE_FOLD) + { + // Determine the delimiter based on formatting type + char token = (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR) ? '\n' : ' '; + + // Handle "strip" modifier (remove trailing delimiter) + if (parser->flag.strextr == YAML_PFLAG_STREXTR_SUB) + { + if (yaml->value.string_[yaml->size - 1] == token) + { + yaml->value.string_[yaml->size - 1] = 0; + yaml->size--; + } + } + // Handle "keep" modifier (add trailing empty lines) + else if (parser->flag.strextr == YAML_PFLAG_STREXTR_ADD) + { + char *ss = NULL; // Temporary string pointer + int nlen = 0; // Number of trailing lines to add + + // Calculate required trailing lines + nlen = parser->lines - 1; + if (nlen > 0) + { + // Allocate memory for additional lines + ss = yaml_str_append(yaml, NULL, nlen); + if (!ss) E(YAML_E_MEMORY, parser->sentinel); + + // Update string pointer and fill with delimiters + ss = yaml->value.string_ + yaml->size; + yaml_str_padding(ss, nlen, token); + + // Update string size + yaml->size += nlen; + } + } + } + } + + return parser->sentinel; +} + +/** + * \brief Parse a number from a string and convert it to the appropriate YAML numeric type. + * \param[in] yaml: Pointer to the YAML node containing the string to parse. + * \param[in] parser: Pointer to the parser state. + * \return Pointer to the modified YAML node with the parsed numeric value, or NULL on failure. + * + * This function parses a number represented as a string within a YAML node and converts it to either + * an integer or a floating-point value, depending on the format of the number. It first checks for + * signs, then handles hexadecimal, binary, and decimal representations. For decimal numbers, it + * processes the integer part, decimal part, and exponent part separately. If the number is an integer + * within the range of an `int`, it sets the YAML node's value to an integer; otherwise, it sets it + * to a floating-point value. In case of parsing errors (such as invalid characters), it returns NULL. + * + * Memory management: + * - Does not allocate or free memory directly within this function. + * - Updates the YAML node's value based on the parsed number. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * my_yaml_node.value.string_ = "3.14"; + * PARSER my_parser; + * // Assume my_parser is properly initialized. + * yaml_t result = parse_number(&my_yaml_node, &my_parser); + * if (result) { + * // The number in the YAML node has been parsed and converted to the appropriate type. + * // my_yaml_node now holds the parsed number as an integer or float. + * } else { + * // There was an error during parsing, such as an invalid number format. + * } + * \endcode + */ +static yaml_t parse_number(yaml_t yaml, PARSER *parser) +{ + char *text = yaml->value.string_; + double number = 0; /* Converted number */ + int sign = 1, scale = 0; /* Sign and scale of integer part */ + int e_sign = 1, e_scale = 0; /* Sign and scale of exponent part */ + int isint = 1; /* Flag for whether there is only an integer part within a string */ + + /* First, check if there is a negative number with `-` before it */ + if (*text == '-') + { + sign = -1; + text++; + } + // Skip the '+' symbol + else if (*text == '+') + { + text++; + } + + if (*text == '0') + { + // Hexadecimal parsing + if (text[1] == 'x' || text[1] == 'X') + { + int int_ = 0; + char c = 0; + + // Update position + text += 2; + + // Convert hexadecimal characters one by one + while (c = *text++) + { + if (c >= '0' && c <= '9') c = c - '0'; + else if (c >= 'A' && c <= 'F') c = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') c = c - 'a' + 10; + else return NULL; + + int_ = (int_ << 4) | c; + } + + if (sign < 0) int_ = -int_; + + // Update YAML value to hexadecimal integer + yaml_set_int(yaml, (int)int_); + + return yaml; + } + // Binary parsing + else if (text[1] == 'b') + { + int int_ = 0; + char c = 0; + + // Update position + text += 2; + + // Convert binary characters one by one + while (c = *text++) + { + if (c == '0') int_ = (int_ << 1); + else if (c == '1') int_ = (int_ << 1) | 1; + else if (c == '_') continue; + else return NULL; + } + + if (sign < 0) int_ = -int_; + + // Update YAML value to binary integer + yaml_set_int(yaml, (int)int_); + + return yaml; + } + } + else if (*text == '.') + { + // Check if it's NaN + if (yaml->size == 4) + { + if (strcncmp(text + 1, "nan", 3)) return yaml_set_float(yaml, NAN); + } + + // Check if it's infinity + if (strcncmp(text + 1, "inf", 3)) return yaml_set_float(yaml, sign < 0 ? -INFINITY : INFINITY); + } + + /* Skip invalid leading '0's */ + while (*text == '0') text++; + + /* Integer part */ + if (*text >= '1' && *text <= '9') + { + do + { + number = (number * 10.0) + (*text++ - '0'); /* carry addition */ + } while (*text >= '0' && *text <= '9'); + } + else return yaml; + + /* Decimal part */ + if (*text == '.') + { + text++; + + /* Check if the first character is a valid number */ + if (!(*text >= '0' && *text <= '9')) + { + return NULL; + } + + /* The number of decimal parts is also increased by 10 times first, and then reduced according to the scale later on */ + do + { + number = (number * 10.0) + (*text++ - '0'); + scale--; + } while (*text >= '0' && *text <= '9'); + + /* Decimal part present, marked as non-integer */ + isint = 0; + } + + /* Exponent part */ + if (*text == 'e' || *text == 'E') + { + text++; + + /* Symbol '+' skip */ + if (*text == '+') text++; + /* Symbol '-' with sign */ + else if (*text == '-') + { + e_sign = -1; + text++; + } + + /* Check if the first character is a valid number */ + if (!(*text >= '0' && *text <= '9')) + { + return NULL; + } + + /* Conversion exponent part */ + while (*text >= '0' && *text <= '9') + { + e_scale = (e_scale * 10) + (*text++ - '0'); /* number */ + } + + /* Exponent part present, marked as non-integer */ + isint = 0; + } + + if (*text != 0) return NULL; + + /* Calculate conversion result */ + number = (double)sign * number * pow(10.0, (scale + e_scale * e_sign)); + + /* As an integer and within the scope of an integer */ + if (isint && INT_MIN <= number && number <= INT_MAX) + { + yaml_set_int(yaml, (int)number); + } + /* As a floating-point number */ + else + { + yaml_set_float(yaml, number); + } + + return yaml; +} + +/** + * \brief Parse a date and time string into a YAML date type. + * \param[in] yaml: Pointer to the YAML node containing the string to parse. + * \param[in] parser: Pointer to the parser state. + * \return Pointer to the modified YAML node with the parsed date value, or NULL on failure. + * + * This function parses a date and time string from a YAML node's string value into a structured + * date-time format. It handles ISO 8601-like formats, including dates (YYYY-MM-DD), times + * (HH:MM:SS), fractional seconds, and UTC offsets. The parsed components are validated for + * correctness and stored in the YAML node's date structure. If parsing or validation fails, + * the function returns NULL. + * + * Memory management: + * - Does not allocate or free memory directly. + * - Modifies the existing YAML node's value type and content. + * + * Example usage: + * \code + * yaml_t node; + * // Initialize node with a date string, e.g., "2023-05-15T12:30:45.123+02:00" + * + * yaml_t result = parse_date(&node, parser); + * if (result) { + * // Date parsed successfully, access via node.value.date + * } else { + * // Parsing failed + * } + * \endcode + */ +static yaml_t parse_date(yaml_t yaml, PARSER *parser) +{ + int year = 0; + int month = 0; + int day = 0; + int hour = 0; + int minute = 0; + int second = 0; + int msec = 0; + int utcsign = 0; // 0 for positive offset, 1 for negative + int utchour = 0; + int utcminute = 0; + char flag = 0; // Bitmask flags for parsed components + + if (yaml->vtype != YAML_TYPE_STRING) return yaml; + + // Parse date part in YYYY-MM-DD format + char *s = yaml->value.string_; + if (sscanf(s, "%4d-%2d-%2d", &year, &month, &day) != 3) return yaml; + + // Find time separator 'T' or space + s += 10; + if (*s == 0) goto END; + else if (*s == 't' || *s == 'T') s++; + else if (*s >= '0' && *s <= '9'); // Allow time without explicit separator + else return yaml; + + // Parse time part in HH:MM:SS format + if (sscanf(s, "%2d:%2d:%2d", &hour, &minute, &second) != 3) return yaml; + flag |= 0x01; // Time part parsed + +MSEC: + // Parse fractional seconds (milliseconds) + s += 8; + if (*s == 0) goto END; + else if (*s == '.') s++; + else if (*s == '+' || *s == '-') goto UTC; + else if (*s >= '0' && *s <= '9'); // Allow fractional part without leading dot + else return yaml; + + // Parse up to three decimal places for milliseconds + if (*s >= '0' && *s <= '9') msec += ((*s++ - '0') * 100); + if (*s >= '0' && *s <= '9') msec += ((*s++ - '0') * 10); + if (*s >= '0' && *s <= '9') msec += ((*s++ - '0')); + while (*s >= '0' && *s <= '9') s++; // Skip remaining digits + flag |= 0x02; // Fractional part parsed + +UTC: + // Parse UTC offset + if (*s == 0) goto END; + else if (*s == '+') { s++; utcsign = 0; } + else if (*s == '-') { s++; utcsign = 1; } + else if (*s >= '0' && *s <= '9'); // Allow missing offset sign + else return yaml; + + // Parse UTC offset in HH:MM format + if (sscanf(s, "%d:%d", &utchour, &utcminute) != 2) return yaml; + flag |= 0x04; // UTC offset parsed + +END: + // Validate parsed components + if (year < 0 || year > 4095) return NULL; + if (!date_isvalid(year, month, day)) return NULL; + if (hour < 0 || hour >= 24) return NULL; + if (minute < 0 || minute >= 60) return NULL; + if (second < 0 || second >= 60) return NULL; + if (msec < 0 || msec >= 1000) return NULL; + if (utchour < 0 || utchour > 12) return NULL; + if (utcminute < 0 || utcminute >= 60) return NULL; + + // Update YAML node with parsed date-time values + yaml_set_null(yaml); // Reset node type + yaml->vtype = YAML_TYPE_DATE; + + // Store parsed components in date structure + yaml->value.date.year = year; + yaml->value.date.month = month; + yaml->value.date.day = day; + yaml->value.date.hour = hour; + yaml->value.date.minute = minute; + yaml->value.date.second = second; + yaml->value.date.msec = msec; + yaml->value.date.utcsign = utcsign; + yaml->value.date.utchour = utchour; + yaml->value.date.utcminute = utcminute; + yaml->value.date.flag = flag; + + return yaml; +} + +/** + * \brief Parse a scalar value in a YAML node and convert it to an appropriate type. + * \param[in] yaml: Pointer to the YAML node containing the scalar value. + * \param[in] parser: Pointer to the parser state. + * \return Pointer to the modified YAML node with the parsed scalar value, or the original node if no conversion is needed. + * + * This function parses a scalar value within a YAML node. If the node's value type is a string and + * the target base flag indicates a null target base, it checks if the string matches YAML specification + * keywords (like "~", "no", "yes", "null", "true", "false"). If it matches, it sets the YAML node's + * value to the appropriate type (null, boolean). If it doesn't match, it attempts to parse the string + * as a number by calling the parse_number function. If the value type is not a string or the target + * base flag is not for a null target base, it returns the original YAML node. + * + * Memory management: + * - Calls yaml_set_null and yaml_set_bool which may handle memory related to setting the YAML node's value. + * - Calls parse_number which may also handle memory related to parsing and setting the node's value as a number. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * my_yaml_node.vtype = YAML_TYPE_STRING; + * my_yaml_node.value.string_ = "true"; + * PARSER my_parser; + * my_parser.flag.tgtbase = YAML_PFLAG_TGTBASE_NULL; + * + * yaml_t result = parse_scalar(&my_yaml_node, &my_parser); + * if (result) { + * // The YAML node's value is set to the appropriate type (in this case, boolean true). + * } else { + * // There was an error during parsing or the node's type couldn't be converted. + * } + * \endcode + */ +static yaml_t parse_scalar(yaml_t yaml, PARSER *parser) +{ + if (yaml->vtype == YAML_TYPE_STRING && parser->flag.tgtbase == YAML_PFLAG_TGTBASE_NULL) + { + // Check if the current string is a YAML specification keyword. + if (yaml->size == 1) + { + if (yaml->value.string_[0] == '~') return yaml_set_null(yaml); + } + else if (yaml->size == 2) + { + if (strcncmp(yaml->value.string_, "no", 2)) return yaml_set_bool(yaml, 0); + } + else if (yaml->size == 3) + { + if (strcncmp(yaml->value.string_, "yes", 3)) return yaml_set_bool(yaml, 1); + } + else if (yaml->size == 4) + { + if (strcncmp(yaml->value.string_, "null", 4)) return yaml_set_null(yaml); + if (strcncmp(yaml->value.string_, "true", 4)) return yaml_set_bool(yaml, 1); + } + else if (yaml->size == 5) + { + if (strcncmp(yaml->value.string_, "false", 5)) return yaml_set_bool(yaml, 0); + } + + // If not a keyword, try to convert and parse it as a number. + parse_number(yaml, parser); + } + + return yaml; +} + +/** + * \brief Parse and set the target type of a YAML node based on the text. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] parser: Pointer to the parser state. + * \return Pointer to the position after the parsed type in the text, or NULL if no valid type is found. + * + * This function parses the text to identify the target type of a YAML node. It checks if the text + * matches specific keywords such as "null", "int", "float", "bool", "str", "binary", "timestamp", "set", + * or "map". If a match is found, it sets the target flag in the parser state and moves the sentinel + * to the position after the keyword. If no match is found, it returns NULL. + * + * Memory management: + * - This function does not allocate or free memory directly. It only modifies the parser state. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * PARSER my_parser; + * my_parser.sentinel = "float"; + * + * const char* result = parse_type_convert(&my_yaml_node, &my_parser); + * if (result) { + * // The target type in the parser state is set to YAML_PFLAG_TARGET_FLOAT, + * // and the sentinel is moved to the position after "float". + * } else { + * // No valid type was found in the text. + * } + * \endcode + */ +static const char* parse_type_convert(yaml_t yaml, PARSER *parser) +{ + if (0 == strncmp(parser->sentinel, "null", 4)) + { + parser->flag.target = YAML_PFLAG_TARGET_NULL; + parser->sentinel += 4; + } + else if (0 == strncmp(parser->sentinel, "int", 3)) + { + parser->flag.target = YAML_PFLAG_TARGET_INT; + parser->sentinel += 3; + } + else if (0 == strncmp(parser->sentinel, "float", 5)) + { + parser->flag.target = YAML_PFLAG_TARGET_FLOAT; + parser->sentinel += 5; + } + else if (0 == strncmp(parser->sentinel, "bool", 4)) + { + parser->flag.target = YAML_PFLAG_TARGET_BOOL; + parser->sentinel += 4; + } + else if (0 == strncmp(parser->sentinel, "str", 3)) + { + parser->flag.target = YAML_PFLAG_TARGET_STR; + parser->sentinel += 3; + } + else if (0 == strncmp(parser->sentinel, "binary", 6)) + { + parser->flag.target = YAML_PFLAG_TARGET_BINARY; + parser->sentinel += 6; + } + else if (0 == strncmp(parser->sentinel, "timestamp", 9)) + { + parser->flag.target = YAML_PFLAG_TARGET_TIMESTAMP; + parser->sentinel += 9; + } + else if (0 == strncmp(parser->sentinel, "set", 3)) + { + parser->flag.target = YAML_PFLAG_TARGET_SET; + parser->sentinel += 3; + } + else if (0 == strncmp(parser->sentinel, "seq", 3)) + { + parser->flag.target = YAML_PFLAG_TARGET_SEQ; + parser->sentinel += 3; + } + else if (0 == strncmp(parser->sentinel, "map", 3)) + { + parser->flag.target = YAML_PFLAG_TARGET_MAP; + parser->sentinel += 3; + } + else + { + return NULL; + } + + return parser->sentinel; +} + +/** + * \brief Check if the parsing interval of a YAML object is closed. + * \param[in] yaml: Pointer to the YAML node. + * \param[in] text: Pointer to the text being parsed. + * \param[in] parser: Pointer to the parser state. + * \return Pointer to the original text if the interval is closed, or calls an error function if the interval is not closed. + * + * This function checks whether the parsing interval of a YAML object is properly closed. It examines the type of the YAML node + * (string, sequence, or mapping) and the target base type in the parser state. If the YAML node is a string and the target base + * type is single or double, or if the YAML node is a sequence and the target base type is sequence, or if the YAML node is a + * mapping and the target base type is mapping, it checks the interval flag in the parser state. If the interval flag is 0, + * indicating an unclosed interval, it calls the E function with the YAML_E_LBREAK error code and the parser's sentinel. + * If the interval is closed, it simply returns the original text pointer. + * + * Memory management: + * - This function does not allocate or free memory directly. It only examines the YAML node and parser state. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * const char* my_text = "Some YAML text"; + * PARSER my_parser; + * // Assume my_yaml_node, my_text, and my_parser are properly initialized. + * const char* result = parse_check_interval(&my_yaml_node, my_text, &my_parser); + * if (result == my_text) { + * // The parsing interval is closed. + * } else { + * // An error occurred, and the E function was called for an unclosed interval. + * } + * \endcode + */ +static const char* parse_check_interval(yaml_t yaml, const char* text, PARSER *parser) +{ + if (yaml->vtype == YAML_TYPE_STRING) + { + if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SINGLE || + parser->flag.tgtbase == YAML_PFLAG_TGTBASE_DOUBLE) + { + if (parser->flag.interval == 0) + { + E(YAML_E_LBREAK, parser->sentinel); + } + } + } + else if (yaml->vtype == YAML_TYPE_SEQUENCE) + { + if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SEQUENCE) + { + if (parser->flag.interval == 0) + { + E(YAML_E_LBREAK, parser->sentinel); + } + } + } + else if (yaml->vtype == YAML_TYPE_MAPPING) + { + if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_MAPPING) + { + if (parser->flag.interval == 0) + { + E(YAML_E_LBREAK, parser->sentinel); + } + } + } + + return text; +} + +/** + * \brief Parse a sequence in the YAML text and attach child nodes to the YAML node. + * \param[in] yaml: Pointer to the YAML node being parsed. + * \param[in] text: Pointer to the YAML text. + * \param[in] token: A token character related to the sequence. + * \param[in] parser: Pointer to the PARSER structure for parsing state. + * \param[in] p: Another pointer to the PARSER structure (might be used for recursive parsing). + * \return Pointer to the position after the parsed sequence in the text, or NULL on failure. + * + * This function parses a sequence within the YAML text. It first checks if the YAML node's type is appropriate for a sequence. + * If not, it calls an error function. It then creates a new child YAML node for the sequence. If memory allocation for the child + * fails, it calls an error function. It checks the position in the text and either moves the sentinel or parses the value of the + * sequence child. If there's an error during value parsing, it deletes the child node. Finally, it attaches the child node to the + * parent YAML node and returns the position after the parsed sequence in the text. + * + * Memory management: + * - Calls yaml_create to allocate memory for the child YAML node. If allocation fails, it calls E with YAML_E_MEMORY. + * - Calls yaml_delete to free the memory of the child YAML node if there's an error during parsing. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * const char* yaml_text = "[1, \"hello\"]"; + * PARSER my_parser; + * char sequence_token = '['; + * PARSER* p = &my_parser; + * + * const char* result = parse_sequence(&my_yaml_node, yaml_text, sequence_token, &my_parser, p); + * if (result) { + * // The sequence was successfully parsed and child nodes were attached to the YAML node. + * } else { + * // There was an error during parsing, such as memory allocation failure or incorrect sequence format. + * } + * \endcode + */ +static const char* parse_sequence(yaml_t yaml, const char* text, char token, PARSER *parser, PARSER *p) +{ + yaml_t child = NULL; + + // When parsing a sequence, the value type should not be of other types. + if (yaml->vtype != YAML_TYPE_NULL && yaml->vtype != YAML_TYPE_SEQUENCE) + { + E(YAML_E_MIX, parser->sentinel); + } + + // When not parsing a [] sequence, there must be a child depth for the sequence. + if (rbrace <= 0 && parser->deep_cur == -1) + { + E(YAML_E_FLINE, parser->sentinel); + } + + // Create a child object for the sequence. + child = yaml_create(); + if (!child) E(YAML_E_MEMORY, parser->sentinel); + + yaml->vtype = YAML_TYPE_SEQUENCE; + + // When not parsing a [] sequence, if the next character is the end of the line or the end of the text, + // the current sequence child object is set to null and no further value parsing is done. + if (rbrace <= 0 && (parser->sentinel[1] == 0 || parser->sentinel[1] == '\n')) + { + parser->sentinel++; + } + else + { + if (rbrace <= 0) parser->sentinel += 2; + parser->sentinel = parse_value(child, parser->sentinel, parser->deep_cur, token, p); + if (etype != YAML_E_OK) + { + yaml_delete(child); + return parser->sentinel; + } + } + + yaml_attach(yaml, yaml->size, child); + + text = parser->sentinel; + + return text; +} + +/** + * \brief Parse a mapping in the YAML text and handle key-value pairs for the YAML node. + * \param[in] yaml: Pointer to the YAML node being parsed. + * \param[in] text: Pointer to the YAML text. + * \param[in] token: A token character related to the mapping. + * \param[in] parser: Pointer to the PARSER structure for parsing state. + * \param[in] p: Another pointer to the PARSER structure (might be used for recursive parsing). + * \return Pointer to the position after the parsed mapping in the text, or NULL on failure. + * + * This function parses a mapping (key-value pairs) in the YAML text. It first checks for the presence of a colon ':' + * to identify a key-value pair. If found, it creates a new child YAML node, sets the key from the parsed text, and + * recursively parses the value. Special handling is provided for merge keys ('<<') to handle references. If a comment + * or the end token is encountered, parsing stops. Memory allocation and error handling are managed throughout the process. + * + * Memory management: + * - Creates child nodes with yaml_create() and frees them with yaml_delete() on error. + * - Uses yaml_duplicate() for merging references, which allocates new memory that must be managed. + * - Attaches child nodes to the parent with yaml_attach(), transferring ownership. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * const char* yaml_text = "key: value"; + * PARSER my_parser; + * char token = '}'; + * + * const char* result = parse_mapping(&my_yaml_node, yaml_text, token, &my_parser, &my_parser); + * if (result) { + * // Mapping parsed successfully, key-value pair added to my_yaml_node + * } else { + * // Error occurred during parsing + * } + * \endcode + */ +static const char* parse_mapping(yaml_t yaml, const char* text, char token, PARSER *parser, PARSER *p) +{ + yaml_t child = NULL; + // PARSER p; + char comma = 0; + + if (token) comma = ','; + + // Track start of potential key + parser->line_s = parser->sentinel; + child = NULL; + + // Scan line for key-value separator ':' + while (*parser->sentinel != 0 && *parser->sentinel != '\n') + { + if (*parser->sentinel == ':') + { + const char *t = skip(&parser->sentinel[1]); + + // Validate key (non-empty and followed by value) + if (*t == 0 || t - parser->sentinel > 1) + { + // Check valid state for mapping + if (yaml->vtype != YAML_TYPE_NULL && yaml->vtype != YAML_TYPE_MAPPING) + { + E(YAML_E_MIX, parser->sentinel); + } + + if (rbrace <= 0 && parser->deep_cur == -1) + { + E(YAML_E_FLINE, parser->sentinel); + } + + // Determine key boundaries + parser->line_e = rskip(parser->sentinel - 1, parser->line_s); + + // Create and initialize child node for key-value pair + child = yaml_create(); + if (!child) E(YAML_E_MEMORY, parser->sentinel); + + if (!yaml_set_key_l(child, parser->line_s, parser->line_e - parser->line_s + 1)) E(YAML_E_MEMORY, parser->sentinel); + + DEBUG("[%d]key: %parser->sentinel\r\n", parser->deep_cur, child->key); + + yaml->vtype = YAML_TYPE_MAPPING; + + // Parse value after colon + parser->sentinel = parse_value(child, t, parser->deep_cur, token, p); + text = parser->sentinel; + + if (etype != YAML_E_OK) + { + yaml_delete(child); + return parser->sentinel; + } + + // Handle merge key ('<<') special case + if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_MAPPING && strcmp("<<", child->key) == 0) + { + yaml_t ref = child->value.child_; + + // Validate reference + if (!ref->alias) + E(YAML_E_TANCHOR, parser->sentinel); + + // Duplicate referenced node and attach to current mapping + yaml_t copy = yaml_duplicate(ref, YAML_F_RECURSE | YAML_F_COMPLEX | YAML_F_REFERENCE, parser->anchor); + if (!copy) E(YAML_E_MEMORY, parser->sentinel); + + yaml_attach(yaml, yaml->size, copy->value.child_); + + // Clean up temporary nodes + copy->value.child_ = NULL; + copy->vtype = YAML_TYPE_NULL; + copy->alias = NULL; + yaml_delete(copy); + + child->value.child_ = NULL; + yaml_delete(child); + + DEBUG("child->size %d\r\n", yaml->size); + } + else + { + // Attach regular key-value pair + yaml_attach(yaml, yaml->size, child); + } + + break; + } + } + else if (*parser->sentinel == '#') + { + // Comment encountered, stop parsing current line + text = skip_ln(parser->sentinel); + break; + } + else if (*parser->sentinel == token || *parser->sentinel == comma) + { + // End of mapping or list separator + break; + } + + parser->sentinel++; + } + + return text; +} + +/** + * \brief Parse a complex key in YAML and create a new YAML node for it. + * \param[in] yaml: Pointer to the parent YAML node. + * \param[in] text: Pointer to the YAML text to parse. + * \param[in] parser: Pointer to the parser state. + * \return Pointer to the position after the parsed complex key in the text, or NULL on failure. + * + * This function parses a complex key within the YAML text. It first creates two new YAML nodes: one for the child node + * and one for the complex key itself. Then, it parses the complex key as a value. If the parsing of the value is successful, + * it sets the type of the child node to indicate a complex key and attaches the key node to the child node. Finally, it attaches + * the child node to the parent YAML node. If any step fails (such as memory allocation or value parsing), it deletes the relevant + * nodes and returns an appropriate pointer. + * + * Memory management: + * - Calls yaml_create to allocate memory for new YAML nodes. If allocation fails, it calls E with YAML_E_MEMORY + * and deletes any partially created nodes. + * - Calls yaml_delete to free memory of nodes when an error occurs during parsing. + * - Uses yaml_attach to attach nodes, which may involve memory management related to the node structure. + * + * Example usage: + * \code + * yaml_t parent_yaml_node; + * const char* yaml_text = "complex_key: value"; + * PARSER my_parser; + * + * const char* result = parse_complex_key(&parent_yaml_node, yaml_text, &my_parser); + * if (result) { + * // The complex key was successfully parsed and a new YAML node was created and attached to the parent node. + * } else { + * // There was an error during parsing, such as memory allocation failure or incorrect key format. + * } + * \endcode + */ +static const char* parse_complex_key(yaml_t yaml, const char* text, PARSER *parser) +{ + yaml_t child = NULL; + yaml_t key = NULL; + PARSER p; + + // Create a child YAML node. + child = yaml_create(); + if (!child) E(YAML_E_MEMORY, parser->sentinel); + + // Create a complex key YAML node. + key = yaml_create(); + if (!key) + { + yaml_delete(child); + E(YAML_E_MEMORY, parser->sentinel); + } + + // A complex key is also a YAML value, so parse it as a value. + text = parse_value(key, text, parser->deep_cur, 0, &p); + parser->sentinel = text; + if (etype != YAML_E_OK) + { + yaml_delete(child); + yaml_delete(key); + return parser->sentinel; + } + + // Update child node information for complex key handling. + child->ktype = YAML_KTYPE_COMPLEX; + child->complex = key; + + // Attach the child node to the parent YAML node. + yaml_attach(yaml, yaml->size, child); + + return text; +} + +/** + * \brief Parse a complex value in YAML that corresponds to a previously parsed complex key. + * \param[in] yaml: Pointer to the parent YAML node. + * \param[in] text: Pointer to the YAML text to parse. + * \param[in] parser: Pointer to the parser state. + * \return Pointer to the position after the parsed complex value in the text, or NULL on failure. + * + * This function parses a complex value in the YAML text that corresponds to a complex key. + * It first locates the last child node of the parent YAML node. Then it checks if this + * child node represents a complex key. If it is a complex key, it proceeds to parse the + * value associated with it. If the parsing of the value fails (i.e., the return value + * of parse_value is not YAML_E_OK), it returns the current position of the parser's sentinel. + * If the parsing is successful, it returns the position after the parsed value in the text. + * + * Memory management: + * - Does not directly allocate or free memory within this function. Relies on functions like + * yaml_iterator_to and parse_value which may handle memory in their own way. + * + * Example usage: + * \code + * yaml_t parent_yaml_node; + * const char* yaml_text = "complex_key: complex_value"; + * PARSER my_parser; + * + * const char* result = parse_complex_value(&parent_yaml_node, yaml_text, &my_parser); + * if (result) { + * // The complex value corresponding to the complex key was successfully parsed. + * } else { + * // There was an error during parsing, such as an incorrect complex key or value format. + * } + * \endcode + */ +static const char* parse_complex_value(yaml_t yaml, const char* text, PARSER *parser) +{ + yaml_t child = NULL; + PARSER p; + + // Locate the last child node of the YAML node. + child = yaml_iterator_to(yaml, yaml->size - 1); + + // Check if the last child node is a complex key YAML node. + if (child->ktype != YAML_KTYPE_COMPLEX) return NULL; + + // Parse the value corresponding to the complex key. + text = parse_value(child, text, parser->deep_cur, 0, &p); + parser->sentinel = text; + if (etype != YAML_E_OK) + { + return parser->sentinel; + } + + return text; +} + +/** + * \brief Parse a YAML value from text into a YAML node structure + * \param[in] yaml: Pointer to the YAML node being constructed + * \param[in] text: Pointer to the start of YAML text to parse + * \param[in] deep: Current indentation depth in the YAML structure + * \param[in] token: Delimiter token for the current value context + * \param[in] parser: Pointer to the parser state structure + * \return Pointer to the next character after the parsed value + * + * This function processes a YAML value, handling various data types + * including strings, sequences, mappings, anchors, and references. + * It uses indentation and special characters to determine the value type + * and structure, recursively parsing nested elements as needed. + * + * Memory management: + * - Calls functions like yaml_create to allocate memory for new YAML nodes when needed. + * - Manages the memory of child nodes when attaching them to parent nodes using functions like yaml_attach. + * - When an error occurs during parsing (e.g., incorrect format), it may call functions like yaml_delete + * to free the memory of partially constructed nodes. + * + * Example usage: + * \code + * yaml_t my_yaml_node; + * const char* yaml_text = "key: value"; + * int indentation_depth = 0; + * char delimiter_token = '\0'; + * PARSER my_parser; + * + * const char* result = parse_value(&my_yaml_node, yaml_text, indentation_depth, delimiter_token, &my_parser); + * if (result) { + * // The YAML value in the text has been parsed and the YAML node structure is updated accordingly. + * } else { + * // There was an error during parsing, such as incorrect YAML format or memory allocation failure. + * } + * \endcode + */ +static const char* parse_value(yaml_t yaml, const char* text, int deep, char token, PARSER *parser) +{ + PARSER p; + char comma = 0; + + // Initialize comma separator if token is specified (for sequences/mappings) + if (token) comma = ','; + + /* Initialize parser state */ + parser->sentinel = text; // Set parsing position to start of text + parser->deep_chd = -1; // Initialize child depth (invalid value) + parser->deep_cur = 0; // Current indentation depth + parser->lines = 0; // Count of empty lines before value + parser->line_s = NULL; // Start of current line's valid content + parser->line_e = NULL; // End of current line's valid content + parser->scope_s = text; // Start of value's scope + parser->scope_e = NULL; // End of value's scope (to be determined) + parser->flag.int_ = 0; // Clear integer parsing flag + + // Share anchor list across all parser instances in this document + p.anchor = parser->anchor; + + // Main parsing loop - processes lines until value scope ends + while (*text) + { + parser->lines = 0; + + // Skip whitespace and identify next valid character + while (1) + { + // Skip leading whitespace + parser->sentinel = skip(text); + + // Handle end of text + if (*parser->sentinel == 0) + { + text = parser->sentinel; + goto END; + } + // Handle empty line + else if (*parser->sentinel == '\n') + { + text = parser->sentinel + 1; + lbegin = text; + + // Increment empty line count + parser->lines++; + + // Update the line currently parsed + eline++; + } + // Handle comment line + else if (*parser->sentinel == '#') + { + // Position directly to the end of the line + text = skip_ln(parser->sentinel); + } + // Handle delimiter or end token + else if (*parser->sentinel == token || *parser->sentinel == comma) + { + text = parser->sentinel; + goto END; + } + // Found valid content + else + { + break; + } + } + + // Reset current depth + parser->deep_cur = -1; + + if (parser->lines > 0 || // New rows appear that need to be computed when pro depth + (yaml->ktype == YAML_KTYPE_ANCHOR && yaml->vtype == YAML_TYPE_NULL)) // When first start parsing a document, need to calculate the depth + { + // Calculate current indentation depth + parser->deep_cur = parser->sentinel - text; + + // Initialize child depth if not set + if (parser->deep_chd == -1) parser->deep_chd = parser->deep_cur; + + // Check for document separator (---) + if (parser->deep_cur == 0) + { + // Document splitter, ends parsing the current document + if (strnwcmp("---", parser->sentinel, 3) == 0) + { + // Ensure that there are no more valid characters on the current line, + // meaning that the document separator is on a single line, and no other valid characters are allowed + parser->line_s = parser->sentinel; + + // Ensure document separator is on its own line + parser->sentinel = skip(parser->sentinel + 3); + if (*parser->sentinel == '\n' || *parser->sentinel == 0) + { + // Rewind to handle separator on next parse pass + // TODO: This place needs to be optimized to avoid repeated parsing of document splitters + text = parser->line_s - 1; + goto END; + } + } + } + + // Check if current depth exceeds parent depth + // Ends parsing the current value + if (parser->deep_cur <= deep) + { + // Go back one line and continue parsing from the previous line + eline--; + text = text - 1; + goto END; + } + + // Validate indentation consistency + if (parser->deep_cur < parser->deep_chd) E(YAML_E_TAB, text); + + DEBUG("***[deep %d, deep_chd %d, deep_cur %d, lines %d]\r\n", deep, deep_chd, deep_cur, lines); + } + + // Process value content if scope is not closed + if (parser->scope_e == NULL) + { + // Handle quoted strings + if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SINGLE || + parser->flag.tgtbase == YAML_PFLAG_TGTBASE_DOUBLE) + { + // Start parsing the quoted string + text = parse_string_quote(yaml, text, parser); + if (etype != YAML_E_OK) + { + return text; + } + } + // Handle literal or folded block scalars + else if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR || + parser->flag.tgtbase == YAML_PFLAG_TGTBASE_FOLD) + { + // The start position of the current valid part is recorded + parser->line_s = parser->sentinel; + + // Position directly to the end of the line + parser->sentinel = skip_ln(parser->sentinel); + + // Parse the line control string + text = parse_string_line(yaml, text, parser); + if (etype != YAML_E_OK) + { + return text; + } + } + // Handle sequence ([]) + else if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SEQUENCE) + { + // Parsing sequence + text = parse_sequence(yaml, text, ']', parser, &p); + if (etype != YAML_E_OK) + { + return parser->sentinel; + } + + // Process sequence delimiters + if (*parser->sentinel == ',') + { + parser->sentinel++; + } + // Parsed to the closed [] + else if (*parser->sentinel == ']') + { + // Close sequence scope + rbrace--; + + // Interval closed + parser->flag.interval = 1; + + // Update the parse location + text = parser->sentinel + 1; + + goto END; + } + + text = parser->sentinel; + } + // Handle mapping ({}) + else if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_MAPPING) + { + // Parsing mapping + DEBUG("text %d[%c]\r\n", *text, *text); + text = parse_mapping(yaml, text, '}', parser, &p); + if (etype != YAML_E_OK) + { + return parser->sentinel; + } + + // Process mapping delimiters + if (*parser->sentinel == ',') + { + parser->sentinel++; + } + // Parsed to the closed {} + else if (*parser->sentinel == '}') + { + // Close mapping scope + rbrace--; + + // Interval closed + parser->flag.interval = 1; + + // Update the parse location + text = parser->sentinel + 1; + + goto END; + } + + text = parser->sentinel; + } + // Handle untyped content - detect type based on initial character + else + { + // Handle quoted strings + if (*parser->sentinel == '\'' || *parser->sentinel == '\"') + { + // Set target base type based on quote type + if (*parser->sentinel == '\'') + parser->flag.tgtbase = YAML_PFLAG_TGTBASE_SINGLE; + else + parser->flag.tgtbase = YAML_PFLAG_TGTBASE_DOUBLE; + + // Initialize string value if type not set + if (yaml->vtype == YAML_TYPE_NULL) + { + yaml->value.string_ = NULL; + yaml->vtype = YAML_TYPE_STRING; + text = parser->sentinel + 1; + continue; + } + else if (yaml->vtype == YAML_TYPE_STRING) + { + if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_NULL) + E(YAML_E_MIX, parser->sentinel); + } + else + { + E(YAML_E_MIX, parser->sentinel); + } + } + // Handle block scalars (| or >) + else if (*parser->sentinel == '|' || *parser->sentinel == '>') + { + // Set target base type for block scalar + if (*parser->sentinel == '|') + parser->flag.tgtbase = YAML_PFLAG_TGTBASE_SCALAR; + else + parser->flag.tgtbase = YAML_PFLAG_TGTBASE_FOLD; + + if (yaml->vtype == YAML_TYPE_NULL) + { + // Reset child depth for block scalar + parser->deep_chd = -1; + + parser->sentinel++; + + // Parse block scalar modifiers (+, -, indentation) + if (*parser->sentinel == '+') + { + parser->flag.strextr = YAML_PFLAG_STREXTR_ADD; + parser->sentinel++; + } + else if (*parser->sentinel == '-') + { + parser->flag.strextr = YAML_PFLAG_STREXTR_SUB; + parser->sentinel++; + } + else if (*parser->sentinel >= '1' && *parser->sentinel <= '9') + { + // Parse explicit indentation level + int num = 0; + do { + num = (num * 10) + (*parser->sentinel++ - '0'); /* carry addition */ + } while (*parser->sentinel >= '0' && *parser->sentinel <= '9'); + + // Validate indentation level + if (num > 256) E(YAML_E_LNUMBER, parser->sentinel); + + // Record the number of Spaces + parser->flag.space_n = num; + + parser->flag.strextr = YAML_PFLAG_STREXTR_NUM; + } + else parser->flag.strextr = YAML_PFLAG_STREXTR_NULL; + + // Ensure no other content on this line + parser->sentinel = skip(parser->sentinel); + if (*parser->sentinel && *parser->sentinel != '\n') E(YAML_E_LBREAK, parser->sentinel); + + // Update the parsing position + text = parser->sentinel; + + continue; + } + else if (yaml->vtype == YAML_TYPE_STRING) + { + if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_NULL) E(YAML_E_MIX, parser->sentinel); + } + else + { + E(YAML_E_MIX, parser->sentinel); + } + } + // Handle anchor definition (&) + else if (*parser->sentinel == '&') + { + if (yaml->vtype == YAML_TYPE_NULL) + { + // Validate no existing anchor + if (yaml->alias) E(YAML_E_RANCHOR, parser->sentinel); + + // Parse anchor name + parser->line_s = parser->sentinel + 1; + + // doesn't stop until it encounters an invalid character + while (*parser->sentinel && *parser->sentinel > ' ') parser->sentinel++; + + // Validate anchor name length + if (parser->sentinel <= parser->line_s) E(YAML_E_NANCHOR, parser->sentinel); + + // The end position of alias is found, and the end position is recorded + parser->line_e = parser->sentinel - 1; + + // Continue exploring in the current line + parser->sentinel = skip(parser->sentinel); + + // Ensure no other content after anchor + if (*parser->sentinel != 0 && + *parser->sentinel != '\n' && + *parser->sentinel != '[' && + *parser->sentinel != '{' && + *parser->sentinel != '\'' && + *parser->sentinel != '\"') + { + E(YAML_E_IANCHOR, parser->sentinel); + } + + // Allocate and set anchor name + yaml->alias = yaml_strndup(parser->line_s, parser->line_e - parser->line_s + 1); + if (!yaml->alias) E(YAML_E_MEMORY, parser->sentinel); + + // Add anchor to lookup table + if (!yaml_add_anchor(parser->anchor, yaml)) + { + free(yaml->alias); + yaml->alias = NULL; + E(YAML_E_MEMORY, parser->sentinel); + } + + DEBUG("yaml->alias %s\r\n", yaml->alias); + + // Update the parsing position + text = parser->sentinel; + + continue; + } + else + { + E(YAML_E_MIX, parser->sentinel); + } + } + // Handle anchor reference (*) + else if (*parser->sentinel == '*') + { + if (yaml->vtype == YAML_TYPE_NULL) + { + // Parse anchor reference name + parser->line_s = parser->sentinel + 1; + + // Just skip to the end of the line + parser->sentinel = skip_ln(parser->line_s); + + // Go back from the end of the line to the end of the anchor name + parser->line_e = rskip(parser->sentinel - 1, parser->line_s); + + // Lookup referenced anchor + yaml_t anchor = yaml_match_anchor(parser->anchor, parser->line_s, parser->line_e - parser->line_s + 1); + if (!anchor) + { + E(YAML_E_UANCHOR, parser->sentinel); + } + + // Set value as reference + yaml->vtype = YAML_TYPE_REFERENCE; + yaml->value.child_ = anchor; + + // Update the parsing position + text = parser->sentinel; + + continue; + } + else + { + E(YAML_E_MIX, parser->sentinel); + } + } + // Handle sequence start ([) + else if (*parser->sentinel == '[') + { + parser->flag.tgtbase = YAML_PFLAG_TGTBASE_SEQUENCE; + + // brace parsing recursion depth is increased + rbrace++; + + // Initialize sequence type + if (yaml->vtype == YAML_TYPE_NULL) + { + yaml->value.child_ = NULL; + yaml->vtype = YAML_TYPE_SEQUENCE; + + // Update the parsing position + parser->sentinel++; + text = parser->sentinel; + + continue; + } + else if (yaml->vtype == YAML_TYPE_SEQUENCE) + { + if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_NULL) E(YAML_E_MIX, parser->sentinel); + } + else + { + E(YAML_E_MIX, parser->sentinel); + } + } + // Handle mapping start ({) + else if (*parser->sentinel == '{') + { + parser->flag.tgtbase = YAML_PFLAG_TGTBASE_MAPPING; + + // brace parsing recursion depth is increased + rbrace++; + + // Initialize mapping type + if (yaml->vtype == YAML_TYPE_NULL) + { + yaml->value.child_ = NULL; + yaml->vtype = YAML_TYPE_MAPPING; + + // Update the parsing position + parser->sentinel++; + text = parser->sentinel; + + continue; + } + else if (yaml->vtype == YAML_TYPE_MAPPING) + { + if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_NULL) E(YAML_E_MIX, parser->sentinel); + } + else + { + E(YAML_E_MIX, parser->sentinel); + } + } + // Handle sequence entry (-) + else if (*parser->sentinel == '-') + { + // A valid character is located + const char *t = skip(&parser->sentinel[1]); + + // The format is a list format + if (*t == 0 || // End of text + t - parser->sentinel > 1) // Or not '-' followed by a valid character + { + text = parse_sequence(yaml, text, token, parser, &p); + if (etype != YAML_E_OK) + { + return parser->sentinel; + } + + continue; + } + } + // Handle type specification (!!) + else if (*parser->sentinel == '!') + { + // Two consecutive '! 'is to parse by the specified type + if (parser->sentinel[1] == '!') + { + // Only those who have just started parsing yaml objects can specify type resolution + if (yaml->vtype == YAML_TYPE_NULL && parser->flag.tgtbase == YAML_PFLAG_TGTBASE_NULL) + { + // skip !! + parser->sentinel += 2; + if (parse_type_convert(yaml, parser) == NULL) + { + E(YAML_E_TARTGET, parser->sentinel); + } + + // Update the parsing position + parser->sentinel++; + text = parser->sentinel; + + continue; + } + } + } + // Handle complex key (?) + else if (*parser->sentinel == '?') + { + parser->sentinel++; + text = parser->sentinel; + + // Parse complex key + text = parse_complex_key(yaml, text, parser); + if (etype != YAML_E_OK) + { + return parser->sentinel; + } + + continue; + } + // Handle complex value (:) + else if (*parser->sentinel == ':') + { + // Parse value for complex key + if (parse_complex_value(yaml, parser->sentinel + 1, parser)) + { + + if (etype != YAML_E_OK) + { + return parser->sentinel; + } + + // Update the parsing position + text = parser->sentinel; + + continue; + } + } + + // There are no parse targets yet + // first check if it is a mapped object + // Default string parsing only if it's not a mapped object either + + // Record the current sub-depth of yaml + unsigned int csize = yaml->size; + + // First, follow the map to parse + text = parse_mapping(yaml, text, token, parser, &p); + if (etype != YAML_E_OK) + { + return parser->sentinel; + } + + // If mapping parsing succeeded, continue + if (yaml->size > csize) continue; + + // Fallback to parsing as plain string + text = parse_string_line(yaml, text, parser); + + DEBUG("value: %parser->sentinel\r\n", yaml->value.string_); + } + } + } + + +END: // Single exit point for successful parsing + + // Finalize value parsing + parser->scope_e = text; + DEBUG("> scope[%parser->sentinel] %d\r\n", yaml->key ? yaml->key : "-", parser->scope_e - parser->scope_s); + + // Validate value interval + parse_check_interval(yaml, text, parser); + + // Process trailing whitespace in strings + parse_string_end(yaml, text, parser); + + // Convert plain strings to appropriate scalar types + parse_scalar(yaml, parser); + + // Parse date/time values + if (parse_date(yaml, parser) == NULL) E(YAML_E_DATE, parser->sentinel); + + // Validate parsed type against expected target type + if (parser->flag.target != YAML_PFLAG_TARGET_NONE) + { + if ((parser->flag.target == YAML_PFLAG_TARGET_NULL && yaml->vtype != YAML_TYPE_NULL) || + (parser->flag.target == YAML_PFLAG_TARGET_BOOL && yaml->vtype != YAML_TYPE_BOOL) || + (parser->flag.target == YAML_PFLAG_TARGET_INT && yaml->vtype != YAML_TYPE_INT) || + (parser->flag.target == YAML_PFLAG_TARGET_FLOAT && yaml->vtype != YAML_TYPE_FLOAT) || + (parser->flag.target == YAML_PFLAG_TARGET_STR && yaml->vtype != YAML_TYPE_STRING) || + (parser->flag.target == YAML_PFLAG_TARGET_TIMESTAMP && yaml->vtype != YAML_TYPE_DATE) || + (parser->flag.target == YAML_PFLAG_TARGET_SEQ && yaml->vtype != YAML_TYPE_SEQUENCE) || + (parser->flag.target == YAML_PFLAG_TARGET_MAP && yaml->vtype != YAML_TYPE_MAPPING)) + { + E(YAML_E_TARTGET, parser->sentinel); + } + } + + return text; +} + +/** + * \brief Parse a YAML string into a YAML document tree + * \param[in] text: Pointer to the YAML string to parse + * \param[in] flag: Parsing flags (e.g., YAML_F_LDOCS for multiple documents) + * \return Pointer to the root YAML node on success, NULL on failure + * + * This function parses a YAML string and constructs a tree of YAML nodes. + * It supports both single and multi-document YAML streams based on the flags provided. + * Each document is parsed into a separate root node with its own anchor table. + * + * Memory management: + * - Calls yaml_create to allocate memory for YAML nodes (both document nodes and child nodes). + * - Allocates memory for the anchor table using malloc for each document node. + * - Uses memset to initialize the anchor table to zero. + * - Calls yaml_delete to free the memory of YAML nodes when an error occurs or during cleanup. + * - Calls yaml_attach to attach child nodes to the document node, which may involve memory management related to the node structure. + * + * Example usage: + * \code + * const char* yaml_text = "key: value\n---\nanother_key: another_value"; + * int flags = YAML_F_LDOCS; + * + * yaml_t root_node = yaml_loads(yaml_text, flags); + * if (root_node) { + * // The YAML string has been successfully parsed into a document tree. + * // The root_node points to the root of the YAML document tree. + * } else { + * // There was an error during parsing, such as memory allocation failure or incorrect YAML format. + * } + * \endcode + */ +yaml_t yaml_loads(const char* text, int flag) +{ + yaml_t yaml = NULL; + yaml_t child = NULL; + PARSER p; + + /* Reset error information */ + lbegin = text; + eline = 1; + etype = YAML_E_OK; + rbrace = 0; + + /* Parse each document in the stream until completion or error */ + do + { + // Create new YAML node for the document + child = yaml_create(); + if (!child) + { + E(YAML_E_MEMORY, NULL); + } + + /* Each document has its own anchor table for tracking references */ + child->anchor = (ANCHOR *)malloc(sizeof(ANCHOR)); + if (!child->anchor) + { + yaml_delete(child); + E(YAML_E_MEMORY, NULL); + } + // Initialize anchor table + memset(child->anchor, 0, sizeof(ANCHOR)); + + // Mark as anchor type (shared memory with key) + child->ktype = YAML_KTYPE_ANCHOR; + + // Initialize parser state + memset(&p, 0, sizeof(p)); + p.anchor = child->anchor; + + // Parse the document value + // Use -1 to indicate no parent indentation depth + text = parse_value(child, text, -1, 0, &p); + /* Handle parsing errors */ + if (etype != YAML_E_OK) + { + yaml_delete(child); + yaml_delete(yaml); + return NULL; + } + + /* Handle multi-document parsing */ + if (flag & YAML_F_LDOCS) + { + if (!yaml) + { + // Exit if end of text is reached + if (*text == 0) break; + + /* Create root document node */ + yaml = yaml_create(); + if (!yaml) + { + E(YAML_E_MEMORY, NULL); + } + + /* Set as document type */ + yaml->vtype = YAML_TYPE_DOCUMENT; + } + + // Attach parsed document to the document list + if (!yaml_attach(yaml, yaml->size, child)) + { + DEBUG("attach fail!\r\n"); + } + } + /* Single document mode - exit after first document */ + else + { + break; + } + + // Exit if end of text is reached + if (*text == 0) break; + + // Move to next document + text = p.sentinel + 1; + } while (*text); + + // Use single document as root if not in multi-document mode + if (!yaml) yaml = child; + + return yaml; +} + +/** + * \brief Load and parse a YAML file into a YAML document tree + * \param[in] filename: Path to the YAML file + * \param[in] flag: Parsing flags (e.g., YAML_F_LDOCS for multiple documents) + * \return Pointer to the root YAML node on success, NULL on failure + * + * This function reads a YAML file from disk and parses its contents. + * It handles file operations and error checking before passing the content to yaml_loads. + * The function first opens the file, determines its size, allocates memory to store the file content, + * reads the file into the allocated buffer, null-terminates the buffer, and then passes the content + * to yaml_loads for parsing. After parsing, it frees the memory allocated for the buffer. + * + * Memory management: + * - Allocates memory using malloc to store the file content in a buffer. + * - Frees the buffer memory using free after the content has been passed to yaml_loads for parsing. + * - Closes the file using fclose to release associated resources. + * + * Example usage: + * \code + * char* yaml_file_path = "example.yaml"; + * int parsing_flags = YAML_F_LDOCS; + * + * yaml_t root_node = yaml_file_load(yaml_file_path, parsing_flags); + * if (root_node) { + * // The YAML file has been successfully loaded and parsed into a document tree. + * // The root_node points to the root of the YAML document tree. + * } else { + * // There was an error during file reading, memory allocation, or YAML parsing. + * } + * \endcode + */ +yaml_t yaml_file_load(char* filename, int flag) +{ + FILE* f; + yaml_t yaml = NULL; + long len; + char* data; + + /* Open the YAML file */ + f = fopen(filename, "rb"); + if (!f) return NULL; + + /* Determine file size */ + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + + /* Allocate memory and read file content */ + data = (char*)malloc(len + 1); + if (data) + { + /* Read entire file into buffer */ + fread(data, 1, len, f); + fclose(f); + } + else + { + fclose(f); + return NULL; + } + + /* Null-terminate the buffer */ + data[len] = 0; + + /* Parse the YAML content */ + yaml = yaml_loads(data, flag); + + /* Clean up allocated resources */ + free(data); + + return yaml; +} + diff --git a/source/05_parser/yaml.h b/source/05_parser/yaml.h new file mode 100644 index 0000000..3357854 --- /dev/null +++ b/source/05_parser/yaml.h @@ -0,0 +1,278 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file yaml.h + * \unit yaml + * \brief This is a C language version of yaml streamlined parser + * \author Lamdonn + * \version v0.1.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2025 Lamdonn. + ********************************************************************************************************/ +#ifndef __yaml_H +#define __yaml_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include + +/* version infomation */ + +#define YAML_V_MAJOR 0 +#define YAML_V_MINOR 1 +#define YAML_V_PATCH 0 + +/* yaml type definition, hiding structural members, not for external use */ + +/** + * \brief Opaque handle to YAML node structure + * + * This typedef provides an opaque pointer to the internal YAML node structure. + * The actual structure definition is hidden from external code to maintain encapsulation. + */ +typedef struct YAML* yaml_t; + +/* yaml normal types define */ + +/** + * \brief Enumeration of YAML data types + * + * These constants represent the different data types supported by the YAML parser. + * They are used to identify the type of value stored in a YAML node. + */ +#define YAML_TYPE_NULL (0) /* null type */ +#define YAML_TYPE_BOOL (1) /* bool type */ +#define YAML_TYPE_INT (2) /* number int type */ +#define YAML_TYPE_FLOAT (3) /* number float type */ +#define YAML_TYPE_STRING (4) /* string type */ +#define YAML_TYPE_DATE (5) /* date type */ +#define YAML_TYPE_SEQUENCE (6) /* sequence type */ +#define YAML_TYPE_MAPPING (7) /* mapping type */ +#define YAML_TYPE_DOCUMENT (8) /* document type */ +#define YAML_TYPE_REFERENCE (9) /* reference type */ +#define YAML_TYPE_COMPLEX_KEY (10) /* complex key */ + +/* bool define */ +/** + * \brief Boolean values + * + * These constants represent boolean true and false values in the YAML parser. + */ +#define YAML_FALSE (0) /* bool false */ +#define YAML_TRUE (1) /* bool true */ + +/* error type define */ +/** + * \brief Error codes returned by the YAML parser + * + * These constants represent various error conditions that can occur during YAML parsing. + * They are used to indicate the nature of parsing errors to the calling code. + */ +#define YAML_E_OK (0) /* ok, no error */ +#define YAML_E_INVALID (1) /* invalid, not a valid expected value */ +#define YAML_E_END (2) /* many invalid characters appear at the end */ +#define YAML_E_KEY (3) /* parsing key, invalid key content found */ +#define YAML_E_VALUE (4) /* parsing value, invalid value content found */ +#define YAML_E_MEMORY (5) /* memory allocation failed */ +#define YAML_E_SQUARE (6) /* mising ']' */ +#define YAML_E_CURLY (7) /* mising '}' */ +#define YAML_E_TAB (8) /* incorrect indent depth */ +#define YAML_E_MIX (9) /* mix type */ +#define YAML_E_FLINE (10) /* the first line of value can only be a literal */ +#define YAML_E_LNUMBER (11) /* the number exceeds the storage capacity */ +#define YAML_E_LBREAK (12) /* line break */ +#define YAML_E_NANCHOR (13) /* null anchor */ +#define YAML_E_IANCHOR (14) /* invalid anchor */ +#define YAML_E_RANCHOR (15) /* repeat anchor */ +#define YAML_E_UANCHOR (16) /* undefine anchor */ +#define YAML_E_TANCHOR (17) /* type error anchor */ +#define YAML_E_DATE (18) /* date error */ +#define YAML_E_TARTGET (19) /* target error */ + +/* dump flags define */ +/** + * \brief Flags used to control YAML dumping and loading behavior + * + * These flags can be combined using bitwise OR to modify the behavior + * of functions that dump or load YAML documents. + */ +#define YAML_F_NONE (0) /* none flag */ +#define YAML_F_DFLOW (0x01) /* dumps flow format */ +#define YAML_F_LDOCS (0x02) /* load muti documents */ +#define YAML_F_NOKEY (0x04) /* operate without key */ +#define YAML_F_COMPLEX (0x08) /* operate with complex key */ +#define YAML_F_ANCHOR (0x10) /* operate with anchor */ +#define YAML_F_RECURSE (0x20) /* operate recurse */ +#define YAML_F_REFERENCE (0x40) /* operate with reference */ + +/** + * \brief Represents an invalid index value + * + * This constant is used to indicate an invalid or non-existent index + * in functions that operate on indexed YAML structures. + */ +#define YAML_INV_INDEX ((unsigned int)(-1)) + +// Functions for creating and deleting YAML nodes. +// These functions are used to initialize and free the memory resources of YAML nodes. + +yaml_t yaml_create(void); +void yaml_delete(yaml_t yaml); + +// Function for getting YAML parsing error information. +// It is used to obtain error line number and column number during YAML parsing. + +int yaml_error_info(int* line, int* column); + +// Functions for getting the type and size of a YAML node. +// Used to determine the data type of a YAML node and get the number of child elements of a container node. + +int yaml_type(yaml_t yaml); +unsigned int yaml_size(yaml_t yaml); + +// Functions for comparing and copying YAML nodes. +// Used to check if two YAML nodes are equal and to create a copy of a YAML node. + +int yaml_compare(yaml_t yaml, yaml_t cmp, int flag); +yaml_t yaml_copy(yaml_t yaml, int flag); + +// Functions for setting the key of a YAML node. +// Used to set a simple key or a complex key for a YAML node. + +yaml_t yaml_set_key(yaml_t yaml, const char* key); +yaml_t yaml_set_key_complex(yaml_t yaml, yaml_t key); + +// Functions for getting the key of a YAML node. +// Used to retrieve the simple key or complex key of a YAML node. + +const char* yaml_key(yaml_t yaml); +yaml_t yaml_key_complex(yaml_t yaml); + +// Functions for setting the value of a YAML node. +// Used to set a YAML node to different types of values such as null, boolean, integer, float, string, date, time, sequence, mapping, document, etc. + +yaml_t yaml_set_null(yaml_t yaml); +yaml_t yaml_set_bool(yaml_t yaml, int b); +yaml_t yaml_set_int(yaml_t yaml, int num); +yaml_t yaml_set_float(yaml_t yaml, double num); +yaml_t yaml_set_string(yaml_t yaml, const char* string); +yaml_t yaml_set_date(yaml_t yaml, int year, char month, char day); +yaml_t yaml_set_time(yaml_t yaml, char hour, char minute, char second, int msec); +yaml_t yaml_set_utc(yaml_t yaml, char hour, char minute); +yaml_t yaml_set_sequence(yaml_t yaml, yaml_t sequence); +yaml_t yaml_set_mapping(yaml_t yaml, yaml_t mapping); +yaml_t yaml_set_document(yaml_t yaml, yaml_t document); + +// Functions for getting the value of a YAML node. +// Used to retrieve the boolean value, integer value, float value, string value, sequence value, mapping value, document value, etc. of a YAML node. + +int yaml_value_bool(yaml_t yaml); +int yaml_value_int(yaml_t yaml); +double yaml_value_float(yaml_t yaml); +const char* yaml_value_string(yaml_t yaml); +yaml_t yaml_value_sequence(yaml_t yaml); +yaml_t yaml_value_mapping(yaml_t yaml); +yaml_t yaml_value_document(yaml_t yaml); + +// Functions for attaching and detaching YAML nodes. +// Used to attach a YAML node to another node or detach a child node from a node. + +yaml_t yaml_attach(yaml_t yaml, unsigned int index, yaml_t attach); +yaml_t yaml_dettach(yaml_t yaml, unsigned int index); + +// Functions for inserting values into a YAML node. +// Used to insert different types of values such as null, boolean, integer, float, string, sequence, mapping, document, reference, etc. into a YAML node. + +yaml_t yaml_insert_null(yaml_t yaml, const char* key, unsigned int index); +yaml_t yaml_insert_bool(yaml_t yaml, const char* key, unsigned int index, int b); +yaml_t yaml_insert_int(yaml_t yaml, const char* key, unsigned int index, int num); +yaml_t yaml_insert_float(yaml_t yaml, const char* key, unsigned int index, double num); +yaml_t yaml_insert_string(yaml_t yaml, const char* key, unsigned int index, const char* string); +yaml_t yaml_insert_sequence(yaml_t yaml, const char* key, unsigned int index, yaml_t sequence); +yaml_t yaml_insert_mapping(yaml_t yaml, const char* key, unsigned int index, yaml_t mapping); +yaml_t yaml_insert_document(yaml_t yaml, unsigned int index, yaml_t document); +yaml_t yaml_insert_reference(yaml_t yaml, const char* key, unsigned int index, const char* anchor, yaml_t doc); + +// Function for removing a value from a YAML node. +// Used to remove a value from a YAML node according to the key and index. + +int yaml_remove(yaml_t yaml, const char* key, unsigned int index); + +// Functions for getting the index of a YAML node. +// Used to obtain the index of a value in a YAML node, supporting the index acquisition of simple keys and complex keys. + +unsigned int yaml_get_index(yaml_t yaml, const char* key, unsigned int index); +unsigned int yaml_get_index_complex(yaml_t yaml, yaml_t key); + +// Functions for getting child nodes of a YAML node. +// Used to get the child nodes of a YAML node according to the key and index, supporting the retrieval of child nodes of simple keys and complex keys. + +yaml_t yaml_get_child(yaml_t yaml, const char* key, unsigned int index); +yaml_t yaml_get_child_complex(yaml_t yaml, yaml_t key); + +// Functions for setting and getting the alias of a YAML node. +// Used to set an alias for a YAML node and retrieve the alias of a YAML node. + +yaml_t yaml_set_alias(yaml_t yaml, const char* alias, yaml_t doc); +const char* yaml_get_alias(yaml_t yaml); + +// Functions for setting and getting the anchor of a YAML node. +// Used to set an anchor for a YAML node, retrieve the anchor, and get the size of the anchor. + +yaml_t yaml_set_anchor(yaml_t yaml, const char* anchor, yaml_t doc); +yaml_t yaml_get_anchor(yaml_t yaml, unsigned int index); +unsigned int yaml_anchor_size(yaml_t yaml); + +// YAML serialization functions. +// Used to serialize a YAML node into a string (yaml_dumps) or write it to a file (yaml_file_dump). + +char* yaml_dumps(yaml_t yaml, int preset, int* len, int flag); +int yaml_file_dump(yaml_t yaml, char* filename); + +// YAML deserialization functions. +// Used to load YAML data from a string (yaml_loads) or a file (yaml_file_dump) and construct a YAML node. + +yaml_t yaml_loads(const char* text, int flag); +yaml_t yaml_file_load(char* filename, int flag); + +// Macros for judging the type of a YAML node. +// Used to determine whether a YAML node is of a specific type (null, bool, int, float, string, sequence, mapping, document). + +#define yaml_isnull(yaml) (yaml_type(yaml)==YAML_TYPE_NULL) +#define yaml_isbool(yaml) (yaml_type(yaml)==YAML_TYPE_BOOL) +#define yaml_isint(yaml) (yaml_type(yaml)==YAML_TYPE_INT) +#define yaml_isfloat(yaml) (yaml_type(yaml)==YAML_TYPE_FLOAT) +#define yaml_isstring(yaml) (yaml_type(yaml)==YAML_TYPE_STRING) +#define yaml_issequence(yaml) (yaml_type(yaml)==YAML_TYPE_SEQUENCE) +#define yaml_ismapping(yaml) (yaml_type(yaml)==YAML_TYPE_MAPPING) +#define yaml_isdocument(yaml) (yaml_type(yaml)==YAML_TYPE_DOCUMENT) + +// Macros for adding values to a YAML node. +// Used to quickly add different types of values to a YAML sequence or mapping node. + +#define yaml_seq_add_null(yaml) yaml_insert_null((yaml), 0, yaml_size((yaml))) +#define yaml_seq_add_bool(yaml, b) yaml_insert_bool((yaml), 0, yaml_size((yaml)), (b)) +#define yaml_seq_add_int(yaml, num) yaml_insert_int((yaml), 0, yaml_size((yaml)), (num)) +#define yaml_seq_add_float(yaml, num) yaml_insert_float((yaml), 0, yaml_size((yaml)), (num)) +#define yaml_seq_add_string(yaml, string) yaml_insert_string((yaml), 0, yaml_size((yaml)), (string)) +#define yaml_seq_add_sequence(yaml, sequence) yaml_insert_sequence((yaml), 0, yaml_size((yaml)), (sequence)) +#define yaml_seq_add_mapping(yaml, mapping) yaml_insert_mapping((yaml), 0, yaml_size((yaml)), (mapping)) +#define yaml_map_add_null(yaml, key) yaml_insert_null((yaml), (key), yaml_size((yaml))) +#define yaml_map_add_bool(yaml, key, b) yaml_insert_bool((yaml), (key), yaml_size((yaml)), (b)) +#define yaml_map_add_int(yaml, key, num) yaml_insert_int((yaml), (key), yaml_size((yaml)), (num)) +#define yaml_map_add_float(yaml, key, num) yaml_insert_float((yaml), (key), yaml_size((yaml)), (num)) +#define yaml_map_add_string(yaml, key, string) yaml_insert_string((yaml), (key), yaml_size((yaml)), (string)) +#define yaml_map_add_sequence(yaml, key, sequence) yaml_insert_sequence((yaml), (key), yaml_size((yaml)), (sequence)) +#define yaml_map_add_mapping(yaml, key, mapping) yaml_insert_mapping((yaml), (key), yaml_size((yaml)), (mapping)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/test/file/read.yaml b/test/file/read.yaml new file mode 100644 index 0000000..70a1e4c --- /dev/null +++ b/test/file/read.yaml @@ -0,0 +1,108 @@ +mapping: + open: Yes + + string null: string null + string null n: string null + n + + string sigle: 'string sigle' + string sigle n: 'string + sigle' + + string double: "string double" + string double n: "string + double" + + string scalar: | + string + scalar + + string scalar n: |2 + string + scalar + + string scalar -: |- + string + scalar + + string scalar +: |+ + string + scalar + + + + + + string fold: > + string + fold + + string fold n: >2 + string + fold + + string fold -: >- + string + fold + + string fold +: >+ + string + fold + +sequence: + - file description + - This is a C language version of yaml streamlined parser + - Copyright (C) 2023 Lamdonn. + - + age: 18 + weight: 75kg + number dec: 123456 + number hex: 0x123456 + number float: 012.3456 + number e: 1.23e-4 + number nan: .nan + number inf: .inf + number inf n: -.inf + +Anchors: + people: &people + name: people + age: 18 + height: 178 + cat: &cat + name: cat + age: 2 + string: &string + This is string + sequence: &sequence + - 1 + - 2 + +Zhang San: &default + color: blue + size: medium + property: *people + +Inherit: + <<: *people + # shape: circle + +square brackets: [1,2,3,4] + +brace brackets: {a: 1, b: 2, c: 3} + +bin: -0b1010_0111_0100_1010_1110 + +iso8601: 2025-03-29T23:59:43.89-05:00 +date: 2025-03-29 +time: 2025-03-29T23:59:43 +utc: 2025-03-29T23:59:43+08:00 + +int_22: !!int 123456 + +muti: + a: "AABCDEFG" + ? + - a + - b + : fruit \ No newline at end of file diff --git a/test/file/write.yaml b/test/file/write.yaml new file mode 100644 index 0000000..3620c57 --- /dev/null +++ b/test/file/write.yaml @@ -0,0 +1,11 @@ +mapping: + version: 1.0.0 + author: Lamdonn + license: GPL-2.0 +sequence: + - file description + - This is a C language version of yaml streamlined parser + - Copyright (C) 2023 Lamdonn. + - + age: 18 + weight: 75kg diff --git a/test/test.mk b/test/test.mk index f64539f..3743dc0 100644 --- a/test/test.mk +++ b/test/test.mk @@ -9,6 +9,7 @@ TEST_LIST += txls TEST_LIST += xml TEST_LIST += csv TEST_LIST += json +TEST_LIST += yaml TEST_LIST += vector TEST_LIST += list TEST_LIST += str diff --git a/test/test_yaml.c b/test/test_yaml.c new file mode 100644 index 0000000..f500541 --- /dev/null +++ b/test/test_yaml.c @@ -0,0 +1,579 @@ +#include +#include +#if defined(TEST_TARGET_yaml) +#include +#include +#include +#else +#include "init.h" +#include "command.h" +#include "unitt.h" +#include "kern.h" +#include "yaml.h" +#endif + +/************************************************************************************/ +/************************************* Unit Test ************************************/ +/************************************************************************************/ + +// #define EXIT_TEST +extern uint64_t unitt_clock(void); + +static int test_0(void) +{ + for (int i = 0; i < 100; i++) + { + if (0) + { + + #if defined (EXIT_TEST) + exit(0); + #endif + return UNITT_E_FAIL; + } + } + + return UNITT_E_OK; +} + +static void unitt_task(void) +{ + static UNITT_TCASE rand_tests[] = { + UNITT_TCASE(test_0), + // UNITT_TCASE(test_1), + // UNITT_TCASE(test_2), + }; + + static UNITT suites[] = { + { "xxx suite", rand_tests, sizeof(rand_tests) / sizeof(rand_tests[0]) , unitt_clock }, + }; + + UNITT_EXE(suites); +} + +/************************************************************************************/ +/************************************* Base Test ************************************/ +/************************************************************************************/ + +#define READ_FILE "test/file/read.yaml" +#define WRITE_FILE "test/file/write.yaml" + +static void yaml_preview(yaml_t yaml) +{ + char *s = yaml_dumps(yaml, 0, 0, YAML_F_NONE); + if (s) + { + printf("%s\r\n", s); + free(s); + } +} + +static void test_create(void) +{ + yaml_t yaml = yaml_create(); + if (yaml) + { + printf("yaml_create success!!! %p\r\n", yaml); + } + else + { + printf("yaml_create fail!!!\r\n"); + } + yaml_delete(yaml); +} + +static void test_add(void) +{ + yaml_t yaml = NULL, info = NULL, hobby = NULL, key = NULL, complex = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + yaml_map_add_int(info, "age", 26); + yaml_map_add_float(info, "height", 178.5); + yaml_map_add_string(info, "email", "Lamdonn@163.com"); + + yaml_set_alias(info, "Lamdonn", yaml); + + info = yaml_map_add_mapping(yaml, "Zhangsan", NULL); + yaml_map_add_int(info, "age", 29); + yaml_map_add_float(info, "height", 175); + yaml_map_add_string(info, "email", "Zhangsan@163.com"); + + hobby = yaml_map_add_sequence(info, "hobby", NULL); + yaml_seq_add_string(hobby, "Playing basketball"); + yaml_seq_add_string(hobby, "Playing football"); + yaml_seq_add_string(hobby, "Playing mobile game"); + yaml_t kk = yaml_seq_add_mapping(hobby, NULL); + yaml_map_add_int(kk, "year", 29); + yaml_map_add_float(kk, "month", 6); + + info = yaml_map_add_mapping(yaml, "Lisi", NULL); + complex = yaml_map_add_int(info, "1", 29); + + yaml_preview(yaml); + + yaml_delete(yaml); +} + +static void test_insert(void) +{ + yaml_t root = NULL, doc = NULL, t = NULL; + root = yaml_create(); + + doc = yaml_insert_document(root, yaml_size(root), NULL); + yaml_map_add_int(doc, "age", 26); + yaml_map_add_float(doc, "height", 178.5); + yaml_map_add_string(doc, "email", "Lamdonn@163.com"); + + doc = yaml_insert_document(root, yaml_size(root), NULL); + yaml_map_add_int(doc, "age", 26); + yaml_map_add_float(doc, "height", 178.5); + yaml_map_add_string(doc, "email", "Lamdonn@163.com"); + + doc = yaml_insert_document(root, yaml_size(root), NULL); + yaml_map_add_int(doc, "age", 26); + yaml_map_add_float(doc, "height", 178.5); + yaml_map_add_string(doc, "email", "Lamdonn@163.com"); + + doc = yaml_insert_document(root, yaml_size(root), NULL); + yaml_map_add_int(doc, "age", 26); + yaml_map_add_float(doc, "height", 178.5); + yaml_map_add_string(doc, "email", "Lamdonn@163.com"); + + yaml_preview(root); + + yaml_delete(root); +} + +static void test_remove(void) +{ + yaml_t yaml = NULL, info = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + yaml_map_add_int(info, "age", 26); + yaml_map_add_float(info, "height", 178.5); + yaml_map_add_string(info, "email", "Lamdonn@163.com"); + + yaml_remove(info, "email", 0); + + yaml_preview(yaml); + + yaml_delete(yaml); +} + +static void test_set(void) +{ + yaml_t yaml = NULL, info = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + yaml_map_add_int(info, "age", 26); + yaml_map_add_float(info, "height", 178.5); + yaml_map_add_string(info, "email", "Lamdonn@163.com"); + + yaml_set_null(info); + yaml_preview(yaml); + + yaml_set_bool(info, YAML_TRUE); + yaml_preview(yaml); + + yaml_set_int(info, 123); + yaml_preview(yaml); + + yaml_set_float(info, 3.14159); + yaml_preview(yaml); + + yaml_set_string(info, "Hello yaml"); + yaml_preview(yaml); + + yaml_set_date(info, 2025, 01, 01); + yaml_set_time(info, 16, 30, 03, 200); + yaml_set_utc(info, +8, 30); + yaml_preview(yaml); + + yaml_delete(yaml); +} + +static void test_get(void) +{ + yaml_t yaml = NULL, info = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + + yaml_t bool_ = yaml_map_add_bool(info, "open source", YAML_TRUE); + printf("bool_: %d\r\n", yaml_value_bool(bool_)); + + yaml_t int_ = yaml_map_add_int(info, "age", 26); + printf("int_: %d\r\n", yaml_value_int(int_)); + + yaml_t float_ = yaml_map_add_float(info, "height", 178.5); + printf("float_: %f\r\n", yaml_value_float(float_)); + + yaml_t string_ = yaml_map_add_string(info, "email", "Lamdonn@163.com"); + printf("string_: %s\r\n", yaml_value_string(string_)); + + yaml_preview(yaml); + + yaml_delete(yaml); +} + +static void test_key(void) +{ + yaml_t yaml = NULL, key = NULL; + yaml = yaml_create(); + + yaml_set_key(yaml, "StringKey"); + printf("key: %s\r\n", yaml_key(yaml)); + + key = yaml_create(); + yaml_map_add_int(key, "a", 12); + yaml_map_add_int(key, "b", 13); + yaml_set_key_complex(yaml, key); + printf("complex key: \r\n"); + yaml_preview(yaml_key_complex(yaml)); + + yaml_preview(yaml); + + yaml_delete(yaml); +} + +static void test_child(void) +{ + yaml_t yaml = NULL, info = NULL, key = NULL, child = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + yaml_map_add_int(info, "age", 26); + yaml_map_add_float(info, "height", 178.5); + yaml_map_add_string(info, "email", "Lamdonn@163.com"); + + key = yaml_create(); + yaml_map_add_int(key, "a", 12); + yaml_map_add_int(key, "b", 13); + yaml_set_key_complex(yaml_map_add_string(info, "0", "This is a complex key"), key); + + child = yaml_get_child(info, "age", 0); + printf("child['age']: %d\r\n", yaml_value_int(child)); + + child = yaml_get_child(info, NULL, 1); + printf("child[1]: %f\r\n", yaml_value_float(child)); + + child = yaml_get_child_complex(info, key); + printf("child[?]: %s\r\n", yaml_value_string(child)); + + printf("child['email'] index = %u\r\n", yaml_get_index(info, "email", 0)); + printf("child[?] index = %u\r\n", yaml_get_index_complex(info, key)); + + yaml_preview(yaml); + + yaml_delete(yaml); +} + +static void test_alias(void) +{ + yaml_t yaml = NULL, info = NULL, temp = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + yaml_map_add_int(info, "age", 26); + yaml_map_add_float(info, "height", 178.5); + yaml_map_add_string(info, "email", "Lamdonn@163.com"); + + yaml_set_alias(info, "AuthorInfo", yaml); + + printf("alias: %s\r\n", yaml_get_alias(info)); + + yaml_preview(yaml); + + yaml_delete(yaml); +} + +static void test_anchor(void) +{ + yaml_t yaml = NULL, info = NULL, temp = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + yaml_map_add_int(info, "age", 26); + yaml_map_add_float(info, "height", 178.5); + yaml_map_add_string(info, "email", "Lamdonn@163.com"); + + yaml_set_alias(info, "AuthorInfo", yaml); + + for (int i = 0; i < yaml_anchor_size(yaml); i++) + { + yaml_t t = yaml_get_anchor(yaml, i); + printf("anchor[%d]: %s\r\n", i, yaml_get_alias(t)); + } + + temp = yaml_map_add_mapping(yaml, "Something", NULL); + yaml_set_anchor(temp, "AuthorInfo", yaml); + + temp = yaml_map_add_mapping(yaml, "Extended", NULL); + yaml_insert_reference(temp, NULL, 0, "AuthorInfo", yaml); + + yaml_preview(yaml); + + yaml_delete(yaml); +} + +static void test_copy(void) +{ + yaml_t yaml = NULL, info = NULL, copy = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + yaml_map_add_int(info, "age", 26); + yaml_map_add_float(info, "height", 178.5); + yaml_map_add_string(info, "email", "Lamdonn@163.com"); + printf("SOURCE yaml: \r\n"); + yaml_preview(yaml); + + printf("COPY yaml: \r\n"); + copy = yaml_copy(yaml, YAML_F_RECURSE); + yaml_preview(copy); + + yaml_delete(copy); + yaml_delete(copy); +} + +static void test_compare(void) +{ + yaml_t yaml = NULL, info = NULL, temp = NULL, copy = NULL; + yaml = yaml_create(); + + info = yaml_map_add_mapping(yaml, "Lamdonn", NULL); + yaml_map_add_int(info, "age", 26); + yaml_map_add_float(info, "height", 178.5); + yaml_map_add_string(info, "email", "Lamdonn@163.com"); + + copy = yaml_copy(yaml, YAML_F_RECURSE); + + printf("yaml_compare %d\r\n", yaml_compare(yaml, copy, YAML_F_RECURSE)); + + yaml_delete(copy); + yaml_delete(copy); +} + +static void test_dump(void) +{ + yaml_t root, node, temp; + + root = yaml_create(); + yaml_set_mapping(root, NULL); + + node = yaml_map_add_mapping(root, "mapping", NULL); + yaml_map_add_string(node, "version", "1.0.0"); + yaml_map_add_string(node, "author", "Lamdonn"); + yaml_map_add_string(node, "license", "GPL-2.0"); + + node = yaml_map_add_sequence(root, "sequence", NULL); + yaml_seq_add_string(node, "file description"); + yaml_seq_add_string(node, "This is a C language version of yaml streamlined parser"); + yaml_seq_add_string(node, "Copyright (C) 2023 Lamdonn."); + temp = yaml_seq_add_mapping(node, NULL); + yaml_map_add_string(temp, "age", "18"); + yaml_map_add_string(temp, "height", "178cm"); + yaml_map_add_string(temp, "weight", "75kg"); + + yaml_remove(temp, 0, 1); + + /* preview yaml */ + yaml_preview(root); + + /* dump yaml file */ + yaml_file_dump(root, WRITE_FILE); + + yaml_delete(root); +} + +static void test_load(void) +{ + yaml_t root = NULL, x = NULL; + + root = yaml_file_load(READ_FILE, YAML_F_LDOCS); + if (!root) + { + int type = 0, line = 0, column = 0; + type = yaml_error_info(&line, &column); + printf("error at line %d column %d type %d.\r\n", line, column, type); + return; + } + printf("load success!\r\n"); + + yaml_preview(root); + + yaml_delete(root); +} + +static void test_base(void) +{ + // test_dump(); + test_load(); +} + +/************************************************************************************/ +/************************************* Command ************************************/ +/************************************************************************************/ + +static void usage(void) +{ + printf( +"Usage: yaml [opt] [arg] ...\n" +"\n" +"options:\n" +" -e Specifies the function to execute, the default is the test\n" +" Test base function\n" +" Unit test\n" +" Test create and delete functions\n" +" Test add category functions\n" +" Test insert category functions\n" +" Test remove category functions\n" +" Test set category functions\n" +" Test get category functions\n" +" Test key category functions\n" +" Test child category functions\n" +" Test alias category functions\n" +" Test anchor category functions\n" +" Test copy function\n" +" Test compare function\n" +" Test dump functions\n" +" Test load functions\n" +" -h Print help\n" +" -v Print version\n" +" -u [] Unit test period, unit ms, the default is 1000ms\n" +"\n" + ); +} + +static int test(int argc, char *argv[]) +{ + char *execute = NULL; + int ut_period = 1000; + + /* reset getopt */ + command_opt_init(); + + while (1) + { + int opt = command_getopt(argc, argv, "e:hvu::"); + if (opt == -1) break; + + switch (opt) + { + case 'u' : + if (command_optarg) ut_period = atoi(command_optarg); + break; + case 'e' : + execute = command_optarg; + break; + case 'v' : + printf("yaml version %d.%d.%d\r\n", YAML_V_MAJOR, YAML_V_MINOR, YAML_V_PATCH); + return 0; + case '?': + printf("Unknown option `%c`\r\n", command_optopt); + return -1; + case 'h' : + default: + usage(); + return 0; + } + } + + if (execute) + { + if (!strcmp(execute, "base")) + { + test_base(); + } + else if (!strcmp(execute, "ut")) + { + #if defined(TEST_TARGET_yaml) + while (1) + { + unitt_task(); + usleep(1000 * ut_period); + } + #else + printf("create task %d\r\n", task_create(ut_period, unitt_task)); + #endif + } + else if (!strcmp(execute, "add")) + { + test_add(); + } + else if (!strcmp(execute, "insert")) + { + test_insert(); + } + else if (!strcmp(execute, "remove")) + { + test_remove(); + } + else if (!strcmp(execute, "set")) + { + test_set(); + } + else if (!strcmp(execute, "get")) + { + test_get(); + } + else if (!strcmp(execute, "key")) + { + test_key(); + } + else if (!strcmp(execute, "child")) + { + test_child(); + } + else if (!strcmp(execute, "alias")) + { + test_alias(); + } + else if (!strcmp(execute, "anchor")) + { + test_anchor(); + } + else if (!strcmp(execute, "copy")) + { + test_copy(); + } + else if (!strcmp(execute, "compare")) + { + test_compare(); + } + else if (!strcmp(execute, "dump")) + { + test_dump(); + } + else if (!strcmp(execute, "load")) + { + test_load(); + } + } + else + { + test_base(); + } + + return 0; +} + +/************************************************************************************/ +/************************************ Test entry ************************************/ +/************************************************************************************/ + +#if defined(TEST_TARGET_yaml) +int main(int argc, char *argv[]) +{ + return test(argc, argv); +} +#else +void test_yaml(void) +{ + command_export("yaml", test); +} +init_export_app(test_yaml); +#endif