{"id":21098490,"url":"https://github.com/scarsty/cifa","last_synced_at":"2026-06-12T22:33:34.819Z","repository":{"id":92170460,"uuid":"265778771","full_name":"scarsty/cifa","owner":"scarsty","description":"类C语法脚本极简实现","archived":false,"fork":false,"pushed_at":"2026-06-02T09:05:26.000Z","size":351,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-06-02T11:07:05.349Z","etag":null,"topics":["cpp","script"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scarsty.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-05-21T07:03:14.000Z","updated_at":"2026-05-28T02:26:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"137269d2-83fe-4741-a03d-0d88dec21d23","html_url":"https://github.com/scarsty/cifa","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/scarsty/cifa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scarsty%2Fcifa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scarsty%2Fcifa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scarsty%2Fcifa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scarsty%2Fcifa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scarsty","download_url":"https://codeload.github.com/scarsty/cifa/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scarsty%2Fcifa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34265491,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","script"],"created_at":"2024-11-19T22:55:26.885Z","updated_at":"2026-06-12T22:33:34.797Z","avatar_url":"https://github.com/scarsty.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cifa\n\n## 简介\n\n一个简易类C语法脚本。语法是C的子集，有少量C++风格的扩展。\n\n用于嵌入一些只需要简单计算，且不想引入较复杂的外部库的C++程序。\n\n例如某些情况下，需要在外部配置文件中执行一个简单的C++过程，且希望程序内部的代码可以不修改直接放到配置文件。\n\n项目名就是“词法”。实际上本垃圾一开始理解错了，以为词法分析要生成语法树，就这样吧。\n\n执行模式为直接对语法树求值，并处理分支和循环。\n\n千万不要用它完成过于复杂的任务，正如描述所说，这只是一个非常简单的语法解析器和执行器。如果你想做更复杂的事，请选用Python或者Lua。如果你喜欢C风格的语法，请选用AngelScript或者ChaiScript，但是这两个库都不小，而且在很多Linux发行版上没有预编译包可用。\n\n有大佬做的js版：\u003chttps://github.com/whyb/cifa.js\u003e，线上演示为：\u003chttps://whyb.github.io/cifa.js/playground/web\u003e.\n\nps：目前经实测，Cifa已经能在一定程度上替换Lua。\n\nCifa曾经单独开发，后来为方便改成了mlcc的一部分，目前因为AI的发展，可以迅速为其添加功能，逐渐可以实用化，因此再次转为独立的项目。\n\n## 使用方法\n\n### 基本用法\n\n在自己的工程中加入Cifa.h和Cifa.cpp即可。例程如下：\n\n```c++\n#include \"Cifa.h\"\n#include \u003cfstream\u003e\n#include \u003ciostream\u003e\n\nusing namespace cifa;\n\nint main()\n{\n    Cifa c1;\n    std::ifstream ifs;\n    ifs.open(\"1.c\");\n    std::string str;\n    getline(ifs, str, '\\0');\n    auto o = c1.run_script(str);\n    if(o.hasValue() \u0026\u0026 o.isNumber() \u0026\u0026 o.isType\u003cdouble\u003e())\n    {\n        std::cout \u003c\u003c \"Cifa value is: \" \u003c\u003c o.ref\u003cdouble\u003e() \u003c\u003c \"\\n\";\n    }\n}\n```\n\n其中1.c文件即为脚本内容，一个例子为：\n\n```c++\nint sum = 1;\nfor (int i = 1; i \u003c= 10; i++)\n{\n    for (int j = 1; j \u003c= 10; j++)\n    {\n        int x = 0;\n        if ((i + j) % 2 == 0)\n        {\n            x = -1;\n        }\n        else\n            x = 1;\n        sum += (i * j) * x;\n    }\n}\nreturn sum;\n```\n\n计算的结果为-24。\n\n若脚本只有一个表达式，则结果就是表达式的求值。若脚本包含多行，则需用return来指定返回值，否则返回值是`\u003cempty\u003e`（强行当成数值则是NaN）。\n\n### 宿主程序添加自定义函数\n\n自定义函数必须可以转化为`std::function\u003ccifa::Object(cifa::ObjectVector\u0026)\u003e`，其中`cifa::ObjectVector`即`std::vector\u003ccifa::Object\u003e`。\n\n#### 方式一\n\n以下自定义3个数学函数（省略了检测越界）：\n```c++\nusing namespace cifa;\n\nObject sin1(ObjectVector\u0026 d) { return sin(d[0]); }\nObject cos1(ObjectVector\u0026 d) { return cos(d[0]); }\nObject pow1(ObjectVector\u0026 d) { return pow(d[0].value, d[1].value); }\n\nint main()\n{\n    Cifa c1;\n    c1.register_function(\"sin\", sin1);\n    c1.register_function(\"cos\", cos1);\n    c1.register_function(\"pow\", pow1);\n    //....\n}\n```\n这里函数原型写成了xxx1的形式，只是为了避免与cmath中的数学函数同名，造成一些麻烦。\n\n#### 方式二\n\n其实更推荐lambda表达式的形式，例如将上面正弦函数的注册修改为：\n\n```c++\n    c1.register_function(\"sin\", [](ObjectVector\u0026 d) { return sin(d[0]); });\n```\n这样也不必再定义sin1这个函数。\n\n此时再运行如下脚本：\n```c++\nauto pi = 3.1415927;\nprint(sin(pi / 6));\nprint(cos(pi / 6));\nprint(pow(2, 10));\n```\n输出应是：\n```\n0.5\n0.866025\n1024\n```\n需注意语言已经内置了一些函数，如下面的表格所示。如果想覆盖掉内置函数，可以直接注册同名函数即可。\n\n### 脚本中的自定义函数\n\n例如脚本为：\n\n```c++\nmyfun(i)\n{\n    return i*i*i+i*i+i+1;\n}\n\nprint(myfun(3));\n```\n可以得到输出为40。\n\n注意这种函数不能做类型检查。\n\n### 预定义变量\n\n通过预定义变量可以模拟一些外置函数的效果。下面这个例子中，将pi预先定义好，并将degree视作一个C++送到Cifa的参数:\n```c++\n    c1.register_parameter(\"degree\", 30);\n    c1.register_parameter(\"pi\", 3.14159265358979323846);\n```\n脚本为：\n```c++\nprint(sin(degree*pi/180));\n```\n输出应为0.5。\n\n### 变量的可见范围\n\n一对大括号{}内定义的变量在该大括号内可见，外部不可见。函数参数在函数体内可见。与C++规则相同。\n\n### 用户的数据类型\n\nCifa中Object的实现其实是std::any，故可以容纳任何类型。但目前数值相关的类型实际都是double，另内置了std::string的支持。\n\n如果用户希望使用自己的类型，需要增加一些功能函数和对应的运算符重载。\n\n例如，增加以下几个函数支持OpenCV中cv::Mat相关的一些功能：\n\n```c++\n    c.register_function(\"imread\", [](cifa::ObjectVector\u0026 v) -\u003e cifa::Object\n        {\n            int flag = -1;\n            if (v.size() \u003e= 2)\n            {\n                flag = int(v[1]);\n            }\n            return cv::Mat(cv::imread(v[0].toString(), flag));\n        });\n    c.register_function(\"imshow\", [](cifa::ObjectVector\u0026 v) -\u003e cifa::Object\n        {\n            cv::imshow(v[0].toString(), v[1].to\u003ccv::Mat\u003e());\n            return cifa::Object();\n        });\n    c.register_function(\"imwrite\", [](cifa::ObjectVector\u0026 v) -\u003e cifa::Object\n        {\n            cv::imwrite(v[0].toString(), v[1].to\u003ccv::Mat\u003e());\n            return cifa::Object();\n        });\n```\n除此之外，也可以支持用户自定义某些运算符的重载，但是需注意应进行类型检查。下面以增加加号和减号的重载为例：\n```c++\n    c.user_add.push_back([](const cifa::Object\u0026 l, const cifa::Object\u0026 r) -\u003e cifa::Object\n        {\n            if (l.isType\u003ccv::Mat\u003e() \u0026\u0026 r.isType\u003ccv::Mat\u003e())\n            {\n                return cv::Mat(l.to\u003ccv::Mat\u003e() + r.to\u003ccv::Mat\u003e());\n            }\n            return cifa::Object();\n        });\n    c.user_sub.push_back([](const cifa::Object\u0026 l, const cifa::Object\u0026 r) -\u003e cifa::Object\n        {\n            if (l.isType\u003ccv::Mat\u003e() \u0026\u0026 r.isType\u003ccv::Mat\u003e())\n            {\n                return cv::Mat(l.to\u003ccv::Mat\u003e() - r.to\u003ccv::Mat\u003e());\n            }\n            return cifa::Object();\n        });\n```\n\n因为变量作用域的关系，用户自定义类型会按照RAII的原则进行管理，无需手动释放资源，即没有必要进行垃圾收集。但是用户原则上不应使用非RAII的类型。\n\n### 数组\n\n#### 空数组初始化\n\n有两种方式创建空数组：\n\n```c++\nint a[];        // 类型声明方式\nb = {};         // 赋值方式\n```\n\n两种写法均会产生一个长度为 0 的数组，之后可以用 `push_back` 等方法添加元素。\n\n#### 数组越界行为\n\n- **写入越界**：自动扩展数组大小，中间元素为空值。\n- **读取越界**：同样自动扩展，返回的空值在后续数值运算中可能产生运行时错误。\n\n#### 数组字面量\n\n使用 `{}` 花括号构造数组，元素之间用逗号分隔。元素类型可以混合（数字、字符串等）：\n\n```c++\narr = {1, 2, 3, 4, 5};\nmixed = {1, \"hello\", 3.14, \"world\"};\n```\n\n#### 数组下标访问\n\n使用整数下标访问元素，下标从 0 开始：\n\n```c++\narr = {10, 20, 30};\nint x = arr[0];    // x = 10\narr[1] = 99;       // 修改元素\n\nint i = 2;\ndouble v = arr[i]; // 变量作为下标\n```\n\n#### 多维数组\n\n数组元素本身也可以是数组（嵌套）：\n\n```c++\ngrid = { {1, 2}, {3, 4} };\nint v = grid[1][0];    // v = 3\n```\n\n#### 数组大小\n\n使用内置函数 `size()` 获取数组元素个数：\n\n```c++\narr = {1, 2, 3, 4, 5};\nint n = size(arr);    // n = 5\n```\n\n#### 从宿主注册向量\n\n宿主程序可以将 `std::vector\u003cdouble\u003e` 注册为脚本内的数组变量：\n\n```c++\nc1.register_vector(\"v\", std::vector\u003cdouble\u003e{1.2, 1.45, 77.3});\n```\n\n脚本中即可 `v[0]`、`v[1]` 访问。\n\n### 字符串键映射（Map）\n\n变量可通过字符串下标使用，此时它会变成一个 `string → Object` 的映射。\n\n```c++\ndict[\"name\"] = \"Alice\";\ndict[\"age\"]  = 30;\n\nstring key = \"name\";\nstring n = dict[key];    // n = \"Alice\"\ndouble a = dict[\"age\"];  // a = 30\n```\n\n`size(dict)` 返回 map 中的元素个数。\n\n访问不存在的 key 后使用该值会触发运行时错误。\n\n#### Map 越界行为\n\n访问不存在的 key 会自动创建该 key 并赋空值（与 C++ `std::map::operator[]` 行为一致）。\n\n### 数组和 Map 的内置方法\n\n数组和 Map 支持通过 `.` 语法调用内置方法，对自身进行原地修改。\n\n#### 数组方法\n\n| 方法 | 说明 | 返回值 |\n|------|------|--------|\n| `arr.push_back(x)` | 在末尾追加元素 `x`（支持多个参数） | 新的数组大小 |\n| `arr.pop_back()` | 移除最后一个元素 | 新的数组大小 |\n| `arr.resize(n)` | 将数组大小调整为 `n` | 新的数组大小 |\n| `arr.insert(i, x)` | 在下标 `i` 处插入元素 `x` | 新的数组大小 |\n| `arr.erase(i)` | 删除下标 `i` 处的元素 | 新的数组大小 |\n| `arr.clear()` | 清空所有元素 | 0 |\n| `arr.contains(x)` | 检查数组中是否存在值 `x` | 1（存在）或 0（不存在） |\n\n示例：\n\n```c++\na = {};\na.push_back(10);\na.push_back(20);\na.push_back(30);\na.insert(1, 15);    // a = {10, 15, 20, 30}\na.erase(0);          // a = {15, 20, 30}\nint has = a.contains(20);  // has = 1\na.clear();           // a = {}\n```\n\n#### Map 方法\n\n| 方法 | 说明 | 返回值 |\n|------|------|--------|\n| `m.erase(\"key\")` | 删除指定 key | 新的 map 大小 |\n| `m.clear()` | 清空所有键值对 | 0 |\n| `m.contains(\"key\")` | 检查 key 是否存在 | 1（存在）或 0（不存在） |\n| `m.keys()` | 返回所有 key 组成的数组 | 字符串数组 |\n\n示例：\n\n```c++\nm[\"name\"] = \"Alice\";\nm[\"age\"] = 30;\nint has = m.contains(\"name\");  // has = 1\nk = m.keys();                  // k = {\"age\", \"name\"}（按字典序）\nm.erase(\"age\");\nint n = size(m);               // n = 1\n```\n\n### 结构体（struct）\n\nCifa 支持 C 风格的结构体定义，用于将多个字段组合成一个命名类型。\n\n#### 定义与声明\n\n```c++\nstruct Point { int x; int y; };\nPoint p;\n```\n\n- `struct` 定义声明字段列表，字段类型名（如 `int`）会被忽略，仅字段名有效。\n- 结构体名本身作为类型关键字使用，声明变量时自动将其初始化为含所有字段的对象。\n\n#### 字段读写\n\n```c++\nstruct Point { int x; int y; };\nPoint p;\np.x = 10;\np.y = 20;\nreturn p.x + p.y;    // 30\n```\n\n#### 复合赋值\n\n```c++\nstruct Counter { int n; };\nCounter cnt;\ncnt.n = 5;\ncnt.n += 3;    // cnt.n == 8\n```\n\n#### 作为函数参数\n\n结构体实例可以作为参数传入脚本定义的函数：\n\n```c++\nstruct Rect { int w; int h; };\narea(r) { return r.w * r.h; }\nRect r;\nr.w = 4; r.h = 5;\nreturn area(r);    // 20\n```\n\n\u003e **注意**：传递时为值拷贝，函数内修改字段不影响外部变量。\n\n#### 跨调用复用\n\n`struct` 定义绑定在 `Cifa` 实例上，**只需定义一次**，后续对同一实例的 `run_script` 调用均可直接使用该类型，无需重复写定义：\n\n```c++\nCifa c;\nc.run_script(\"struct Point { int x; int y; };\");   // 第一次：定义结构体\n\nauto o = c.run_script(R\"(\n    Point p;\n    p.x = 3; p.y = 7;\n    return p.x + p.y;\n)\");    // 第二次：直接使用，结果为 10\n```\n\n#### 限制\n\n- 不支持嵌套 struct 定义（字段类型只能是基本类型）。\n- 不支持构造函数、成员函数、继承等 C++ OOP 特性。\n\n### 字符串\n\n字符串长度可用 `size()` 获取：\n\n```c++\nstring s = \"hello\";\nint n = size(s);    // n = 5\n```\n\n字符串拼接用 `+`：\n\n```c++\nstring s = \"hello\" + \" \" + \"world\";\n```\n\n类型转换：\n- `to_string(3.14)` → 将数值转为字符串\n- `to_number(\"3.14\")` → 将字符串转为数值\n\n### 内置函数汇总\n\n| 函数 | 说明 |\n|------|------|\n| `print(...)` | 输出一个或多个值，不换行 |\n| `println(...)` | 输出一个或多个值，最后换行 |\n| `to_string(x)` | 将数值转为字符串 |\n| `to_number(s)` | 将字符串转为数值 |\n| `size(x)` | 返回数组、map 或字符串的大小 |\n| `pow(x, y)` | x 的 y 次方 |\n| `max(a, b, ...)` | 多个数中的最大值 |\n| `min(a, b, ...)` | 多个数中的最小值 |\n| `random()` | `[0, 1)` 均匀随机数；`random(n)` 返回 `[0, n)`；`random(a, b)` 返回 `[a, b)` |\n| `ifv(cond, a, b)` | 三元选择，等价于 `cond ? a : b` |\n| `abs / sqrt / round / floor / ceil` | 常见数学函数 |\n| `sin / cos / tan / asin / acos / atan` | 三角函数 |\n| `sinh / cosh / tanh` | 双曲函数 |\n| `exp / log / log10` | 指数对数函数 |\n| `sprintf(fmt, ...)` | C printf 风格格式化，支持 `%s %d %f %g %x` 等；`%%` 输出字面 `%` |\n| `format(fmt, ...)` | `{}` / `{N}` 占位符风格格式化；整数不带小数点，`{{` / `}}` 转义为字面括号 |\n\n### 错误处理\n\nCifa 将错误分为两类：**语法错误**（静态检查阶段）和**运行时错误**（执行阶段）。\n\n#### 语法错误\n\n```c++\nif (c.has_error())\n{\n    // 推荐：返回带源码行和位置指示的错误字符串\n    std::string err = c.get_errors_str();\n    std::cerr \u003c\u003c err;\n\n    // 或者直接打印到 stderr\n    // c.print_errors();\n\n    // 也可逐条访问（建议优先使用上面的字符串接口）\n    for (auto\u0026 e : c.get_errors())\n    {\n        // e.line, e.col, e.message\n    }\n}\n```\n\n错误输出示例：\n\n**未初始化变量**（脚本：`int y = undef;`）：\n```\nSyntax Error: parameter undef is at right of = but not been initialized\n  at line 2, col 9: int y = undef;\n                            ^\n```\n\n**未定义函数**（脚本：`return foo(1, 2);`）：\n```\nSyntax Error: function foo is not defined\n  at line 1, col 8: return foo(1, 2);\n                           ^\n```\n\n**不可赋值的左值**（脚本：`123 = 5;`）：\n```\nSyntax Error: 123 cannot be assigned\n  at line 1, col 1: 123 = 5;\n                    ^\n```\n\n**括号不匹配**（脚本：`int x = (1 + 2));`）：\n```\nSyntax Error: unpaired right bracket )\n  at line 1, col 16: int x = (1 + 2));\n                                    ^\n```\n\n**else 无对应 if**（脚本：`int x = 1; else { x = 2; }`）：\n```\nSyntax Error: else has no if\n  at line 2, col 1: else { x = 2; }\n                    ^\n```\n\n**位置信息**：下表所有语法错误均记录了精确的行列位置，`get_errors_str()` / `print_errors()` 输出时均会附带原始源码行与 `^` 位置指示。\n\n\u003e 若某个错误节点未能记录位置（内部极少见情况），则仅输出 `at line 0, col 0`，不附原始行文本。\n\n语法错误在 `run_script` 返回前会自动打印到 stderr（`output_error` 为 `true` 时，这是默认行为），也可手动调用 `print_errors()` 或 `get_errors_str()` 获取。\n\n静态检查可检出的语法错误列表：\n\n| 错误 | 说明 |\n|------|------|\n| unpaired right bracket | 右括号 `)` / `]` / `}` 无对应左括号 |\n| unpaired left bracket | 左括号 `(` / `[` / `{` 无对应右括号 |\n| parameter ... is at right of = but not been initialized | 使用了未经赋值的变量 |\n| ... cannot be assigned | 赋值左侧是常量或字符串字面量 |\n| function ... is not defined | 调用了未注册/未定义的函数 |\n| function ... has no operands | 函数缺少参数列表 |\n| operator ? has no : | 三元运算符 `?` 缺少 `:` 分支 |\n| if/while has empty condition | `if()` 或 `while()` 条件为空 |\n| if has no condition/statement | `if` 缺少条件或语句体 |\n| else has no if | `else` 无对应 `if` |\n| while has constant true condition, may cause infinite loop | `while(1)` / `while(true)` 静态检测到潜在死循环 |\n| for loop may cause infinite loop | `for(;;)` 等无终止条件 |\n| for loop condition is not right | `for` 循环条件格式不正确 |\n| while/do while has no statement/condition | 循环缺少必要部分 |\n| switch has no condition/statement | `switch` 缺少条件或语句体 |\n| case has no condition / case missing : | `case` 格式不正确 |\n| default missing : | `default` 后缺少冒号 |\n| missing ; | 语句末尾缺少分号 |\n| no parameters inside [] | 下标表达式为空（数组声明 `int a[];` 除外） |\n| wrong parameters inside [] / () | 括号内参数格式不正确 |\n| variable declaration not allowed in non-block body | 在无 `{}` 的分支/循环/case 体中定义或引入了新变量 |\n\n#### 运行时错误\n\n执行阶段的错误（如类型不兼容、访问不存在的 map 键等）：\n\n- 运行时错误在**触发时立即自动打印**到 stderr（`output_error` 为 `true` 时，这是默认行为）。\n- 若要关闭自动输出，可在 `run_script` 前调用 `c.set_output_error(false)`。\n- `run_script` 的返回值会携带错误标记，可用于程序逻辑判断：\n\n```c++\n// 运行时错误发生时会自动打印到 stderr\n// 若要关闭自动输出：c.set_output_error(false);\n\nauto result = c.run_script(script);\nif (result.getSpecialType() == \"Error\")\n{\n    // 运行时发生了错误（已自动打印到 stderr）\n}\n```\n\n**位置信息**：自动输出的运行时错误包含完整调用栈，每个调用帧均附带原始源码行与 `^` 位置指示。\n\n运行时错误输出示例（含调用栈）：\n\n**类型转换失败**（脚本：`int bad; return sqrt(bad);`）：\n```\nRuntime Error: type conversion failed: variable 'bad' from \u003cempty\u003e to double\nCall Stack (most recent call last):\n  at func sqrt()\n  at line 2, col 8: return sqrt(bad);\n                           ^\n```\n\n**无限递归**（脚本：`f(n){ return f(n); } return f(0);`）：\n```\nRuntime Error: max call depth exceeded (possible infinite recursion)\nCall Stack (most recent call last):\n  at line 1, col 14: f(n){ return f(n); }\n                                  ^\n  at func f()\n  at func f()\n  at line 2, col 8: return f(0);\n                           ^\n```\n\n**循环超限**（脚本：`for (int i = 0; i \u003c 99999999; i++) {}`）：\n```\nRuntime Error: for loop exceeded max iterations\nCall Stack (most recent call last):\n  at line 1, col 1: for (int i = 0; i \u003c 99999999; i++) {}\n                    ^\n```\n\n运行时错误列表：\n\n| 错误 | 说明 |\n|------|------|\n| type conversion failed: variable '...' from ... to double | 将非数值类型（空值、字符串等）转换为 double 时失败 |\n| type conversion failed: variable '...' from ... to string | 将非字符串类型转换为 string 时失败 |\n| max call depth exceeded (possible infinite recursion) | 函数递归调用过深，超过最大调用深度 |\n| for/while/do-while loop exceeded max iterations | 循环次数超过最大限制 |\n| function ... is not defined | 调用了运行时未找到的函数 |\n| ...() is not supported on arrays/maps | 对数组或 map 调用了不支持的内置方法 |\n| ...() requires an array or map | 对非数组、非 map 的变量调用了内置方法 |\n\n### 语法上的注意事项\n\n- Cifa的变量定义其实不需要指定类型，但是为了实现“简单C（C++）代码可以直接被Cifa运行”这一目的，auto、int、float、double等类型名会被忽略。- 结构体（struct）名用作类型时同理，声明变量时只需使用结构体名，字段声明中的类型名同样被忽略。- 未经初始化即出现在赋值号右侧的变量值为空，即std::any的`\u003cempty\u003e`，相当于强制要求显式初始化。\n- 函数调用时，a.func(c)等价于func(a, c)。但对于内置数组/map方法（push_back、erase等），仅能通过 `.` 语法调用，不能写成 `push_back(arr, x)` 的形式——这是因为内置方法需要直接修改原始变量，而普通函数调用传的是值的副本，无法修改原始对象。\n- 自加算符不支持++++或----这种写法，请不要瞎折腾。\n- 没有goto。\n\n### 一个完整的用例\n\n以下是使用Cifa计算一个数值的完整用例，包含错误检查和结果处理：\n\n```c++\n    cifa::Cifa cifa;\n    std::string str1 = \"a1 = 2;\\na3=a1;\\nreturn 5+4*9*(a1+3)/23;\";\n    auto c = cifa.run_script(str1);\n    if (cifa.has_error())    //检查语法错误\n    {\n        //可以选择输出语法错误字符串（已包含源码行和错误位置指示）\n        std::string err_str = cifa.get_errors_str();\n        std::cerr \u003c\u003c err_str;\n        //也可以直接打印到 stderr\n        //cifa.print_errors();\n    }\n    //无语法错误，判断结果是否是一个数值\n    if (c.isNumber())\n    {\n        std::print(\"{}\\n\", c.toDouble());\n    }\n    //若需正常继续计算，需要排除nan和inf\n    if (c.isEffectNumber())\n    {\n        //do something\n    }\n```\n\n\n## 其他\n\n### 已知问题\n\n- 生成语法树时的检查不太严格，例如if和while后面的条件其实可以不写括号，但是最好要写全（此处若严格处理需要将括号多归约一层，略微影响效率）。\n- 报错位置有时不太准确。例如括号被归约后，在语法树上就不再存在，所以会相差一个或多个括号。要解决需要在归约时记录最终位置，比较复杂且意义不是很大，不再处理。\n\n### 有可能会加的\n\n- 变量的括号初始化。\n- 遍历最终语法树可以生成执行码，不再处理。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscarsty%2Fcifa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscarsty%2Fcifa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscarsty%2Fcifa/lists"}