~divmod-dev/divmod.org/compressed-resources-2747

« back to all changes in this revision

Viewing changes to Imaginary/imagination/text/english.py

  • Committer: exarkun
  • Date: 2006-02-26 02:37:39 UTC
  • Revision ID: svn-v4:866e43f7-fbfc-0310-8f2a-ec88d1da2979:trunk:4991
Merge move-pottery-to-trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: imagination.test -*-
 
2
 
 
3
import pprint
 
4
 
 
5
from zope import interface
 
6
from zope.interface import Interface, implements
 
7
from twisted.python.reflect import accumulateMethods
 
8
 
 
9
from imagination import iimagination
 
10
from imagination.facets import Facet, IReprable, registerAdapter
 
11
from imagination.errors import Nonsense, NoSuchObject, RealityException, ActionFailed, ActionRefused, Refusal, LostThing
 
12
 
 
13
 
 
14
# English-Specific Interfaces
 
15
 
 
16
class INoun(Interface):
 
17
    """This interface is intended to specify a specifically English noun.
 
18
    """
 
19
 
 
20
class INoun(Interface):
 
21
    """An abstract concept that can be expressed in some language.
 
22
 
 
23
    @type name: C{str}
 
24
    @ivar name: A simple string describing this noun.
 
25
    """
 
26
 
 
27
    def getLanguage():
 
28
        """Return a brief string identifier of the language this concept is in.
 
29
        """
 
30
 
 
31
    def expressTo(observer):
 
32
        """
 
33
        Return a string or unicode object that expresses this concept in some
 
34
        language.
 
35
        """
 
36
 
 
37
    def describe(component, text, priority=1):
 
38
        """Describe a facet of this object with some text in my native language.
 
39
        """
 
40
 
 
41
    def explainTo(observer):
 
42
        """Return a long description of this object.
 
43
        """
 
44
 
 
45
    def knownAs(name, observer):
 
46
        """Determine whether the given name refers to this object.
 
47
        """
 
48
 
 
49
    def changeName(newname):
 
50
        """Change the name which refers to this object.
 
51
        """
 
52
 
 
53
 
 
54
class IThinker(Interface):
 
55
    def recognizes(name):
 
56
        """
 
57
        XXX Whether or not this thinker recognizes this name. What name??? I dunno!
 
58
        """
 
59
 
 
60
    def parse(text):
 
61
        """
 
62
        parse and execute a command from the thinker.
 
63
        """
 
64
 
 
65
 
 
66
# Formatting
 
67
 
 
68
class Noun(Facet):
 
69
    implements(INoun, IReprable)
 
70
 
 
71
    def __init__(self, original, name = '', description = None, unique = False):
 
72
        Facet.__init__(self, original)
 
73
        self.name = name
 
74
        self.description = description
 
75
        self.unique = unique
 
76
        original[IReprable] = name
 
77
 
 
78
    def describe(self, component, text, priority=1):
 
79
        if self.description is None:
 
80
            self.description = []
 
81
        for idx in xrange(len(self.description)):
 
82
            p, c, t = self.description[idx]
 
83
            if c == component:
 
84
                if text is None:
 
85
                    del self.description[idx]
 
86
                else:
 
87
                    self.description[idx] = priority, component, text
 
88
                break
 
89
        else:
 
90
            self.description.append((priority, component, text))
 
91
        self.description.sort()
 
92
 
 
93
    def explainTo(self, observer, component=None):
 
94
        if self.description is None:
 
95
            return ''
 
96
        return express(self.description, observer)
 
97
        #return express([t for p, c, t in self.description if (component is None)
 
98
        #                       or (c == component)], observer)
 
99
 
 
100
    def getLanguage(self):
 
101
        return 'english'
 
102
 
 
103
    def expressTo(self, observer):
 
104
        return self.nounPhrase(observer)
 
105
 
 
106
    def hasName(self, name):
 
