From f755f052d3daf7a536f0b1bccccd041b774d0769 Mon Sep 17 00:00:00 2001 From: Cal Date: Sun, 21 Apr 2024 15:18:09 -0600 Subject: [PATCH] add mapping support, pronoun conjugation to actor stance callables --- docs/source/Components/FuncParser.md | 10 +- evennia/utils/funcparser.py | 112 +++++++++++++++++--- evennia/utils/tests/test_funcparser.py | 10 ++ evennia/utils/verb_conjugation/conjugate.py | 11 +- 4 files changed, 119 insertions(+), 24 deletions(-) diff --git a/docs/source/Components/FuncParser.md b/docs/source/Components/FuncParser.md index 05aee2d509..458bbeb3dc 100644 --- a/docs/source/Components/FuncParser.md +++ b/docs/source/Components/FuncParser.md @@ -337,13 +337,17 @@ Here the `caller` is the one sending the message and `receiver` the one to see i result of `you_obj.get_display_name(looker=receiver)`. This allows for a single string to echo differently depending on who sees it, and also to reference other people in the same way. - `$You([key])` - same as `$you` but always capitalized. -- `$conj(verb)` ([code](evennia.utils.funcparser.funcparser_callable_conjugate)) - conjugates a verb - between 2nd person presens to 3rd person presence depending on who +- `$conj(verb [,key])` ([code](evennia.utils.funcparser.funcparser_callable_conjugate)) - conjugates a verb + between 2nd person presence to 3rd person presence depending on who sees the string. For example `"$You() $conj(smiles)".` will show as "You smile." and "Tom smiles." depending on who sees it. This makes use of the tools in [evennia.utils.verb_conjugation](evennia.utils.verb_conjugation) to do this, and only works for English verbs. -- `$pron(pronoun [,options])` ([code](evennia.utils.funcparser.funcparser_callable_pronoun)) - Dynamically +- `$pron(pronoun [,options] [,key])` ([code](evennia.utils.funcparser.funcparser_callable_pronoun)) - Dynamically map pronouns (like his, herself, you, its etc) between 1st/2nd person to 3rd person. +- `$pconj(verb, [,key])` ([code](evennia.utils.funcparser.funcparser_callable_conjugate_for_pronouns)) - conjugates + a verb between 2nd and 3rd person, like `$conj`, but for pronouns instead of nouns to account for plural + gendering. For example `"$Pron(you) $pconj(smiles)"` will show to others as "He smiles" for a gender of "male", or + "They smile" for a gender of "plural". ### `evennia.prototypes.protfuncs` diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index 2c434badcf..bca394530f 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -1320,15 +1320,18 @@ def funcparser_callable_your_capitalize( ) -def funcparser_callable_conjugate(*args, caller=None, receiver=None, **kwargs): +def funcparser_callable_conjugate(*args, caller=None, receiver=None, mapping=None, **kwargs): """ - Usage: $conj(word, [options]) + Usage: $conj(word, [key]) Conjugate a verb according to if it should be 2nd or third person. Keyword Args: caller (Object): The object who represents 'you' in the string. receiver (Object): The recipient of the string. + mapping (dict, optional): This is a mapping `{key:Object, ...}` and is + used to find which object the optional `key` argument refers to. If not given, + the `caller` kwarg is used. Returns: str: The parsed string. @@ -1337,13 +1340,10 @@ def funcparser_callable_conjugate(*args, caller=None, receiver=None, **kwargs): ParsingError: If `you` and `recipient` were not both supplied. Notes: - Note that the verb will not be capitalized. It also - assumes that the active party (You) is the one performing the verb. - This automatic conjugation will fail if the active part is another person - than 'you'. The caller/receiver must be passed to the parser directly. - + Note that the verb will not be capitalized. + Examples: - This is often used in combination with the $you/You( callables. + This is often used in combination with the $you/You callables. - `With a grin, $you() $conj(jump)` @@ -1356,14 +1356,76 @@ def funcparser_callable_conjugate(*args, caller=None, receiver=None, **kwargs): if not (caller and receiver): raise ParsingError("No caller/receiver supplied to $conj callable") - second_person_str, third_person_str = verb_actor_stance_components(args[0]) - return second_person_str if caller == receiver else third_person_str + verb, *options = args + obj = caller + if mapping and options: + # get the correct referenced object from the mapping, or fall back to caller + obj = mapping.get(options[0], caller) + + second_person_str, third_person_str = verb_actor_stance_components(verb) + return second_person_str if obj == receiver else third_person_str -def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=False, **kwargs): +def funcparser_callable_conjugate_for_pronouns(*args, caller=None, receiver=None, mapping=None, **kwargs): + """ + Usage: $pconj(word, [key]) + + Conjugate a verb according to if it should be 2nd or third person, respecting the + singular/plural gendering for third person. + + Keyword Args: + caller (Object): The object who represents 'you' in the string. + receiver (Object): The recipient of the string. + mapping (dict, optional): This is a mapping `{key:Object, ...}` and is + used to find which object the optional `key` argument refers to. If not given, + the `caller` kwarg is used. + + Returns: + str: The parsed string. + + Raises: + ParsingError: If `you` and `recipient` were not both supplied. + + Notes: + Note that the verb will not be capitalized. + + Examples: + This is often used in combination with the $pron/Pron callables. + + - `With a grin, $pron(you) $pconj(jump)` + + You will see "With a grin, you jump." + With your gender as "male", others will see "With a grin, he jumps." + With your gender as "plural", others will see "With a grin, they jump." + + """ + if not args: + return "" + if not (caller and receiver): + raise ParsingError("No caller/receiver supplied to $conj callable") + + verb, *options = args + obj = caller + if mapping and options: + # get the correct referenced object from the mapping, or fall back to caller + obj = mapping.get(options[0], caller) + + # identify whether the 3rd person form should be singular or plural + plural = False + if hasattr(obj, "gender"): + if callable(obj.gender): + plural = (obj.gender() == "plural") + else: + plural = (obj.gender == "plural") + + second_person_str, third_person_str = verb_actor_stance_components(verb, plural=plural) + return second_person_str if obj == receiver else third_person_str + + +def funcparser_callable_pronoun(*args, caller=None, receiver=None, mapping=None, capitalize=False, **kwargs): """ - Usage: $pron(word, [options]) + Usage: $pron(word, [options], [key]) Adjust pronouns to the expected form. Pronouns are words you use instead of a proper name, such as 'him', 'herself', 'theirs' etc. These look different @@ -1424,6 +1486,9 @@ def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=Fa - `1st person`/`1st`/`1` - `2nd person`/`2nd`/`2` - `3rd person`/`3rd`/`3` + key (str, optional): If a mapping is provided, a string defining which object to + reference when finding the correct pronoun. If not provided, it defaults + to `caller` Keyword Args: @@ -1435,6 +1500,9 @@ def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=Fa receiver (Object): The recipient of the string. This being the same as `caller` or not helps determine 2nd vs 3rd-person forms. This is provided automatically by the funcparser. + mapping (dict, optional): This is a mapping `{key:Object, ...}` and is + used to find which object the optional `key` argument refers to. If not given, + the `caller` kwarg is used. capitalize (bool): The input retains its capitalization. If this is set the output is always capitalized. @@ -1457,8 +1525,17 @@ def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=Fa """ if not args: return "" + # by default, we use the caller as the object being referred to + obj = caller pronoun, *options = args + if options and mapping: + # check if the last argument is a valid mapping key + if options[-1] in mapping: + # get the object and remove the key from options + obj = mapping[options[-1]] + options = options[:-1] + # options is either multiple args or a space-separated string if len(options) == 1: options = options[0] @@ -1468,11 +1545,11 @@ def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=Fa default_gender = "neutral" default_viewpoint = "2nd person" - if hasattr(caller, "gender"): - if callable(caller.gender): - default_gender = caller.gender() + if hasattr(obj, "gender"): + if callable(obj.gender): + default_gender = obj.gender() else: - default_gender = caller.gender + default_gender = obj.gender if "viewpoint" in kwargs: # passed into FuncParser initialization @@ -1490,7 +1567,7 @@ def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=Fa pronoun_1st_or_2nd_person = pronoun_1st_or_2nd_person.capitalize() pronoun_3rd_person = pronoun_3rd_person.capitalize() - return pronoun_1st_or_2nd_person if caller == receiver else pronoun_3rd_person + return pronoun_1st_or_2nd_person if obj == receiver else pronoun_3rd_person def funcparser_callable_pronoun_capitalize( @@ -1557,6 +1634,7 @@ ACTOR_STANCE_CALLABLES = { "obj": funcparser_callable_you, "Obj": funcparser_callable_you_capitalize, "conj": funcparser_callable_conjugate, + "pconj": funcparser_callable_conjugate_for_pronouns, "pron": funcparser_callable_pronoun, "Pron": funcparser_callable_pronoun_capitalize, **FUNCPARSER_CALLABLES, diff --git a/evennia/utils/tests/test_funcparser.py b/evennia/utils/tests/test_funcparser.py index 7b13926ede..1b2fd41da1 100644 --- a/evennia/utils/tests/test_funcparser.py +++ b/evennia/utils/tests/test_funcparser.py @@ -435,6 +435,7 @@ class TestDefaultCallables(TestCase): ("$You() $conj(smile) at him.", "You smile at him.", "Char1 smiles at him."), ("$You() $conj(smile) at $You(char1).", "You smile at You.", "Char1 smiles at Char1."), ("$You() $conj(smile) at $You(char2).", "You smile at Char2.", "Char1 smiles at You."), + ("$You() $conj(smile) while $You(char2) $conj(waves, char2).", "You smile while Char2 waves.", "Char1 smiles while You wave."), ( "$You(char2) $conj(smile) at $you(char1).", "Char2 smile at you.", @@ -512,6 +513,15 @@ class TestDefaultCallables(TestCase): ret = self.parser.parse(string, caller=self.obj1, raise_errors=True) self.assertEqual(expected, ret) + def test_pronoun_mapping(self): + self.obj1.gender = "female" + self.obj2.gender = "male" + + string = "Char1 raises $pron(your, char1) fist as Char2 raises $pron(yours, char2)" + expected = "Char1 raises her fist as Char2 raises his" + ret = self.parser.parse(string, caller=self.obj1, mapping={'char1': self.obj1, 'char2': self.obj2}, raise_errors=True) + self.assertEqual(expected, ret) + def test_pronoun_viewpoint(self): string = "Char1 smiles at $pron(I)" diff --git a/evennia/utils/verb_conjugation/conjugate.py b/evennia/utils/verb_conjugation/conjugate.py index 20043b4e09..21ec134ced 100644 --- a/evennia/utils/verb_conjugation/conjugate.py +++ b/evennia/utils/verb_conjugation/conjugate.py @@ -365,25 +365,28 @@ def verb_is_past_participle(verb): return tense == "past participle" -def verb_actor_stance_components(verb): +def verb_actor_stance_components(verb, plural=False): """ Figure out actor stance components of a verb. Args: verb (str): The verb to analyze + plural (bool): Whether to force 3rd person to plural form Returns: tuple: The 2nd person (you) and 3rd person forms of the verb, in the same tense as the ingoing verb. - """ tense = verb_tense(verb) + them = "*" if plural else "3" + them_suff = "" if plural else "s" + if "participle" in tense or "plural" in tense: return (verb, verb) if tense == "infinitive" or "present" in tense: you_str = verb_present(verb, person="2") or verb - them_str = verb_present(verb, person="3") or verb + "s" + them_str = verb_present(verb, person=them) or verb + them_suff else: you_str = verb_past(verb, person="2") or verb - them_str = verb_past(verb, person="3") or verb + "s" + them_str = verb_past(verb, person=them) or verb + them_suff return (you_str, them_str)