|
4 | 4 |
|
5 | 5 | from __future__ import annotations |
6 | 6 | from collections.abc import Iterator |
7 | | -from typing import TypeVar |
| 7 | +from enum import Enum, unique |
| 8 | +from typing import TypeVar, final, Generator, TypeAlias |
8 | 9 | from .. import terms |
9 | 10 | from . import Visitor |
10 | | -from .substitution import CountingSubstitutingVisitor |
| 11 | +from .substitution.renaming import CountingSubstitution |
11 | 12 |
|
12 | 13 | __all__ = ( |
| 14 | + "Conversion", |
13 | 15 | "BetaNormalisingVisitor", |
14 | 16 | ) |
15 | 17 |
|
16 | 18 | V = TypeVar("V") |
17 | 19 |
|
| 20 | +Step: TypeAlias = tuple["Conversion", terms.Term[str]] |
18 | 21 |
|
19 | | -class BetaNormalisingVisitor(Visitor[Iterator[terms.Term[str]], str]): |
| 22 | + |
| 23 | +@unique |
| 24 | +class Conversion(Enum): |
| 25 | + """Conversion performed by normalisation""" |
| 26 | + ALPHA = 0 |
| 27 | + BETA = 1 |
| 28 | + |
| 29 | + |
| 30 | +@final |
| 31 | +class BetaNormalisingVisitor(Visitor[Iterator[Step], str]): |
20 | 32 | """ |
21 | 33 | Visitor which transforms a term into its beta normal form, |
22 | | - yielding intermediate results until it is reached |
| 34 | + yielding intermediate steps until it is reached |
23 | 35 | """ |
24 | 36 |
|
25 | 37 | __slots__ = () |
26 | 38 |
|
27 | 39 | def skip_intermediate(self, term: terms.Term[str]) -> terms.Term[str]: |
28 | 40 | """return the beta normal form directly""" |
29 | 41 | result = term |
30 | | - for intermediate in term.accept(self): |
| 42 | + for _, intermediate in term.accept(self): |
31 | 43 | result = intermediate |
32 | 44 | return result |
33 | 45 |
|
34 | | - def visit_variable(self, variable: terms.Variable[str]) -> Iterator[terms.Variable[str]]: |
| 46 | + def visit_variable(self, variable: terms.Variable[str]) -> Iterator[Step]: |
35 | 47 | """visit a Variable term""" |
36 | 48 | return iter(()) |
37 | 49 |
|
38 | | - def visit_abstraction(self, abstraction: terms.Abstraction[str]) -> Iterator[terms.Abstraction[str]]: |
| 50 | + def visit_abstraction(self, abstraction: terms.Abstraction[str]) -> Iterator[Step]: |
39 | 51 | """visit an Abstraction term""" |
40 | 52 | results = abstraction.body.accept(self) |
41 | | - return map(lambda b: terms.Abstraction(abstraction.bound, b), results) |
| 53 | + return map(lambda s: (s[0], terms.Abstraction(abstraction.bound, s[1])), results) |
| 54 | + |
| 55 | + def beta_reducation(self, abstraction: terms.Abstraction[str], argument: terms.Term[str]) -> Generator[Step, None, terms.Term[str]]: |
| 56 | + """perform beta reduction of an application""" |
| 57 | + conversions = CountingSubstitution.from_substitution(abstraction.bound, argument).trace() |
| 58 | + reduced = yield from map( |
| 59 | + lambda body: ( |
| 60 | + Conversion.ALPHA, |
| 61 | + terms.Application(terms.Abstraction(abstraction.bound, body), argument) |
| 62 | + ), |
| 63 | + abstraction.body.accept(conversions) # type: ignore |
| 64 | + ) |
| 65 | + yield (Conversion.BETA, reduced) |
| 66 | + return reduced # type: ignore |
42 | 67 |
|
43 | | - def visit_application(self, application: terms.Application[str]) -> Iterator[terms.Term[str]]: |
| 68 | + def visit_application(self, application: terms.Application[str]) -> Iterator[Step]: |
44 | 69 | """visit an Application term""" |
45 | | - match application.abstraction: |
46 | | - # normal order dictates we reduce leftmost outermost redex first |
47 | | - case terms.Abstraction(bound, body): |
48 | | - reduced = body.accept(CountingSubstitutingVisitor(bound, application.argument)) |
49 | | - yield reduced |
50 | | - yield from reduced.accept(self) |
51 | | - case _: |
52 | | - # try to reduce the abstraction until this is a redex |
53 | | - abstraction = application.abstraction |
54 | | - for transformation in application.abstraction.accept(self): |
55 | | - yield terms.Application(transformation, application.argument) |
56 | | - match transformation: |
57 | | - case terms.Abstraction(bound, body): |
58 | | - reduced = body.accept( |
59 | | - CountingSubstitutingVisitor(bound, application.argument) |
60 | | - ) |
61 | | - yield reduced |
62 | | - yield from reduced.accept(self) |
63 | | - return |
64 | | - case _: |
65 | | - abstraction = transformation |
66 | | - # no redex, continue with argument |
67 | | - transformations = application.argument.accept(self) |
68 | | - yield from map(lambda a: terms.Application(abstraction, a), transformations) |
| 70 | + if isinstance(application.abstraction, terms.Abstraction): |
| 71 | + # normal order dictates we reduce the leftmost outermost redex first |
| 72 | + reduced = yield from self.beta_reducation(application.abstraction, application.argument) |
| 73 | + yield from reduced.accept(self) |
| 74 | + else: |
| 75 | + # try to reduce the abstraction until this is a redex |
| 76 | + abstraction = application.abstraction |
| 77 | + for conversion, transformation in application.abstraction.accept(self): |
| 78 | + yield (conversion, terms.Application(transformation, application.argument)) |
| 79 | + if isinstance(transformation, terms.Abstraction): |
| 80 | + reduced = yield from self.beta_reducation(transformation, application.argument) |
| 81 | + yield from reduced.accept(self) |
| 82 | + return |
| 83 | + else: |
| 84 | + abstraction = transformation |
| 85 | + # no redex, continue with argument |
| 86 | + transformations = application.argument.accept(self) |
| 87 | + yield from map(lambda s: (s[0], terms.Application(abstraction, s[1])), transformations) |
0 commit comments