107
        return name and self.name == name or [
 
108
            None for n in self.name.lower().split()
 
109
            if n.startswith(name.lower())]
 
110
 
 
111
    def knownAs(self, name, observer):
 
112
        # print "HELLO????"
 
113
        if IThinker(observer).recognizes(self, name):
 
114
            # print "using recognizes"
 
115
            return True
 
116
        else:
 
117
            # print "using hasName"
 
118
            return self.hasName(name)
 
119
 
 
120
    def changeName(self, newName):
 
121
        self.name = newName
 
122
        # TODO: broadcast a name change event?
 
123
 
 
124
    def article(self, observer):
 
125
        if self.unique:
 
126
            return self.definiteArticle(observer)
 
127
        else:
 
128
            return self.indefiniteArticle(observer)
 
129
 
 
130
    def indefiniteArticle(self, observer):
 
131
        if self.name[0].lower() in 'aeiou':
 
132
            return 'an '
 
133
        return 'a '
 
134
 
 
135
    def definiteArticle(self, observer):
 
136
        return 'the '
 
137
 
 
138
    def shortName(self, observer):
 
139
        return self.name
 
140
 
 
141
    def nounPhrase(self, observer):
 
142
        return (self.article(observer) + self.shortName(observer))
 
143
 
 
144
    def containerPreposition(self, content, observer):
 
145
        """When this object acts as a container, this is the preposition that
 
146
        objects will be in relation to it."""
 
147
        return "on"
 
148
 
 
149
    def containedPhrase(self, observer, containerNoun):
 
150
        # A self is container.preposition the container
 
151
        return "%s is %s %s." % (self.nounPhrase(observer),
 
152
                                 containerNoun.containerPreposition(self, observer),
 
153
                                 containerNoun.nounPhrase(observer))
 
154
 
 
155
    def presentPhrase(self, observer):
 
156
        # ugh: this is terrible.
 
157
        if (isinstance(self.original, things.Movable) and
 
158
            isinstance(observer, things.Movable) and
 
159
            self.original.location == observer.location):
 
160
            return "%s is here." % self.nounPhrase(observer)
 
161
        else:
 
162
            return INoun(self).containedPhrase(self)
 
163
 
 
164
    def heShe(self, observer):
 
165
        if self.original.gender == things.MALE:
 
166
            return 'he'
 
167
        elif self.original.gender == things.FEMALE:
 
168
            return 'she'
 
169
        else:
 
170
            return 'it'
 
171
 
 
172
    def himHer(self, observer):
 
173
        if self.original.gender == things.MALE:
 
174
            return 'him'
 
175
        elif self.original.gender == things.FEMALE:
 
176
            return 'her'
 
177
        else:
 
178
            return 'its'
 
179
 
 
180
    def hisHer(self, observer):
 
181
        if self.original.gender == things.MALE:
 
182
            return 'his'
 
183
        elif self.original.gender == things.FEMALE:
 
184
            return 'her'
 
185
        else:
 
186
            return 'its'
 
187
 
 
188
class Describability(Facet):
 
189
    implements(INoun)
 
190
 
 
191
    description = None
 
192
 
 
193
    def describe(*a): raise NotImplementedError
 
194
 
 
195
    def explainTo(self, observer):
 
196
        if self.description is not None:
 
197
            return self.description
 
198
        noun = INoun(self, default=None)
 
199
        if noun is not None:
 
200
            return noun.nounPhrase(observer)
 
201
        return "an nondescript object"
 
202
 
 
203
 
 
204
 
 
205
class ProperNoun(Noun):
 
206
    def indefiniteArticle(self, observer):
 
207
        return ''
 
208
 
 
209
    def definiteArticle(self, observer):
 
210
        return ''
 
211
 
 
212
 
 
213
class CollectiveNoun(Noun):
 
214
    implements(INoun)
 
215
    def indefiniteArticle(self, observer):
 
216
        return ''
 
