Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

# Natural Language Toolkit: Combinatory Categorial Grammar 

# 

# Copyright (C) 2001-2012 NLTK Project 

# Author: Graeme Gange <ggange@csse.unimelb.edu.au> 

# URL: <http://www.nltk.org/> 

# For license information, see LICENSE.TXT 

 

from nltk.parse import ParserI 

from nltk.internals import Counter 

 

from nltk.ccg.api import FunctionalCategory 

 

class UndirectedBinaryCombinator(object): 

    """ 

    Abstract class for representing a binary combinator. 

    Merely defines functions for checking if the function and argument 

    are able to be combined, and what the resulting category is. 

 

    Note that as no assumptions are made as to direction, the unrestricted 

    combinators can perform all backward, forward and crossed variations 

    of the combinators; these restrictions must be added in the rule 

    class. 

    """ 

    def can_combine(self, function, argument): 

        raise NotImplementedError() 

 

    def combine (self,function,argument): 

        raise NotImplementedError() 

 

class DirectedBinaryCombinator(object): 

    """ 

    Wrapper for the undirected binary combinator. 

    It takes left and right categories, and decides which is to be 

    the function, and which the argument. 

    It then decides whether or not they can be combined. 

    """ 

    def can_combine(self, left, right): 

        raise NotImplementedError() 

 

    def combine(self, left, right): 

        raise NotImplementedError() 

 

class ForwardCombinator(DirectedBinaryCombinator): 

    ''' 

    Class representing combinators where the primary functor is on the left. 

 

    Takes an undirected combinator, and a predicate which adds constraints 

    restricting the cases in which it may apply. 

    ''' 

    def __init__(self, combinator, predicate, suffix=''): 

        self._combinator = combinator 

        self._predicate = predicate 

        self._suffix = suffix 

 

    def can_combine(self, left, right): 

        return (self._combinator.can_combine(left,right) and 

                  self._predicate(left,right)) 

 

    def combine(self, left, right): 

        for cat in self._combinator.combine(left,right): 

            yield cat 

 

    def __str__(self): 

        return '>' + str(self._combinator) + self._suffix 

 

class BackwardCombinator(DirectedBinaryCombinator): 

    ''' 

    The backward equivalent of the ForwardCombinator class. 

    ''' 

    def __init__(self, combinator, predicate, suffix=''): 

        self._combinator = combinator 

        self._predicate = predicate 

        self._suffix = suffix 

 

    def can_combine(self, left, right): 

        return (self._combinator.can_combine(right, left) and 

                  self._predicate(left,right)) 

    def combine(self, left, right): 

        for cat in self._combinator.combine(right, left): 

            yield cat 

 

    def __str__(self): 

        return '<' + str(self._combinator) + self._suffix 

 

class UndirectedFunctionApplication(UndirectedBinaryCombinator): 

    """ 

    Class representing function application. 

    Implements rules of the form: 

    X/Y Y -> X (>) 

    And the corresponding backwards application rule 

    """ 

 

    def can_combine(self, function, argument): 

        if not function.is_function(): 

            return False 

 

        return not function.arg().can_unify(argument) is None 

 

    def combine(self,function,argument): 

        if not function.is_function(): 

            return 

 

        subs = function.arg().can_unify(argument) 

        if subs is None: 

            return 

 

        yield function.res().substitute(subs) 

 

    def __str__(self): 

        return '' 

 

 

# Predicates for function application. 

 

# Ensures the left functor takes an argument on the right 

def forwardOnly(left,right): 

    return left.dir().is_forward() 

 

# Ensures the right functor takes an argument on the left 

def backwardOnly(left,right): 

    return right.dir().is_backward() 

 

# Application combinator instances 

ForwardApplication = ForwardCombinator(UndirectedFunctionApplication(), 

                        forwardOnly) 

BackwardApplication = BackwardCombinator(UndirectedFunctionApplication(), 

                        backwardOnly) 

 

 

class UndirectedComposition(UndirectedBinaryCombinator): 

    """ 

    Functional composition (harmonic) combinator. 

    Implements rules of the form 

    X/Y Y/Z -> X/Z (B>) 

    And the corresponding backwards and crossed variations. 

    """ 

    def can_combine(self, function, argument): 

        # Can only combine two functions, and both functions must 

        # allow composition. 

        if not (function.is_function() and argument.is_function()): 

            return False 

        if function.dir().can_compose() and argument.dir().can_compose(): 

            return not function.arg().can_unify(argument.res()) is None 

        return False 

 

    def combine(self, function, argument): 

        if not (function.is_function() and argument.is_function()): 

            return 

        if function.dir().can_compose() and argument.dir().can_compose(): 

            subs = function.arg().can_unify(argument.res()) 

            if not subs is None: 

                yield FunctionalCategory(function.res().substitute(subs), 

                            argument.arg().substitute(subs),argument.dir()) 

 

    def __str__(self): 

        return 'B' 

 

# Predicates for restricting application of straight composition. 

def bothForward(left,right): 

    return left.dir().is_forward() and right.dir().is_forward() 

 

