| Trees | Indices | Help |
|
|---|
|
|
1 """Grammar rules module for the music_halfspan formalism.
2
3 Grammatical rules for the keyspan formalism. A lot of this is standard
4 CCG, with a few special additions.
5
6 """
7 """
8 ============================== License ========================================
9 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
10
11 This file is part of The Jazz Parser.
12
13 The Jazz Parser is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 The Jazz Parser is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
25
26 ============================ End license ======================================
27
28 """
29 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
30
31 import logging, copy
32 from jazzparser.formalisms.base.rules import ApplicationRuleBase, CompositionRuleBase, \
33 Rule
34 from jazzparser.formalisms.base.semantics.temporal import temporal_rule_apply
35 from jazzparser.formalisms.base.semantics.lambdacalc import distinguish_variables, \
36 next_unused_variable
37 from .syntax import AtomicCategory, Sign
38 from .semantics import concatenate, Variable, LambdaAbstraction, \
39 FunctionApplication, Semantics, Now, \
40 EnharmonicCoordinate, Coordination
41 from jazzparser.parsers import RuleApplicationError
42 from jazzparser import settings
43 from jazzparser.utils.tonalspace import coordinate_to_et_2d
44
45 # Get the logger from the logging system
46 logger = logging.getLogger("main_logger")
53
55 """
56 For halfspan, the definition of the application rule is very
57 important. It's not the standard CCG application. Crucially,
58 it requires the argument part of the slash category to match
59 the nearest end of the argument category.
60
61 """
62 ################## Check the rule is valid
63 # Must be applied to 2 args
64 if len(cat_list) != 2:
65 return None
66
67 # Functor arg must be a slash category in the right direction
68 if self.forward:
69 # X/Y Y => X
70 functor = cat_list[0]
71 argument = cat_list[1]
72 else:
73 # Y X\Y => X
74 functor = cat_list[1]
75 argument = cat_list[0]
76
77 # Check correct cat type
78 if not self.formalism.Syntax.is_atomic_category(argument.category):
79 return None
80 if not self.formalism.Syntax.is_complex_category(functor.category):
81 return None
82 # Check right slash direction
83 if functor.category.slash.forward != self.forward:
84 return None
85
86 if self.forward:
87 near_argument = argument.category.from_half
88 else:
89 near_argument = argument.category.to_half
90
91 # Here's the crucial check:
92 # in W/X Y-Z, X must match Y
93 # in W-V Y\Z, Z must match V
94 if not functor.category.argument.matches(near_argument):
95 return None
96
97 ################## Checks passed: apply the rule
98 # Apply the unification substitutions
99 # Build the new category out of the result part of the slash
100 # category and the far part of the argument
101 functor_cat = functor.category.copy()
102 argument_cat = argument.category.copy()
103
104 if self.forward:
105 category = AtomicCategory(functor_cat.result, argument_cat.to_half)
106 else:
107 category = AtomicCategory(argument_cat.from_half, functor_cat.result)
108
109 semantics = self.apply_rule_semantics(cat_list, proc_sems=proc_sems)[0]
110 result = self.formalism.Syntax.Sign(category, semantics)
111
112 if settings.WARN_ABOUT_FREE_VARS:
113 free_vars = semantics.lf.get_unbound_variables()
114 if free_vars:
115 logger.warn("Found free variables after application: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics))
116
117 return [result]
118
119 @temporal_rule_apply(semantics_only=True)
121 """
122 Provides the semantic part of rule application separately from the
123 syntactic part.
124
125 @see: L{jazzparser.formalisms.base.rules.Rule.apply_rule_semantics}
126
127 """
128 # Assume all syntactic checks have succeeded and do the semantic processing
129 if self.forward:
130 # X/Y Y => X
131 functor = cat_list[0]
132 argument = cat_list[1]
133 else:
134 # Y X\Y => X
135 functor = cat_list[1]
136 argument = cat_list[0]
137
138 # Combine semantics by function application
139 new_functor = functor.semantics.copy()
140 new_argument = argument.semantics.copy()
141 # Run any additional processing on the semantics of the inputs
142 if proc_sems is not None:
143 proc_sems(new_functor, new_argument)
144
145 semantics = self.formalism.Semantics.apply(new_functor, new_argument, grammar=self.grammar)
146
147 if settings.WARN_ABOUT_FREE_VARS:
148 free_vars = semantics.lf.get_unbound_variables()
149 if free_vars:
150 logger.warn("Found free variables after application: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics))
151 return [semantics]
152
158
160 """
161 Unlike application, composition could use the standard CCG
162 definition, but here we redefine it. This allows us to handle
163 multiple functions on arguments not by using unification, but
164 just by calling C{matches}.
165
166 """
167 ################## Check the rule is valid
168 # Can only operate on 2 categories
169 if len(cat_list) != 2:
170 return None
171
172 first = cat_list[0]
173 second = cat_list[1]
174
175 if not self.formalism.Syntax.is_complex_category(first.category) or \
176 not self.formalism.Syntax.is_complex_category(second.category):
177 return None
178
179 if self.harmonic:
180 if first.category.slash.forward != self.forward or \
181 second.category.slash.forward != self.forward:
182 return None
183 else:
184 if not first.category.slash.forward or \
185 second.category.slash.forward:
186 return None
187
188 # Extract the right bits according to whether it's forward or backward
189 if self.forward:
190 middle_arg = first.category.argument
191 middle_res = second.category.result
192 else:
193 middle_arg = second.category.argument
194 middle_res = first.category.result
195
196 if not middle_arg.matches(middle_res):
197 return None
198
199 ################## Checks passed: apply the rule
200 first_cat = first.category.copy()
201 second_cat = second.category.copy()
202 # Extract the right bits according to whether it's forward or backward.
203 # This bit is the same for harmonic and crossing.
204 if self.forward:
205 result = first_cat.result
206 argument = second_cat.argument
207 else:
208 result = second_cat.result
209 argument = first_cat.argument
210
211 # Harmonic: forward slashed result for forward slashed rule.
212 # Crossing: backward slashed result for forward slashed rule.
213 slash = self.formalism.Syntax.Slash(self.forward == self.harmonic)
214
215 # Set the slash modality
216 if first.category.slash.modality == '':
217 slash.modality = second.category.slash.modality
218 else:
219 # The first slash is cadential. If the second is cadential,
220 # they agree, so the result is cadential. If the second is empty,
221 # the result is still cadential, since the first one wins.
222 slash.modality = first.category.slash.modality
223
224 category = self.formalism.Syntax.ComplexCategory(
225 result, \
226 slash, \
227 argument)
228
229 semantics = self.apply_rule_semantics(cat_list, proc_sems=proc_sems)[0]
230 result = self.formalism.Syntax.Sign(category, semantics)
231
232 return [result]
233
234 @temporal_rule_apply(semantics_only=True)
236 """
237 Provides the semantic part of rule application separately from the
238 syntactic part.
239
240 @see: L{jazzparser.formalisms.base.rules.Rule.apply_rule_semantics}
241
242 """
243 # Assume all syntactic checks have succeeded and do the semantic processing
244 if self.forward:
245 fun_f = cat_list[0].semantics.copy()
246 fun_g = cat_list[1].semantics.copy()
247 else:
248 fun_g = cat_list[0].semantics.copy()
249 fun_f = cat_list[1].semantics.copy()
250
251 # Make sure we don't confuse similarly-named variables
252 # Don't think we should need to do this, because fun app should take care of it
253 #from jazzparser.formalisms.base.semantics.lambdacalc import distinguish_variables
254 #distinguish_variables(fun_f, fun_g)
255
256 if proc_sems is not None:
257 proc_sems(fun_f, fun_g)
258
259 semantics = self.formalism.Semantics.compose(fun_f, fun_g)
260
261 # Make sure semantics is in BNF
262 semantics.beta_reduce(grammar=self.grammar)
263
264 if settings.WARN_ABOUT_FREE_VARS:
265 free_vars = semantics.lf.get_unbound_variables()
266 if free_vars:
267 logger.warn("Found free variables after composition: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics))
268 return [semantics]
269
272 """
273 The development rule strings together sequences of resolved cadences.
274
275 This used to be called "continuation" and was renamed long ago, but
276 the old name persisted in the keyspan implementation.
277
278 """
280 from . import Formalism
281 super(DevelopmentRule, self).__init__(Formalism, *args, **kwargs)
282 self.name = "<dev>"
283 self.internal_name = "dev"
284 self.readable_rule = "W-X Y-Z =>dev W-Z"
285
286 self.arity = 2
287
289 from . import Formalism
290 ################## Check the rule is valid
291 # Only 2 categories at a time
292 if len(cat_list) != 2:
293 return None
294
295 # Can only apply to atomic categories
296 if not Formalism.Syntax.is_atomic_category(cat_list[0].category) or \
297 not Formalism.Syntax.is_atomic_category(cat_list[1].category):
298 return None
299
300 ################## Checks passed: apply the rule
301 # Build the resulting syntactic category
302 first_cat = cat_list[0].category
303 second_cat = cat_list[1].category
304
305 from .syntax import AtomicCategory
306 result_cat = AtomicCategory(
307 first_cat.from_half.copy(),
308 second_cat.to_half.copy())
309
310 semantics = self.apply_rule_semantics(cat_list, proc_sems=proc_sems)[0]
311
312 return [Sign(result_cat, semantics)]
313
314 @temporal_rule_apply(semantics_only=True)
316 """
317 Provides the semantic part of rule application separately from the
318 syntactic part.
319
320 @see: L{jazzparser.formalisms.base.rules.Rule.apply_rule_semantics}
321
322 """
323 # Assume all syntactic checks have succeeded and do the semantic processing
324 sem0 = cat_list[0].semantics.copy()
325 sem1 = cat_list[1].semantics.copy()
326
327 if proc_sems is not None:
328 proc_sems(sem0, sem1)
329
330 # The two inputs should be lists: just concatenate them
331 semantics = concatenate(sem0, sem1)
332 return [semantics]
333
335 """
336 The coordination rule allows partial cadences to combine and share
337 a resolution.
338
339 """
341 from . import Formalism
342 super(CoordinationRule, self).__init__(Formalism, *args, **kwargs)
343 self.name = "<&>"
344 self.internal_name = "coord"
345 self.readable_rule = "X/Y Z/Y =>& X/Y"
346
347 self.arity = 2
348
350 from . import Formalism
351 ################## Check the rule is valid
352 # Only 2 categories at a time
353 if len(cat_list) != 2:
354 return None
355 cat0, cat1 = cat_list[0], cat_list[1]
356
357 # Can only apply to cadential slash categories
358 if not Formalism.Syntax.is_complex_category(cat0.category) or \
359 not Formalism.Syntax.is_complex_category(cat1.category):
360 return None
361
362 # Both categories must have a cadence modality
363 if not (cat0.category.slash.modality == cat1.category.slash.modality == 'c'):
364 return None
365
366 arg0, arg1 = cat0.category.argument, cat1.category.argument
367 # Both categories must have an identical argument (except for
368 # functions, see below)
369 if not (arg0.root == arg1.root):
370 return None
371
372 # The categories must at least share some function on their arguments
373 if arg0.functions.isdisjoint(arg1.functions):
374 return None
375
376 # They must have the same function on their result
377 if not (cat0.category.result.functions == cat1.category.result.functions):
378 return None
379
380 ################## Checks passed: apply the rule
381 # The result is syntactically the same as the first input
382 new_cat = cat0.category.copy()
383 # Restrict the argument to functions allowed by both the inputs' arguments
384 new_cat.argument.functions = arg0.functions & arg1.functions
385
386 semantics = self.apply_rule_semantics(cat_list, proc_sems=proc_sems)[0]
387 result = self.formalism.Syntax.Sign(new_cat, semantics)
388
389 return [result]
390
391 @temporal_rule_apply(semantics_only=True)
393 """
394 Provides the semantic part of rule application separately from the
395 syntactic part.
396
397 @see: L{jazzparser.formalisms.base.rules.Rule.apply_rule_semantics}
398
399 """
400 # Assume all syntactic checks have succeeded and do the semantic processing
401 cad0 = cat_list[0].semantics.lf.copy()
402 cad1 = cat_list[1].semantics.lf.copy()
403
404 if proc_sems is not None:
405 proc_sems(cad0, cad1)
406
407 # Use the special coordination logical operator to combine these
408 semantics = Semantics(
409 Coordination([cad0, cad1]))
410 semantics.beta_reduce()
411
412 if settings.WARN_ABOUT_FREE_VARS:
413 free_vars = semantics.lf.get_unbound_variables()
414 if free_vars:
415 logger.warn("Found free variables after coordination: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics))
416 return [semantics]
417
420 """
421 A special unary rule for expanding the lexicon to add tonic
422 repetition categories, generated from the tonic categories already
423 in the lexicon.
424
425 This doesn't get used during parsing, but only to generate the
426 full lexicon when it's loaded up.
427
428 It will expand any tonic category: X[T] => X[T]/X[T] : \\x.x
429
430 """
432 from . import Formalism
433 super(TonicRepetitionRule, self).__init__(Formalism, *args, **kwargs)
434 self.name = "<rep>"
435 self.internal_name = "rep"
436 self.readable_rule = "X[T] =>rep X[T]/X[T]"
437 self.arity = 1
438
440 from . import Formalism
441 ################## Check the rule is valid
442 # Unary rule
443 if len(cat_list) != 1:
444 return None
445 sign = cat_list[0]
446 cat = sign.category
447
448 if not Formalism.Syntax.is_atomic_category(cat):
449 return None
450
451 # Lexical categories will only ever be specified as half
452 # categories, since their start and end will always be equal.
453 # Check that this is only being applied to such categories
454 if cat.from_half != cat.to_half:
455 return None
456
457 ################## Checks passed: apply the rule
458 # We need two half categories: both are the same as the halves
459 # of the input cat
460 new_res = cat.from_half.copy()
461 new_arg = cat.from_half.copy()
462 new_cat = Formalism.Syntax.ComplexCategory(
463 new_res,
464 Formalism.Syntax.Slash(dir=True),
465 new_arg)
466
467 # Semantics for every result should be just \x.x
468 semantics = Semantics(
469 LambdaAbstraction(
470 Variable("x"),
471 FunctionApplication(
472 Now(),
473 Variable("x")
474 )
475 ))
476
477 result = Formalism.Syntax.Sign(new_cat, semantics)
478 return [result]
479
482 """
483 A special unary rule for expanding the lexicon to add dominant
484 or subdominant repetition categories for all the substitutions,
485 generated from the cadential categories already in the lexicon.
486
487 This doesn't get used during parsing, but only to generate the
488 full lexicon when it's loaded up.
489
490 It will expand any cadence category: X[f]/c Y => X[f]/X[f] : \\x.x
491
492 """
494 from . import Formalism
495 super(CadenceRepetitionRule, self).__init__(Formalism, *args, **kwargs)
496 self.name = "<crep>"
497 self.internal_name = "crep"
498 self.readable_rule = "X[f]/c Y =>crep X[f]/X[f]"
499 self.arity = 1
500
502 from . import Formalism
503 ################## Check the rule is valid
504 # Unary rule
505 if len(cat_list) != 1:
506 return None
507 sign = cat_list[0]
508 cat = sign.category
509
510 if not Formalism.Syntax.is_complex_category(cat):
511 return None
512
513 # Slash modality must be cadential
514 if cat.slash.modality != "c":
515 return None
516
517 # Double-check that the function on the result is dominant
518 # or subdominant (it should be, if the modality is cadential)
519 if cat.result.function not in ["D","S"]:
520 return None
521
522 ################## Checks passed: apply the rule
523 # We're only interested in the result part (because that
524 # gives the syntactic starting point)
525 new_res = cat.result.copy()
526 new_arg = cat.result.copy()
527 # The output has a non-cadential slash, because these things
528 # don't constitute cadences on their own - only once they've
529 # combined with another cadential cat
530 new_cat = Formalism.Syntax.ComplexCategory(
531 new_res,
532 Formalism.Syntax.Slash(dir=True),
533 new_arg)
534
535 # Semantics for every result should be just \x.x
536 semantics = Semantics(
537 LambdaAbstraction(
538 Variable("x"),
539 FunctionApplication(
540 Now(),
541 Variable("x")
542 )
543 ))
544
545 result = Formalism.Syntax.Sign(new_cat, semantics)
546 return [result]
547
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Nov 26 16:05:02 2012 | http://epydoc.sourceforge.net |