217
 
 
218
 
 
219
# Errors
 
220
 
 
221
import random
 
222
 
 
223
class EException(Facet):
 
224
    implements(INoun)
 
225
    def expressTo(self, observer):
 
226
        return express(self.original.args, observer)
 
227
 
 
228
class ENonsense(Facet):
 
229
    implements(INoun)
 
230
    errors=["You don't think that you want to waste your time with that.",
 
231
            "There are probably better things to do with your life.",
 
232
            "You are nothing if not creative, but that creativity could be better applied to developing a more productive solution.",
 
233
            "Perhaps that's not such a good idea after all.",
 
234
            "Surely, in this world of limitless possibility, you could think of something better to do.",
 
235
            "An interesting idea...",
 
236
            "A valiant attempt.",
 
237
            "What a concept!",
 
238
            "You can't be serious."]
 
239
 
 
240
    def expressTo(self, observer):
 
241
        return random.choice(self.errors)
 
242
 
 
243
class ENoSuchObject(Facet):
 
244
    implements(INoun)
 
245
    def expressTo(self, observer):
 
246
        return express(("You don't see a %r here." % self.original.name), observer)
 
247
 
 
248
registerAdapter(ENonsense, Nonsense, INoun)
 
249
registerAdapter(ENoSuchObject, NoSuchObject, INoun)
 
250
for rex in RealityException, ActionFailed, ActionRefused, Refusal, LostThing:
 
251
    registerAdapter(EException, rex, INoun)
 
252
 
 
253
# Parsing
 
254
 
 
255
class Subparser:
 
256
    """A subparser is a class that implements some number of parse_<word>
 
257
    methods.  These methods will be called by the parser engine for a player.
 
258
    For example, when a player types '<word> foo', the subparser will be
 
259
    invoked (if it implements such a method) with
 
260
    subparser.parse_<word>(player, <text>), where text is the remainder of the
 
261
    sentence after '<word> '.
 
262
 
 
263
    the 'simpleToolParsers' attribute is a dictionary of {"verb_name":
 
264
    ToolActionClass}.
 
265
 
 
266
    """
 
267
    simpleToolParsers = {}
 
268
    simpleTargetParsers = {}
 
269
    def getParserBits(self):
 
270
        dict = {}
 
271
        accumulateMethods(self, dict, "parse_")
 
272
        for parseWord, actionClass in self.simpleToolParsers.items():
 
273
            dict[parseWord] = SimpleToolParser(actionClass).parse
 
274
        for parseWord, actionClass in self.simpleTargetParsers.items():
 
275
            dict[parseWord] = SimpleTargetParser(actionClass).parse
 
276
        items = dict.items()
 
277
        return items
 
278
 
 
279
class SimpleTargetParser:
 
280
 
 
281
    def __init__(self, actionClass):
 
282
        self.actionClass = actionClass
 
283
 
 
284
    def parse(self, player, text):
 
285
        actor = self.actionClass.getInterface("Actor")(player)
 
286
        if actor is None:
 
287
            return []
 
288
        return [self.actionClass(actor, text)]
 
289
 
 
290
 
 
291
class SimpleToolParser:
 
292
 
 
293
    def __init__(self, actionClass):
 
294
        self.actionClass = actionClass
 
295
 
 
296
    def parse(self, player, text):
 
297
        return simpleToolAction(player, self.actionClass, text)
 
298
 
 
299
 
 
300
class VerbParserEngine:
 
301
 
 
302
    def __init__(self):
 
303
        self.subparsers = {}
 
304
        self.spitems = self.subparsers.items()
 
305
 
 
306
    def registerSubparser(self, subparser):
 
307
        bits = subparser.getParserBits()
 
308
        for name, method in bits:
 
309
            name = name.lower().replace("_", " ")
 
310
            self.subparsers.setdefault(name, []).append(method)
 
311
        self.spitems = self.subparsers.items()
 
312
 
 
313
    def parseToActions(self, player, text):
 
