# Part 5: Design and implementation of the to_string method

In this section, we design and implement a method to transform a `WFF`

back to a string. The string will have correct UTF-8 characters for the logical connectives. This is a very simple example to highlight the simplicity and usefulness of textX models.

There are two main approaches to implement a method that returns a string:

**Recursive**approach: The method calls itself multiple times, from different objects, until the string is built. Simplicity and conciseness are the main reasons to use this approach, but it can also lead to a stack overflow due to excessively deep recursion.**Iterative**approach: Use a loop to traverse the AST and build the string. This method also needs a stack to keep track of the nodes but is less likely to overflow and often performs better than the recursive approach.

A recursive approach may be more elegant and simple to implement, so we choose this approach.

## Recursive approach

We will recursively create the string representation of each wff in a `WFF`

object at instantiation and will store the string in a `formula`

attribute. Storing the string avoids regenerating it each time at the price of memory usage. We can then simply override the `__str__`

method to return the `formula`

attribute.

Here’s a refresher on how to design a recursive algorithm:

- Identify the base case: The case that will stop the recursion and prevent it from infinitely calling itself.
- Identify the recursive case: Break the problem into smaller subproblems and call the function to recursively solve them.
- Combine the results: Combine the results of the recursive calls to solve the original problem.

### Step 1: Identify the base case

The recursion will stop with the leaf nodes of the AST. The leaf node will always be of type `Atom`

, which stores the propositional variable name in the `id`

attribute.

```
class Atom(WFF):
def __init__(self, parent: Any, id: Variable):
self.parent = parent
self.id = id
self.formula = id
```

### Step 2: Identify the recursive case

The connectives, however, will keep recursing until they reach a leaf node. In contrast to the unary connective `Negation`

, the binary connective will call the `__str__`

method on both the left-hand and right-hand side wffs.

### Step 3: Combine the results

The substrings are combined using either string concatenation or formatting methods. Here we will use F-strings also called formatted string literals.

To improve the aesthetics of the string representation, we use UTF-8 characters representing logical connectives. The symbols can be found on the following Wikipedia page.

```
class Negation(WFF):
def __init__(self, parent: Any, wff: WFF):
self.parent = parent
self.wff = wff
self.formula = f"¬{self.wff}"
class Conjunction(WFF):
def __init__(self, parent: Any, lhs: WFF, rhs: WFF):
self.parent = parent
self.lhs = lhs
self.rhs = rhs
self.formula = f"({self.lhs} ∧ {self.rhs})"
class Disjunction(WFF):
def __init__(self, parent: Any, lhs: WFF, rhs: WFF):
self.parent = parent
self.lhs = lhs
self.rhs = rhs
self.formula = f"({self.lhs} ∨ {self.rhs})"
class Implication(WFF):
def __init__(self, parent: Any, lhs: WFF, rhs: WFF):
self.parent = parent
self.lhs = lhs
self.rhs = rhs
self.formula = f"({self.lhs} → {self.rhs})"
class Equivalence(WFF):
def __init__(self, parent: Any, lhs: WFF, rhs: WFF):
self.parent = parent
self.lhs = lhs
self.rhs = rhs
self.formula = f"({self.lhs} ↔ {self.rhs})"
```

Finally, we override the `__str__`

method to return the `formula`

attribute.

```
# ... previous code
type Formula = str
class WFF(ABC):
formula: Formula = ""
def __str__(self) -> Formula:
return self.formula
```

Part 6 is a bonus section for those interested in learning about precedence rules and an iterative approach to building the string representation.