diff options
| author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2016-03-24 23:19:07 -0400 | 
|---|---|---|
| committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2016-03-24 23:19:07 -0400 | 
| commit | 1c30fe15bbb7adedf6631c9432f8e20b75e6f3bd (patch) | |
| tree | 2aca49bead3e39570b666788b4e89fb9849a89ca | |
| parent | cc20ae82dd724ec28c34bb2b66eb09693bfaa0f1 (diff) | |
| download | furries-1c30fe15bbb7adedf6631c9432f8e20b75e6f3bd.tar.gz furries-1c30fe15bbb7adedf6631c9432f8e20b75e6f3bd.tar.bz2 furries-1c30fe15bbb7adedf6631c9432f8e20b75e6f3bd.zip  | |
Added support for participle phrases
| -rw-r--r-- | furries.cpp | 416 | ||||
| m--------- | vendor/verbly | 0 | 
2 files changed, 370 insertions, 46 deletions
| diff --git a/furries.cpp b/furries.cpp index e09732c..712ef7c 100644 --- a/furries.cpp +++ b/furries.cpp | |||
| @@ -16,19 +16,151 @@ class fill_blanks { | |||
| 16 | 16 | ||
| 17 | } | 17 | } | 
| 18 | 18 | ||
| 19 | void visit(std::unique_ptr<verbly::token>& it) | 19 | verbly::filter<verbly::noun> parse_selrestrs(verbly::frame::selrestr selrestr) | 
| 20 | { | 20 | { | 
| 21 | switch (it->token_type()) | 21 | switch (selrestr.get_type()) | 
| 22 | { | ||
| 23 | case verbly::frame::selrestr::type::empty: | ||
| 24 | { | ||
| 25 | return verbly::filter<verbly::noun>{}; | ||
| 26 | } | ||
| 27 | |||
| 28 | case verbly::frame::selrestr::type::singleton: | ||
| 29 | { | ||
| 30 | verbly::noun n; | ||
| 31 | |||
| 32 | if (selrestr.get_restriction() == "concrete") | ||
| 33 | { | ||
| 34 | n = database.nouns().with_singular_form("physical entity").limit(1).run().front(); | ||
| 35 | } else if (selrestr.get_restriction() == "time") | ||
| 36 | { | ||
| 37 | n = database.nouns().with_singular_form("time").limit(1).run().front(); | ||
| 38 | } else if (selrestr.get_restriction() == "state") | ||
| 39 | { | ||
| 40 | n = database.nouns().with_singular_form("state").limit(1).run().front(); | ||
| 41 | } else if (selrestr.get_restriction() == "abstract") | ||
| 42 | { | ||
| 43 | n = database.nouns().with_singular_form("abstract entity").limit(1).run().front(); | ||
| 44 | } else if (selrestr.get_restriction() == "time") | ||
| 45 | { | ||
| 46 | n = database.nouns().with_singular_form("time").limit(1).run().front(); | ||
| 47 | } else if (selrestr.get_restriction() == "scalar") | ||
| 48 | { | ||
| 49 | n = database.nouns().with_singular_form("number").limit(1).run().front(); | ||
| 50 | } else if (selrestr.get_restriction() == "currency") | ||
| 51 | { | ||
| 52 | auto nn2 = database.nouns().with_singular_form("currency").limit(2).run(); | ||
| 53 | std::vector<verbly::noun> nn(std::begin(nn2), std::end(nn2)); | ||
| 54 | n = nn[1]; | ||
| 55 | } else if (selrestr.get_restriction() == "location") | ||
| 56 | { | ||
| 57 | n = database.nouns().with_singular_form("location").limit(1).run().front(); | ||
| 58 | } else if (selrestr.get_restriction() == "organization") | ||
| 59 | { | ||
| 60 | n = database.nouns().with_singular_form("organization").limit(1).run().front(); | ||
| 61 | } else if (selrestr.get_restriction() == "int_control") | ||
| 62 | { | ||
| 63 | n = database.nouns().with_singular_form("causal agent").limit(1).run().front(); | ||
| 64 | } else if (selrestr.get_restriction() == "natural") | ||
| 65 | { | ||
| 66 | n = database.nouns().with_singular_form("natural object").limit(1).run().front(); | ||
| 67 | } else if (selrestr.get_restriction() == "phys_obj") | ||
| 68 | { | ||
| 69 | n = database.nouns().with_singular_form("physical object").limit(1).run().front(); | ||
| 70 | } else if (selrestr.get_restriction() == "solid") | ||
| 71 | { | ||
| 72 | n = database.nouns().with_singular_form("solid").limit(1).run().front(); | ||
| 73 | } else if (selrestr.get_restriction() == "shape") | ||
| 74 | { | ||
| 75 | n = database.nouns().with_singular_form("shape").limit(1).run().front(); | ||
| 76 | } else if (selrestr.get_restriction() == "substance") | ||
| 77 | { | ||
| 78 | n = database.nouns().with_singular_form("substance").limit(1).run().front(); | ||
| 79 | } else if (selrestr.get_restriction() == "idea") | ||
| 80 | { | ||
| 81 | n = database.nouns().with_singular_form("idea").limit(1).run().front(); | ||
| 82 | } else if (selrestr.get_restriction() == "sound") | ||
| 83 | { | ||
| 84 | auto nn2 = database.nouns().with_singular_form("sound").limit(4).run(); | ||
| 85 | std::vector<verbly::noun> nn(std::begin(nn2), std::end(nn2)); | ||
| 86 | n = nn[3]; | ||
| 87 | } else if (selrestr.get_restriction() == "communication") | ||
| 88 | { | ||
| 89 | n = database.nouns().with_singular_form("communication").limit(1).run().front(); | ||
| 90 | } else if (selrestr.get_restriction() == "region") | ||
| 91 | { | ||
| 92 | n = database.nouns().with_singular_form("region").limit(1).run().front(); | ||
| 93 | } else if (selrestr.get_restriction() == "place") | ||
| 94 | { | ||
| 95 | n = database.nouns().with_singular_form("place").limit(1).run().front(); | ||
| 96 | } else if (selrestr.get_restriction() == "machine") | ||
| 97 | { | ||
| 98 | n = database.nouns().with_singular_form("machine").limit(1).run().front(); | ||
| 99 | } else if (selrestr.get_restriction() == "animate") | ||
| 100 | { | ||
| 101 | n = database.nouns().with_singular_form("animate being").limit(1).run().front(); | ||
| 102 | } else if (selrestr.get_restriction() == "plant") | ||
| 103 | { | ||
| 104 | auto nn2 = database.nouns().with_singular_form("plant").limit(2).run(); | ||
| 105 | std::vector<verbly::noun> nn(std::begin(nn2), std::end(nn2)); | ||
| 106 | n = nn[1]; | ||
| 107 | } else if (selrestr.get_restriction() == "comestible") | ||
| 108 | { | ||
| 109 | n = database.nouns().with_singular_form("food").limit(1).run().front(); | ||
| 110 | } else if (selrestr.get_restriction() == "artifact") | ||
| 111 | { | ||
| 112 | n = database.nouns().with_singular_form("artifact").limit(1).run().front(); | ||
| 113 | } else if (selrestr.get_restriction() == "vehicle") | ||
| 114 | { | ||
| 115 | n = database.nouns().with_singular_form("vehicle").limit(1).run().front(); | ||
| 116 | } else if (selrestr.get_restriction() == "human") | ||
| 117 | { | ||
| 118 | n = database.nouns().with_singular_form("person").limit(1).run().front(); | ||
| 119 | } else if (selrestr.get_restriction() == "animal") | ||
| 120 | { | ||
| 121 | n = database.nouns().with_singular_form("animal").limit(1).run().front(); | ||
| 122 | } else if (selrestr.get_restriction() == "body_part") | ||
| 123 | { | ||
| 124 | n = database.nouns().with_singular_form("body part").limit(1).run().front(); | ||
| 125 | } else if (selrestr.get_restriction() == "garment") | ||
| 126 | { | ||
| 127 | n = database.nouns().with_singular_form("clothing").limit(1).run().front(); | ||
| 128 | } else if (selrestr.get_restriction() == "tool") | ||
| 129 | { | ||
| 130 | n = database.nouns().with_singular_form("tool").limit(1).run().front(); | ||
| 131 | } else { | ||
| 132 | return verbly::filter<verbly::noun>{}; | ||
| 133 | } | ||
| 134 | |||
| 135 | return verbly::filter<verbly::noun>{n, !selrestr.get_pos()}; | ||
| 136 | } | ||
| 137 | |||
| 138 | case verbly::frame::selrestr::type::group: | ||
| 139 | { | ||
| 140 | verbly::filter<verbly::noun> ret; | ||
| 141 | ret.set_orlogic(selrestr.get_orlogic()); | ||
| 142 | |||
| 143 | std::transform(std::begin(selrestr), std::end(selrestr), std::back_inserter(ret), [&] (verbly::frame::selrestr sr) { | ||
| 144 | return parse_selrestrs(sr); | ||
| 145 | }); | ||
| 146 | |||
| 147 | return ret; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | void visit(verbly::token& it) | ||
| 153 | { | ||
| 154 | switch (it.get_type()) | ||
| 22 | { | 155 | { | 
| 23 | case verbly::token::type::utterance: | 156 | case verbly::token::type::utterance: | 
| 24 | { | 157 | { | 
| 25 | auto& action = *dynamic_cast<verbly::utterance_token*>(it.get()); | 158 | for (auto& tkn : it) | 
| 26 | for (auto& tkn : action) | ||
| 27 | { | 159 | { | 
| 28 | if (!tkn->complete()) | 160 | if (!tkn.is_complete()) | 
| 29 | { | 161 | { | 
| 30 | visit(tkn); | 162 | visit(tkn); | 
| 31 | 163 | ||
| 32 | break; | 164 | break; | 
| 33 | } | 165 | } | 
| 34 | } | 166 | } | 
| @@ -38,46 +170,249 @@ class fill_blanks { | |||
| 38 | 170 | ||
| 39 | case verbly::token::type::fillin: | 171 | case verbly::token::type::fillin: | 
| 40 | { | 172 | { | 
| 41 | auto& tkn = *dynamic_cast<verbly::fillin_token*>(it.get()); | 173 | switch (it.get_fillin_type()) | 
| 42 | switch (tkn.get_fillin_type()) | ||
| 43 | { | 174 | { | 
| 44 | case verbly::fillin_type::participle_phrase: | 175 | case verbly::token::fillin_type::participle_phrase: | 
| 176 | { | ||
| 177 | for (;;) | ||
| 178 | { | ||
| 179 | verbly::verb v = database.verbs().has_frames().random().limit(1).run().front(); | ||
| 180 | auto frames = v.frames().run(); | ||
| 181 | std::vector<verbly::frame> filtered; | ||
| 182 | std::remove_copy_if(std::begin(frames), std::end(frames), std::back_inserter(filtered), [] (verbly::frame& f) { | ||
| 183 | if (f.parts().size() < 2) | ||
| 184 | { | ||
| 185 | return true; | ||
| 186 | } | ||
| 187 | |||
| 188 | if (f.parts()[0].get_type() != verbly::frame::part::type::noun_phrase) | ||
| 189 | { | ||
| 190 | return true; | ||
| 191 | } | ||
| 192 | |||
| 193 | if (f.parts()[0].get_role() != "Agent") | ||
| 194 | { | ||
| 195 | return true; | ||
| 196 | } | ||
| 197 | |||
| 198 | if (f.parts()[1].get_type() != verbly::frame::part::type::verb) | ||
| 199 | { | ||
| 200 | return true; | ||
| 201 | } | ||
| 202 | |||
| 203 | return false; | ||
| 204 | }); | ||
| 205 | |||
| 206 | if (filtered.empty()) | ||
| 207 | { | ||
| 208 | continue; | ||
| 209 | } | ||
| 210 | |||
| 211 | verbly::frame fr = filtered[rand() % filtered.size()]; | ||
| 212 | verbly::token utter; | ||
| 213 | for (auto part : fr.parts()) | ||
| 214 | { | ||
| 215 | switch (part.get_type()) | ||
| 216 | { | ||
| 217 | case verbly::frame::part::type::noun_phrase: | ||
| 218 | { | ||
| 219 | if (part.get_role() == "Agent") | ||
| 220 | { | ||
| 221 | continue; | ||
| 222 | } | ||
| 223 | |||
| 224 | if (part.get_synrestrs().count("adjp") == 1) | ||
| 225 | { | ||
| 226 | utter << verbly::token{verbly::token::fillin_type::adjective_phrase}; | ||
| 227 | |||
| 228 | continue; | ||
| 229 | } else if ((part.get_synrestrs().count("be_sc_ing") == 1) | ||
| 230 | || (part.get_synrestrs().count("ac_ing") == 1) | ||
| 231 | || (part.get_synrestrs().count("sc_ing") == 1) | ||
| 232 | || (part.get_synrestrs().count("np_omit_ing") == 1) | ||
| 233 | || (part.get_synrestrs().count("oc_ing") == 1)) | ||
| 234 | { | ||
| 235 | utter << verbly::token{verbly::token::fillin_type::participle_phrase}; | ||
| 236 | |||
| 237 | continue; | ||
| 238 | } else if ((part.get_synrestrs().count("poss_ing") == 1) | ||
| 239 | || (part.get_synrestrs().count("possing") == 1) | ||
| 240 | || (part.get_synrestrs().count("pos_ing") == 1)) | ||
| 241 | { | ||
| 242 | utter << verbly::token{"their"}; | ||
| 243 | utter << verbly::token{verbly::token::fillin_type::participle_phrase}; | ||
| 244 | |||
| 245 | continue; | ||
| 246 | } else if (part.get_synrestrs().count("genitive") == 1) | ||
| 247 | { | ||
| 248 | utter << verbly::token{"their"}; | ||
| 249 | |||
| 250 | continue; | ||
| 251 | } else if (part.get_synrestrs().count("adv_loc") == 1) | ||
| 252 | { | ||
| 253 | if (rand() % 2 == 0) | ||
| 254 | { | ||
| 255 | utter << verbly::token{"here"}; | ||
| 256 | } else { | ||
| 257 | utter << verbly::token{"there"}; | ||
| 258 | } | ||
| 259 | |||
| 260 | continue; | ||
| 261 | } else if (part.get_synrestrs().count("refl") == 1) | ||
| 262 | { | ||
| 263 | utter << verbly::token{"themselves"}; | ||
| 264 | |||
| 265 | continue; | ||
| 266 | } else if ((part.get_synrestrs().count("sc_to_inf") == 1) | ||
| 267 | || (part.get_synrestrs().count("ac_to_inf") == 1) | ||
| 268 | || (part.get_synrestrs().count("vc_to_inf") == 1) | ||
| 269 | || (part.get_synrestrs().count("rs_to_inf") == 1) | ||
| 270 | || (part.get_synrestrs().count("oc_to_inf") == 1)) | ||
| 271 | { | ||
| 272 | utter << verbly::token{verbly::token::fillin_type::infinitive_phrase}; | ||
| 273 | |||
| 274 | continue; | ||
| 275 | } else if (part.get_synrestrs().count("oc_bare_inf") == 1) | ||
| 276 | { | ||
| 277 | verbly::token tkn{verbly::token::fillin_type::infinitive_phrase}; | ||
| 278 | tkn.set_extra(1); | ||
| 279 | |||
| 280 | utter << tkn; | ||
| 281 | |||
| 282 | continue; | ||
| 283 | } | ||
| 284 | |||
| 285 | auto selrestrs = fr.roles()[part.get_role()]; | ||
| 286 | auto query = database.nouns().limit(1).random().is_not_proper().full_hyponym_of(parse_selrestrs(selrestrs)); | ||
| 287 | verbly::noun n = query.run().front(); | ||
| 288 | if ((rand() % 2 == 0) && (part.get_synrestrs().count("definite") == 0)) | ||
| 289 | { | ||
| 290 | utter << verbly::token{"the"}; | ||
| 291 | } else { | ||
| 292 | if (n.starts_with_vowel_sound()) | ||
| 293 | { | ||
| 294 | utter << verbly::token{"an"}; | ||
| 295 | } else { | ||
| 296 | utter << verbly::token{"a"}; | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | if (part.get_synrestrs().count("plural") == 1) | ||
| 301 | { | ||
| 302 | utter << verbly::token{n, verbly::token::noun_inflection::plural}; | ||
| 303 | } else { | ||
| 304 | utter << verbly::token{n}; | ||
| 305 | } | ||
| 306 | |||
| 307 | if (part.get_synrestrs().count("acc_ing") == 1) | ||
| 308 | { | ||
| 309 | utter << verbly::token{verbly::token::fillin_type::participle_phrase}; | ||
| 310 | } | ||
| 311 | |||
| 312 | break; | ||
| 313 | } | ||
| 314 | |||
| 315 | case verbly::frame::part::type::verb: | ||
| 316 | { | ||
| 317 | utter << verbly::token{v, verbly::token::verb_inflection::ing_form}; | ||
| 318 | |||
| 319 | break; | ||
| 320 | } | ||
| 321 | |||
| 322 | case verbly::frame::part::type::literal_preposition: | ||
| 323 | { | ||
| 324 | utter << verbly::token{part.get_choices()[rand() % part.get_choices().size()]}; | ||
| 325 | |||
| 326 | break; | ||
| 327 | } | ||
| 328 | |||
| 329 | case verbly::frame::part::type::selection_preposition: | ||
| 330 | { | ||
| 331 | auto query = database.prepositions(); | ||
| 332 | for (auto preprestr : part.get_preprestrs()) | ||
| 333 | { | ||
| 334 | query.in_group(preprestr); | ||
| 335 | } | ||
| 336 | utter << verbly::token{query.random().limit(1).run().front()}; | ||
| 337 | |||
| 338 | break; | ||
| 339 | } | ||
| 340 | |||
| 341 | case verbly::frame::part::type::adjective: | ||
| 342 | { | ||
| 343 | utter << verbly::token{verbly::token::fillin_type::adjective_phrase}; | ||
| 344 | |||
| 345 | break; | ||
| 346 | } | ||
| 347 | |||
| 348 | case verbly::frame::part::type::adverb: | ||
| 349 | { | ||
| 350 | utter << verbly::token{verbly::token::fillin_type::adverb_phrase}; | ||
| 351 | |||
| 352 | break; | ||
| 353 | } | ||
| 354 | |||
| 355 | case verbly::frame::part::type::literal: | ||
| 356 | { | ||
| 357 | utter << verbly::token{part.get_literal()}; | ||
| 358 | |||
| 359 | break; | ||
| 360 | } | ||
| 361 | } | ||
| 362 | } | ||
| 363 | |||
| 364 | it = utter; | ||
| 365 | |||
| 366 | break; | ||
| 367 | } | ||
| 368 | |||
| 369 | break; | ||
| 370 | } | ||
| 371 | |||
| 372 | case verbly::token::fillin_type::adjective_phrase: | ||
| 45 | { | 373 | { | 
| 46 | verbly::verb v = database.verbs().random(true).limit(1).run().front(); | 374 | verbly::token phrase; | 
| 47 | /*verbly::utterance_token phrase = verbly::random(v.frames).make_utterance(); | 375 | |
| 48 | while (std::begin(phrase)->token_type() != verbly::type::verb) | 376 | if (rand() % 4 == 0) | 
| 49 | { | 377 | { | 
| 50 | phrase.erase(std::begin(phrase)); | 378 | phrase << verbly::token{verbly::token::fillin_type::adverb_phrase}; | 
| 51 | } | 379 | } | 
| 52 | 380 | ||
| 53 | *std::begin(phrase) = verbly::verb_token(v).conjugate(verbly::conjugation::present_participle); | 381 | if (rand() % 2 == 0) | 
| 54 | *it = phrase;*/ | 382 | { | 
| 55 | auto avt = std::make_unique<verbly::verb_token>(v); | 383 | phrase << verbly::token{verbly::token::fillin_type::participle_phrase}; | 
| 56 | avt->inflect(verbly::verb_token::inflection::ing_form); | 384 | } else { | 
| 57 | it = std::move(avt); | 385 | phrase << verbly::token{database.adjectives().random().limit(1).run().front()}; | 
| 58 | 386 | } | |
| 387 | |||
| 388 | it = phrase; | ||
| 389 | |||
| 59 | break; | 390 | break; | 
| 60 | } | 391 | } | 
| 61 | 392 | ||
| 62 | case verbly::fillin_type::adjective: | 393 | case verbly::token::fillin_type::adverb_phrase: | 
| 63 | { | 394 | { | 
| 64 | verbly::adjective adj = database.adjectives().random(true).limit(1).run().front(); | 395 | it = verbly::token{database.adverbs().random().limit(1).run().front()}; | 
| 65 | it = std::make_unique<verbly::string_token>(adj.base_form()); | ||
| 66 | 396 | ||
| 67 | break; | 397 | break; | 
| 68 | } | 398 | } | 
| 69 | 399 | ||
| 70 | case verbly::fillin_type::adverb: | 400 | case verbly::token::fillin_type::infinitive_phrase: | 
| 71 | { | 401 | { | 
| 72 | verbly::adverb adv = database.adverbs().random(true).limit(1).run().front(); | 402 | verbly::token utter; | 
| 73 | it = std::make_unique<verbly::string_token>(adv.base_form()); | 403 | if (it.get_extra() != 1) | 
| 404 | { | ||
| 405 | utter << verbly::token{"to"}; | ||
| 406 | } | ||
| 407 | |||
| 408 | utter << verbly::token{database.verbs().random().limit(1).run().front()}; | ||
| 74 | 409 | ||
| 75 | break; | 410 | break; | 
| 76 | } | 411 | } | 
| 77 | 412 | ||
| 78 | default: | 413 | default: | 
| 79 | { | 414 | { | 
| 80 | it = std::make_unique<verbly::string_token>("*the reality of the situation*"); | 415 | it = verbly::token{"*the reality of the situation*"}; | 
| 81 | 416 | ||
| 82 | break; | 417 | break; | 
| 83 | } | 418 | } | 
| @@ -105,30 +440,19 @@ int main(int argc, char** argv) | |||
| 105 | { | 440 | { | 
| 106 | std::cout << "Generating tweet" << std::endl; | 441 | std::cout << "Generating tweet" << std::endl; | 
| 107 | 442 | ||
| 108 | std::vector<verbly::utterance_token> forms; | ||
| 109 | forms.push_back({ | ||
| 110 | new verbly::string_token("the furries are"), | ||
| 111 | new verbly::fillin_token(verbly::fillin_type::participle_phrase) | ||
| 112 | }); | ||
| 113 | forms.push_back({ | ||
| 114 | new verbly::string_token("the furries are"), | ||
| 115 | new verbly::fillin_token(verbly::fillin_type::adjective) | ||
| 116 | }); | ||
| 117 | forms.push_back({ | ||
| 118 | new verbly::string_token("the furries are"), | ||
| 119 | new verbly::fillin_token(verbly::fillin_type::adverb), | ||
| 120 | new verbly::fillin_token(verbly::fillin_type::adjective) | ||
| 121 | }); | ||
| 122 | |||
| 123 | verbly::data database {"data.sqlite3"}; | 443 | verbly::data database {"data.sqlite3"}; | 
| 444 | |||
| 124 | fill_blanks yeah {database}; | 445 | fill_blanks yeah {database}; | 
| 125 | std::unique_ptr<verbly::token> action = std::make_unique<verbly::utterance_token>(forms[rand() % forms.size()]); | 446 | verbly::token action{ | 
| 126 | while (!action->complete()) | 447 | {"the furries are"}, | 
| 448 | {verbly::token::fillin_type::adjective_phrase} | ||
| 449 | }; | ||
| 450 | while (!action.is_complete()) | ||
| 127 | { | 451 | { | 
| 128 | yeah.visit(action); | 452 | yeah.visit(action); | 
| 129 | } | 453 | } | 
| 130 | 454 | ||
| 131 | std::string result = action->compile(); | 455 | std::string result = action.compile(); | 
| 132 | result.resize(140); | 456 | result.resize(140); | 
| 133 | 457 | ||
| 134 | std::string replyMsg; | 458 | std::string replyMsg; | 
| @@ -142,6 +466,6 @@ int main(int argc, char** argv) | |||
| 142 | } | 466 | } | 
| 143 | 467 | ||
| 144 | std::cout << "Waiting" << std::endl; | 468 | std::cout << "Waiting" << std::endl; | 
| 145 | sleep(60 * 60 * 3); | 469 | sleep(60 * 60 * 2); | 
| 146 | } | 470 | } | 
| 147 | } | 471 | } | 
| diff --git a/vendor/verbly b/vendor/verbly | |||
| Subproject dc210ee6eba3b1d173808bd858113f6abd90bff | Subproject eef5de613c75661e5d94baa086f6f2ddc26c7ed | ||