314
        """Dispatch to all appropriate subparsers.
 
315
        """
 
316
        # note, this is slightly buggy
 
317
        # look_up shouldn't be different than look__up but the work necessary
 
318
        # to equalize them just doesn't seem worth it.
 
319
        matchtext = text.lower() #.replace(' ', '_')
 
320
        potentialActions = []
 
321
        for prefx, methods in self.spitems:
 
322
            if matchtext.startswith(prefx):
 
323
                subtext = text[len(prefx):].strip()
 
324
                for method in methods:
 
325
                    potact = method(player, subtext)
 
326
                    if potact:
 
327
                        potentialActions.extend(potact)
 
328
        return potentialActions
 
329
try:
 
330
    theParserEngine
 
331
except NameError:
 
332
    theParserEngine = VerbParserEngine()
 
333
    registerSubparser = theParserEngine.registerSubparser
 
334
    parseToActions = theParserEngine.parseToActions
 
335
 
 
336
class Parsing(Facet):
 
337
    implements(IThinker)
 
338
    potentialActions = None
 
339
    action = None
 
340
 
 
341
    def __init__(self, *args, **kwargs):
 
342
        Facet.__init__(self, *args, **kwargs)
 
343
        self.resolved = []
 
344
        self.potentialThings = []
 
345
 
 
346
    def recognizes(self, noun, name):
 
347
        return False
 
348
 
 
349
 
 
350
    def parse(self, text):
 
351
        """
 
352
        Parse a command.
 
353
        My return values are only for debugging purposes.
 
354
        """
 
355
        potentialActions = self.parseToActions(self.original, text)
 
356
 
 
357
        if not potentialActions:
 
358
            raise Nonsense("That doesn't make sense!")
 
359
 
 
360
        #argh, we're relying on getAmbiguities' side-effects.
 
361
        for act in potentialActions:
 
362
            act.getAmbiguities()
 
363
 
 
364
        potentialRealActions = [act for act in potentialActions if not act.isPlaceholder()]
 
365
 
 
366
        if (len(potentialRealActions) and
 
367
            (len(potentialRealActions) < len(potentialActions))):
 
368
            potentialActions = potentialRealActions
 
369
 
 
370
        if len(potentialActions) == 1:
 
371
            # Command was not ambiguous Action-wise
 
372
            return self._runAction(potentialActions[0])
 
373
        else:
 
374
            # More than one Action!
 
375
            return self.askForAction(potentialActions
 
376
                ).addCallback(self._runAction
 
377
                )
 
378
 
 
379
    def _runAction(self, action):
 
380
        # print '[][][][][][][][][][][][][][][][]'
 
381
        potentialThings = action.getAmbiguities()
 
382
        # print '][][][][][][][][][][][][][][][]['
 
383
        # print 'Lala', potentialThings
 
384
 
 
385
        if potentialThings:
 
386
            # It _was_ ambiguous wrt the Things involved
 
387
            return self.askForThing(potentialThings, action)
 
388
        else:
 
389
            #No ambiguities! Woohoo
 
390
            return action.performAction()
 
391
 
 
392
    def parseToActions(self, original, text):
 
393
        return parseToActions(original, text)
 
394
 
 
395
    def askForAction(self, potentialActions):
 
396
        return iimagination.IUI(self.original).presentMenu(potentialActions
 
397
            ).addCallbacks(potentialActions.__getitem__, lambda x: None)
 
398
 
 
399
    def askForThing(self, potentialThings, action):
 
400
        pt = potentialThings
 
401
        def getThing(result):
 
402
            thing = pt[0][1][result]
 
403
            return self.doneResolvingThing(thing, potentialThings, action)
 
404
        return iimagination.IUI(self.original).presentMenu(pt[0][1], pt[0][0]
 
405
            ).addCallbacks(getThing, lambda x: None)
 
406
 
 
407
    def doneResolvingThing(self, thing, potentialThings, action):
 