def bothBackward(left,right): 

    return left.dir().is_backward() and right.dir().is_backward() 

 

# Predicates for crossed composition 

 

def crossedDirs(left,right): 

    return left.dir().is_forward() and right.dir().is_backward() 

 

def backwardBxConstraint(left,right): 

    # The functors must be crossed inwards 

    if not crossedDirs(left, right): 

        return False 

    # Permuting combinators must be allowed 

    if not left.dir().can_cross() and right.dir().can_cross(): 

        return False 

    # The resulting argument category is restricted to be primitive 

    return left.arg().is_primitive() 

 

# Straight composition combinators 

ForwardComposition = ForwardCombinator(UndirectedComposition(), 

                           forwardOnly) 

BackwardComposition = BackwardCombinator(UndirectedComposition(), 

                           backwardOnly) 

 

# Backward crossed composition 

BackwardBx = BackwardCombinator(UndirectedComposition(),backwardBxConstraint, 

                suffix='x') 

 

 

class UndirectedSubstitution(UndirectedBinaryCombinator): 

    """ 

    Substitution (permutation) combinator. 

    Implements rules of the form 

    Y/Z (X\Y)/Z -> X/Z (<Sx) 

    And other variations. 

    """ 

    def can_combine(self, function, argument): 

        if function.is_primitive() or argument.is_primitive(): 

            return False 

 

        # These could potentially be moved to the predicates, as the 

        # constraints may not be general to all languages. 

        if function.res().is_primitive(): 

            return False 

        if not function.arg().is_primitive(): 

            return False 

 

        if not (function.dir().can_compose() and argument.dir().can_compose()): 

            return False 

        return (function.res().arg() == argument.res()) and (function.arg() == argument.arg()) 

 

    def combine(self,function,argument): 

        if self.can_combine(function,argument): 

            yield FunctionalCategory(function.res().res(),argument.arg(),argument.dir()) 

 

    def __str__(self): 

        return 'S' 

 

# Predicate for forward substitution 

def forwardSConstraint(left, right): 

    if not bothForward(left, right): 

        return False 

    return left.res().dir().is_forward() and left.arg().is_primitive() 

 

# Predicate for backward crossed substitution 

def backwardSxConstraint(left,right): 

    if not left.dir().can_cross() and right.dir().can_cross(): 

        return False 

    if not bothForward(left, right): 

        return False 

    return right.res().dir().is_backward() and right.arg().is_primitive() 

 

# Instances of substitution combinators 

ForwardSubstitution = ForwardCombinator(UndirectedSubstitution(), 

                            forwardSConstraint) 

BackwardSx = BackwardCombinator(UndirectedSubstitution(), 

                    backwardSxConstraint,'x') 

 

 

# Retrieves the left-most functional category. 

# ie, (N\N)/(S/NP) => N\N 

def innermostFunction(categ): 

    while categ.res().is_function(): 

        categ = categ.res() 

    return categ 

 

class UndirectedTypeRaise(UndirectedBinaryCombinator): 

    ''' 

    Undirected combinator for type raising. 

    ''' 

    def can_combine(self,function,arg): 

        # The argument must be a function. 

        # The restriction that arg.res() must be a function 

        # merely reduces redundant type-raising; if arg.res() is 

        # primitive, we have: 

        # X Y\X =>(<T) Y/(Y\X) Y\X =>(>) Y 

        # which is equivalent to 

        # X Y\X =>(<) Y 

        if not (arg.is_function() and arg.res().is_function()): 

                return False 

 

        arg = innermostFunction(arg) 

 

        # left, arg_categ are undefined! 

        subs = left.can_unify(arg_categ.arg()) 

        if subs is not None: 

            return True 

        return False 

 

    def combine(self,function,arg): 

        if not (function.is_primitive() and \ 

                arg.is_function() and arg.res().is_function()): 

            return 

 

        # Type-raising matches only the innermost application. 

        arg = innermostFunction(arg) 

 

        subs = function.can_unify(arg.arg()) 

        if subs is not None: 

            xcat = arg.res().substitute(subs) 

            yield FunctionalCategory(xcat, 

                    FunctionalCategory(xcat,function,arg.dir()), 

                    -(arg.dir())) 

 

    def __str__(self): 

        return 'T' 

 

# Predicates for type-raising 

# The direction of the innermost category must be towards 

# the primary functor. 

# The restriction that the variable must be primitive is not 

# common to all versions of CCGs; some authors have other restrictions. 

def forwardTConstraint(left,right): 

    arg = innermostFunction(right) 

    return arg.dir().is_backward() and arg.res().is_primitive() 

 

def backwardTConstraint(left,right): 

    arg = innermostFunction(left) 

    return arg.dir().is_forward() and arg.res().is_primitive() 

 

# Instances of type-raising combinators 

ForwardT = ForwardCombinator(UndirectedTypeRaise(), forwardTConstraint) 

BackwardT = BackwardCombinator(UndirectedTypeRaise(), backwardTConstraint)