-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathibis_types.py
691 lines (534 loc) · 22.5 KB
/
ibis_types.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
# -*- encoding: utf-8 -*-
#
# ibis_types.py
# Copyright (C) 2009, Peter Ljunglöf. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# and the GNU Lesser General Public License along with this program.
# If not, see <http://www.gnu.org/licenses/>.
from trindikit import Type, is_sequence, Move, SingletonMove
from types import FunctionType, MethodType
import functools
import time
######################################################################
# IBIS semantic types
######################################################################
# Atomic types: individuals, predicates, sorts
class Atomic(Type):
"""Abstract base class for semantic classes taking a string argument.
Do not create instances of this class, use instead the subclasses:
- Ind
- Pred0
- Pred1
- Sort
"""
contentclass = str
def __init__(self, atom):
assert isinstance(atom, (str, int, bytes))
assert atom not in ("", "yes", "no")
try:
atom = int(atom)
except ValueError:
if not isinstance(atom, bytes):
assert atom[0].isalpha()
# assert all(ch.isalnum() or ch in "_-+: \n" for ch in atom)
atom = atom.replace("'", "*")
assert all(ch.isalnum() or ch in "_-+: \n.?()/!öüä,*" for ch in atom)
self.content = atom
def __str__(self):
return "%s" % self.content.__str__()
class Ind(Atomic):
"""Individuals."""
def _typecheck(self, context):
assert self.content in context.inds
class Pred0(Atomic):
"""0-place predicates."""
def _typecheck(self, context):
assert self.content in context.preds0
class Pred1(Atomic):
"""1-place predicates."""
def apply(self, ind):
"""Apply the predicate to an individual, returning a proposition."""
assert isinstance(ind, Ind), "%s must be an individual" % ind
return Prop(self, ind)
def _typecheck(self, context):
assert self.content in context.preds1
def __init__(self, *args, **kwargs):
# print("PRED1",*args, **kwargs) #HIER
# self._typecheck(args[0])
super(Pred1, self).__init__(args[0], **{})
if len(args) > 1:
self.arg2 = args[1]
if kwargs.get("createdfrom"):
self.createdfrom = kwargs["createdfrom"]
def __str__(self):
return self.content.__str__()
def __repr__(self):
if hasattr(self, "arg2") and self.arg2:
return "Pred1(" + self.content + ", " + self.arg2 if isinstance(self.arg2, str) else self.arg2.content + ")"
else:
return "Pred1("+self.content+")"
class Pred2(Atomic):
"""2-place predicates."""
def apply(self, ind):
"""Apply the predicate to an individual, returning a 1st order predicate."""
assert isinstance(ind, Ind), "%s must be an individual" % ind
return Pred1(self, ind)
def _typecheck(self, context):
assert self.content in context.preds2
def __init__(self, pred, domaincontext, *args, **kwargs):
# print("PRED2", *args, **kwargs) #HIER
# # self._typecheck(pred)
# print(pred, type(pred))
assert isinstance(pred, (str, Pred2))
self.arg1 = domaincontext.preds2.get(pred, "") #bspw ['semester', 'WhenSemester'] -> means: you need to get to know semester, such that it becomes the 1-order-predicate "WhenSemester"
if isinstance(pred, str) and len(pred) > 0:
if pred.startswith('?x.y.') and pred.endswith('(y)(x)'):
self.appliedContent = pred[3:-3]
self.content = self.appliedContent[2:-3]
elif pred.startswith('?x.') and pred.endswith('(x)'):
self.appliedContent = pred
self.content = self.appliedContent[3:-3]
else:
self.content = pred
self.appliedContent = pred
else:
self.content = pred.content #noo clue warum die Pred1 und Pred2 immer 2 mal gerunnt werden, einmal mit string und einmal mit PredX als argument >.<
self.appliedContent = pred.content
def __str__(self):
if hasattr(self, "arg1"):
cnt = ""
for candidate in self.arg1:
if len(candidate) > 1:
cnt += str(candidate[0])
return self.content+"("+cnt+")" if len(cnt) > 0 else self.content
return self.content
def __repr__(self):
return "Pred2("+self.content+", "+str(self.arg1)+")"
class Sort(Pred1):
"""Sort."""
def _typecheck(self, context):
assert self.content in context.sorts
# Sentences: answers, questions
##################################################################################################################
# Sentence as base
##################################################################################################################
class Sentence(Type):
"""Superclass for answers and questions."""
def __new__(cls, sent, *args, **kw):
if cls is Sentence:
assert isinstance(sent, str)
assert not args and not kw
if sent.startswith('?'):
return Question(sent)
else:
return Ans(sent)
else:
return Type.__new__(cls, sent, *args, **kw)
def __getnewargs__(self): #https://stackoverflow.com/questions/37753425/cannot-unpickle-pickled-object sonst kann man nicht picklen/unpicklen
return (self.content, )
######################################################################
# Answer base class + its derivatives (propositions, short answers, y/n-answers)
######################################################################
class Ans(Sentence):
"""Abstract base class for all kinds of answers.
Currently there are the following answer classes:
- Prop(pred, [ind], [yes]), where pred is a Pred0 or Pred1,
ind is an Ind and yes is a bool.
- ShortAns(ind, [yes]), where ind is an Ind and yes is a bool.
- YesNo(yes), where yes is a bool.
To create an answer, use any of the constructors above,
or call the abstract constructor with a string, Ans("..."):
- Ans("pred(ind)"), Ans("pred()") -> Prop("...")
- Ans("ind") -> ShortAns("...")
- Ans("yes"), Ans("no") -> YesNo("...")
"""
def __new__(cls, ans, *args, **kw):
if cls is Ans:
assert isinstance(ans, str)
assert not args and not kw
if ans in ('yes', 'no'):
return YesNo(ans)
elif '(' not in ans and ')' not in ans:
return ShortAns(ans)
elif '(' in ans and ans.endswith(')'):
return Prop(ans)
else:
raise SyntaxError("Could not parse answer: %s" % ans)
else:
return Sentence.__new__(cls, ans, *args, **kw)
class Knowledge(Ans):
def __init__(self, pred, ind=None, yes=True, expires=None):
assert (isinstance(pred, (Pred0, Pred1, Pred2, str)))
self.content = pred, ind, yes
self.pred = pred
self.ind = ind
self.yes = yes
self.expires = expires
def __hash__(self):
return hash((type(self), self.content[0], str(self.content[1]), self.content[2]))
def __str__(self):
return "Knowledge(Pred1("+str(self.content[0])+"), <not shown>)"+(" - expires in "+str(self.expires)+" secs" if self.expires else "")
def __repr__(self):
return "Knowledge(Pred1("+str(self.content[0])+"), <not shown>)"+(" - expires in "+str(self.expires)+" secs" if self.expires else "")
class Prop(Ans):
"""Proposition."""
def __init__(self, pred, ind=None, yes=True, expires=None):
assert (isinstance(pred, (Pred0, str)) and ind is None or
isinstance(pred, Pred1) and isinstance(ind, Ind)), \
("%s must be a predicate, and %s must be None or an individual" %
(pred, ind))
assert isinstance(yes, bool), "%s must be a bool" % yes
if isinstance(pred, str):
assert '(' in pred and pred.endswith(')'), \
"'%s' must be of the form '[-] pred ( [ind] )'" % pred
pred = pred[:-1]
if pred.startswith('-'):
yes = not yes
pred = pred[1:]
pred, _, ind = pred.partition('(')
if ind:
pred = Pred1(pred)
ind = Ind(ind)
else:
pred = Pred0(pred)
ind = None
self.content = pred, ind, yes
self.expires = expires
@property
def pred(self): return self.content[0]
@property
def ind(self): return self.content[1]
@property
def yes(self): return self.content[2]
def __neg__(self):
pred, ind, yes = self.content
return Prop(self.pred, self.ind, not self.yes)
def __str__(self):
pred, ind, yes = self.content
if self.expires:
return "%s%s(%s) - expires in %s" % ("" if yes else "-", pred, ind or "", self.expires)
else:
return "%s%s(%s)" % ("" if yes else "-", pred, ind or "")
def __repr__(self):
if self.expires:
expires_in = int(self.__dict__['expires'])-round(time.time())
if expires_in > 0:
return "Prop({0}) - expires in {1} secs".format(self.__dict__['content'], expires_in)
else:
return "Prop({}) - expired".format(self.__dict__['content'])
else:
return "Prop({})".format(self.__dict__['content'])
# return "%s(%r)" % (self.__class__, self.__dict__)
def _typecheck(self, context):
pred, ind, yes = self.content
assert (isinstance(pred, Pred0) and ind is None or
isinstance(pred, Pred1) and isinstance(ind, Ind))
assert isinstance(yes, bool)
pred._typecheck(context)
if ind is not None:
ind._typecheck(context)
assert context.preds1[pred.content] == context.inds[ind.content]
class ShortAns(Ans):
"""Short answer."""
contentclass = Ind
def __init__(self, ind, yes=True):
assert isinstance(yes, bool), "%s must be a boolean" % yes
assert isinstance(ind, (Ind, str)), "%s must be an individual" % ind
if isinstance(ind, str):
if ind.startswith('-'):
ind = ind[1:]
yes = not yes
ind = Ind(ind)
self.content = ind, yes
@property
def ind(self): return self.content[0]
@property
def yes(self): return self.content[1]
def __neg__(self):
ind, yes = self.content
return ShortAns(ind, not yes)
def __str__(self):
ind, yes = self.content
return "%s%s" % ("" if yes else "-", ind)
def _typecheck(self, context):
ind, yes = self.content
assert isinstance(ind, Ind)
assert isinstance(yes, bool)
ind._typecheck(context)
class YesNo(ShortAns):
"""Yes/no-answer."""
contentclass = bool
def __init__(self, yes):
assert isinstance(yes, (bool, str)), "%s must be a boolean" % yes
if isinstance(yes, str):
assert yes in ("yes", "no"), "'%s' must be 'yes' or 'no'" % yes
yes = yes == "yes"
self.content = yes
@property
def yes(self): return self.content
def __neg__(self):
return YesNo(not self.content)
def __str__(self):
return "yes" if self.content else "no"
######################################################################
# Question base class + its derivatives (wh-questions, y/n-questions, alternative questions)
######################################################################
class Question(Sentence):
"""Abstract base class for all kinds of questions.
Currently there are the following question classes:
- WhQ(pred), where pred is a Pred1
- YNQ(prop), where prop is a Prop
- AltQ(ynq1, ynq2, ...), where ynq1, ... are YNQs
To create a Question, use any of the constructors above,
or call the abstract constructor with a string, Question("..."):
- Question("?x.pred(x)") -> WhQ("pred")
- Question("?prop") -> YNQ("prop")
"""
def __new__(cls, que, *args, **kw):
"""Parse a string into a Question.
"?x.pred(x)" -> WhQ("pred")
"?prop" -> YNQ("prop")
"""
if cls is Question:
# print("QUESTION's que:", que)
assert isinstance(que, str)
if que.startswith('?x.y.') and que.endswith('(y)(x)'):
return SecOrdQ(que[5:-6], args[0])
assert not args and not kw #second-order-question erwartet als zweites arg die domain
if que.startswith('?x.') and que.endswith('(x)'):
return WhQ(que[3:-3])
elif que.startswith('?'):
return YNQ(que[1:])
else:
raise SyntaxError("Could not parse question: %s" % que)
else:
return Sentence.__new__(cls, que, *args, **kw)
class SecOrdQ(Question):
contentclass = Pred2
def __init__(self, pred, domaincontext=None):
assert isinstance(pred, (Pred2, str))
if isinstance(pred, str):
self.content = Pred2(pred, domaincontext)
else:
self.content = pred
def _typecheck(self, context=None):
assert isinstance(self.content, self.contentclass)
if hasattr(self.content, '_typecheck'):
self.content._typecheck(context)
def __repr__(self):
return "SecOrdQ({})".format(self.content)
def __str__(self):
if hasattr(self.content, "arg1"):
return "?x.y." + str(self.content) + "(x)"
return "?x.y." + str(self.content) + "(y)(x)"
class WhQ(Question):
"""Wh-question."""
contentclass = Pred1
def __init__(self, pred):
assert isinstance(pred, (Pred1, str))
if isinstance(pred, str):
if pred.startswith('?x.') and pred.endswith('(x)'):
pred = pred[3:-3]
pred = Pred1(pred)
self.content = pred
@property
def pred(self): return self.content
def __str__(self):
if hasattr(self.content, "arg2"):
return "?x.%s(%s)(x)" % (self.content.__str__(), self.content.arg2)
else:
return "?x.%s(x)" % self.content.__str__()
def __repr__(self):
return "WhQ("+str(self.content)+")"
class YNQ(Question):
"""Yes/no-question."""
contentclass = Prop
def __init__(self, prop):
assert isinstance(prop, (Prop, str))
if isinstance(prop, str):
if prop.startswith('?'):
prop = prop[1:]
prop = Prop(prop)
self.content = prop
@property
def prop(self): return self.content
def __str__(self):
return "?%s" % self.content.__str__()
class AltQ(Question):
"""Alternative question."""
def __init__(self, *ynqs):
if len(ynqs) == 1 and is_sequence(ynqs[0]):
ynqs = ynqs[0]
if not all(isinstance(q, (YNQ, str)) for q in ynqs):
raise TypeError("all AltQ arguments must be y/n-questions")
self.content = tuple((q if isinstance(q, YNQ) else Question(q))
for q in ynqs)
@property
def ynqs(self): return self.content
def __str__(self):
return "{" + " | ".join(map(str, self.content)) + "}"
def _typecheck(self, context):
assert all(isinstance(q, YNQ) for q in self.content)
for q in self.content:
q._typecheck(context)
######################################################################
# Command base class
######################################################################
class Command(Sentence):
def __new__(cls, cmd, *args, **kw):
if cls is Command:
assert isinstance(cmd, str)
assert not args and not kw
assert cmd.startswith("!(")
cmd = cmd[2:-1]
return Sentence.__new__(cls, cmd, *args, **kw)
def __init__(self, cmd):
self.content = cmd
self.new = True
def __str__(self):
return "cmd: %s" % self.content.__str__()
######################################################################
# Statement base class
######################################################################
class Statement(Sentence):
def __new__(cls, cmd, *args, **kw):
if cls is Statement:
assert isinstance(cmd, str)
assert not args and not kw
return Sentence.__new__(cls, cmd, *args, **kw)
def __init__(self, sent):
self.content = sent
def __str__(self):
return "statement: %s" % self.content.__str__()
##################################################################################################################
# IBIS dialogue moves
##################################################################################################################
class Imperative(Move):
contentclass = Command
class State(Move):
contentclass = Statement
def __str__(self):
return self.content.content
class Greet(SingletonMove): pass
class Quit(SingletonMove): pass
class Ask(Move):
contentclass = Question
def __str__(self):
return "Ask('%s')" % self.content.__str__()
def __repr__(self):
if hasattr(self, 'askedby'):
return "Ask('%s' by %s)" % (self.content.__str__(), self.askedby)
else:
return "Ask('%s')" % self.content.__str__()
def __init__(self, *args, **kwargs):
self.askedby = kwargs.get('askedby', 'SYS')
kwargs.pop('askedby', None)
super().__init__(*args, **kwargs)
class Answer(Move):
contentclass = Ans
class ICM(Move):
contentclass = object
def __init__(self, level, polarity, icm_content=None):
self.content = (level, polarity, icm_content)
def __str__(self):
s = "icm:" + self.level + "*" + self.polarity
if self.icm_content:
s += ":'" + self.icm_content + "'"
return s
@property
def level(self): return self.content[0]
@property
def polarity(self): return self.content[1]
@property
def icm_content(self): return self.content[2]
##################################################################################################################
# IBIS plan constructors
##################################################################################################################
class PlanConstructor(Type):
"""An abstract base class for plan constructors."""
class ClarifyPred2(PlanConstructor):
contentclass = Question
def __str__(self):
return "ClarifyPred2('%s')" % self.content.__str__()
class Respond(PlanConstructor):
contentclass = Question
def __str__(self):
return "Respond('%s')" % self.content.__str__()
class ConsultDB(PlanConstructor):
contentclass = Question
def __str__(self):
return "ConsultDB('%s')" % self.content.__str__()
class Findout(PlanConstructor):
contentclass = Question
def __str__(self):
return "Findout('%s')" % self.content.__str__()
class Raise(PlanConstructor):
contentclass = Question
def __str__(self):
return "Raise('%s')" % self.content.__str__()
# Complex plan constructs
class ExecuteFunc(PlanConstructor):
contentclass = (FunctionType, functools.partial, MethodType)
def __init__(self, funcname, *params, **kwparams):
assert isinstance(funcname, self.contentclass)
self.content = funcname
self.params = params
self.kwparams = kwparams
def __str__(self):
return "ExecuteFunc('%s') needing params %s" % (self.content.__name__, self.params)
class Inform(PlanConstructor):
contentclass = Statement
def __init__(self, formatstr, replacers=[]):
assert isinstance(formatstr, str)
self.content = formatstr
self.replacers = replacers
def __str__(self):
return "Inform('%s')" % self.content.__str__()
def _typecheck(self, context=None):
return True
class If(PlanConstructor):
"""A conditional plan constructor, consisting of a condition,
a true branch and an optional false branch.
"""
def __init__(self, cond, iftrue, iffalse=()):
if isinstance(cond, str):
cond = Question(cond)
self.cond = cond
self.iftrue = tuple(iftrue)
self.iffalse = tuple(iffalse)
@property
def content(self):
return (self.cond, self.iftrue, self.iffalse)
def _typecheck(self, context):
assert isinstance(self.cond, Question)
assert all(isinstance(m, PlanConstructor) for m in self.iftrue)
assert all(isinstance(m, PlanConstructor) for m in self.iffalse)
self.cond._typecheck(context)
for m in self.iftrue:
m._typecheck(context)
for m in self.iffalse:
m._typecheck(context)
def __str__(self):
return "If('%s', %s, %s)" % (self.cond.__str__(),
self.iftrue.__str__(),
self.iffalse.__str__())
def unpack(what):
# print(". unpacking", what, type(what))
if type(what) == Answer:
return unpack(what.content)
elif type(what) == Prop:
return unpack(what.ind)
elif type(what) == Pred1:
return unpack(what.content)
elif type(what) == Ind:
return unpack(what.content)
return str(what)