| 166 | // BICUBIC already use. |
| 167 | |
| 168 | AbstractState* AbstractState::factory(const std::string& backend, const std::vector<std::string>& fluid_names) { |
| 169 | if (get_debug_level() > 0) { |
| 170 | std::cout << "AbstractState::factory(" << backend << "," << stringvec_to_string(fluid_names) << ")" << '\n'; |
| 171 | } |
| 172 | |
| 173 | // Split off any "?<options>" suffix once at the entry-point so the |
| 174 | // rest of the dispatch operates on the unadorned backend / fluid |
| 175 | // strings. PropsSI calls extract_backend() first, which splits |
| 176 | // "BACKEND::FLUID?<options>" on "::" without touching '?' — so |
| 177 | // the suffix arrives on the *fluid* side. Direct factory() |
| 178 | // callers may put it on either side. Strip from wherever it |
| 179 | // lives (rejecting the ambiguous both-sides case) and thread the |
| 180 | // raw options JSON through to the backend generator via the |
| 181 | // options-aware overload below. See |
| 182 | // docs/superpowers/specs/2026-05-16-backend-options-string-design.md. |
| 183 | auto parsed = parse_factory_options(backend); |
| 184 | std::string clean_backend = parsed.clean_string; |
| 185 | std::string options_json = parsed.options_json; |
| 186 | std::vector<std::string> clean_fluid_names = fluid_names; |
| 187 | // The `?<options>` suffix on the fluid side can land on ANY |
| 188 | // element of fluid_names — for mixtures, the |
| 189 | // factory(string, string) convenience overload calls |
| 190 | // strsplit('&') before forwarding, so e.g. |
| 191 | // "HEOS::R32&R125?{...}" arrives as ["R32", "R125?{...}"] with |
| 192 | // the suffix on the LAST token. Scan the whole vector; throw |
| 193 | // if two different tokens carry a suffix (typo); always strip |
| 194 | // the trailing '?' so downstream fluid lookup sees the bare |
| 195 | // component name. |
| 196 | std::size_t fluid_options_index = clean_fluid_names.size(); // sentinel = none |
| 197 | for (std::size_t i = 0; i < clean_fluid_names.size(); ++i) { |
| 198 | if (clean_fluid_names[i].find('?') == std::string::npos) { |
| 199 | continue; |
| 200 | } |
| 201 | if (fluid_options_index != clean_fluid_names.size()) { |
| 202 | throw ValueError("factory: '?<options>' supplied in multiple fluid components; encode it once."); |
| 203 | } |
| 204 | fluid_options_index = i; |
| 205 | } |
| 206 | if (fluid_options_index != clean_fluid_names.size()) { |
| 207 | auto parsed_fluid = parse_factory_options(clean_fluid_names[fluid_options_index]); |
| 208 | if (!parsed_fluid.options_json.empty() && !options_json.empty()) { |
| 209 | throw ValueError("factory: '?<options>' supplied on both the backend and fluid sides; pick one side."); |
| 210 | } |
| 211 | // Always replace the affected fluid name with the stripped |
| 212 | // form — even when the suffix carried no options (bare '?' |
| 213 | // or whitespace) — so the downstream fluid lookup doesn't |
| 214 | // see a trailing '?'. |
| 215 | clean_fluid_names[fluid_options_index] = parsed_fluid.clean_string; |
| 216 | if (!parsed_fluid.options_json.empty()) { |
| 217 | options_json = parsed_fluid.options_json; |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | backend_families f1; |
| 222 | std::string f2; |
| 223 | extract_backend_families_string(clean_backend, f1, f2); |
| 224 | |
| 225 | std::map<backend_families, shared_ptr<AbstractStateGenerator>>::const_iterator gen, end; |
no test coverage detected