| 677 | } |
| 678 | |
| 679 | Expected<std::unique_ptr<FilterInstance>> createInstance( |
| 680 | const FilterClass& klass, const std::string& params_json) override { |
| 681 | auto budget = std::make_unique<BudgetState>(); |
| 682 | lua_State* L = newSandboxedState(budget.get(), limits_); |
| 683 | if (L == nullptr) { |
| 684 | return PJ::unexpected("failed to create Luau state"); |
| 685 | } |
| 686 | bool keep = false; |
| 687 | struct Closer { |
| 688 | lua_State* s; |
| 689 | bool* keep; |
| 690 | ~Closer() { |
| 691 | if (!*keep) { |
| 692 | lua_close(s); |
| 693 | } |
| 694 | } |
| 695 | } closer{L, &keep}; |
| 696 | |
| 697 | if (std::string err = loadModule(L, budget.get(), klass.source); !err.empty()) { |
| 698 | return PJ::unexpected(err); |
| 699 | } |
| 700 | const int module_idx = lua_gettop(L); |
| 701 | if (!lua_istable(L, module_idx)) { |
| 702 | return PJ::unexpected("a filter module must return a table"); |
| 703 | } |
| 704 | |
| 705 | // Locate the class table whose `id` == klass.id (single or within a list). |
| 706 | int class_idx = 0; |
| 707 | lua_rawgetfield(L, module_idx, "create"); |
| 708 | const bool is_single = lua_isfunction(L, -1); |
| 709 | lua_pop(L, 1); |
| 710 | if (is_single) { |
| 711 | if (stringField(L, module_idx, "id") == klass.id) { |
| 712 | class_idx = module_idx; |
| 713 | } |
| 714 | } else { |
| 715 | const int count = arrayLen(L, module_idx); |
| 716 | for (int i = 1; i <= count && class_idx == 0; ++i) { |
| 717 | lua_rawgeti(L, module_idx, i); // leave candidate on stack |
| 718 | if (lua_istable(L, -1) && stringField(L, lua_gettop(L), "id") == klass.id) { |
| 719 | class_idx = lua_gettop(L); // keep this one on the stack |
| 720 | } else { |
| 721 | lua_pop(L, 1); |
| 722 | } |
| 723 | } |
| 724 | } |
| 725 | if (class_idx == 0) { |
| 726 | return PJ::unexpected("class id '" + klass.id + "' not found in module"); |
| 727 | } |
| 728 | |
| 729 | // Build the params table and call create(params). |
| 730 | lua_rawgetfield(L, class_idx, "create"); |
| 731 | if (!lua_isfunction(L, -1)) { |
| 732 | return PJ::unexpected("filter class '" + klass.id + "' has no create() function"); |
| 733 | } |
| 734 | lua_newtable(L); |
| 735 | const int params_idx = lua_gettop(L); |
| 736 | // Seed every declared parameter with its default first, then override with |