408
        self.resolved.append((potentialThings[0][0], thing))
 
409
        del potentialThings[0]
 
410
        if potentialThings:
 
411
            return self.askForThing(potentialThings, action)
 
412
        else:
 
413
            #RESOLVED ALL THINGS!
 
414
            for iface, thing in self.resolved:
 
415
                action.setImplementor(iface, thing)
 
416
            return action.performAction()
 
417
 
 
418
 
 
419
### Utility Functions
 
420
 
 
421
def rsplit1(s, sep):
 
422
    """Split on a delimiter found backwards through a string.
 
423
 
 
424
    Returns a 2-tuple of (before-last-sep, after-last-sep).  If sep is not
 
425
    found, it returns (s,'').
 
426
    """
 
427
    n = s.rfind(sep)
 
428
    if n == -1:
 
429
        return (s, '')
 
430
    return (s[:n].strip(), s[n+len(sep):].strip())
 
431
 
 
432
def _simpleToolact(player, toolActionClass, targetName, toolName):
 
433
    return [toolActionClass(player, targetName, toolName)]
 
434
 
 
435
def simpleToolAction(player, toolActionClass, text):
 
436
    """
 
437
    # gun is the tool, bob is the target
 
438
    1) 'shoot bob with gun'
 
439
    # still the same, though 'at' reverses the order
 
440
    2) 'shoot gun at bob'
 
441
    # the gun is the tool; north is the target.  Do I need to handle this case?
 
442
    3) 'point gun north'
 
443
 
 
444
    ### The action is going to have to deal properly with implicit (None)
 
445
    ### targets and tools, and locate them itself, either in the constructor or
 
446
    ### in processAction. (Some actions might not even need both tool and
 
447
    ### target)
 
448
 
 
449
    # implicit tool (yuck)
 
450
    4) 'shoot bob'
 
451
    # implicit _target_ (extra yuck)
 
452
    5) 'shoot gun'
 
453
    """
 
454
 
 
455
    actor = toolActionClass.getInterface("Actor")(player)
 
456
    if actor is None:
 
457
        return []
 
458
    if text.count(' with '):            # case #1
 
459
        bobName, gunName = rsplit1(text, ' with ')
 
460
        return _simpleToolact(player, toolActionClass, bobName, gunName)
 
461
    else:                               # case #2
 
462
        # XXX What is the use-case for ' and '?
 
463
        for splitter in (' at ', ' to ', ' from ', ' and '):
 
464
            if splitter in text:
 
465
                gunName, bobName = rsplit1(text, splitter)
 
466
                return _simpleToolact(player, toolActionClass, bobName, gunName)
 
467
    # you rolled a 9 (case #4 & 5)
 
468
    lr = []
 
469
    if toolActionClass.allowImplicitTarget:
 
470
        lr.extend(_simpleToolact(player, toolActionClass, None, text))
 
471
 
 
472
    lr.extend(_simpleToolact(player, toolActionClass, text, None))
 
473
    return lr
 
474
 
 
475
## Some utilities.
 
476
 
 
477
class _ExpressSeq(Facet):
 
478
    def expressTo(self, observer):
 
479
        return ''.join([INoun(x).expressTo(observer) for x in self.original])
 
480
 
 
481
class _ExpressNothing(Facet):
 
482
    def expressTo(self, observer):
 
483
        return ''
 
484
 
 
485
class _ExpressYourself(Facet):
 
486
    def expressTo(self, observer):
 
487
        return self.original
 
488
 
 
489
registerAdapter(_ExpressSeq, tuple, INoun)
 
490
registerAdapter(_ExpressSeq, list, INoun)
 
491
registerAdapter(_ExpressNothing, type(None), INoun)
 
492
registerAdapter(_ExpressYourself, str, INoun)
 
493
 
 
494
def express(tup, obs, iface=INoun):
 
495
    return iface(tup).expressTo(obs)