pyrealb Hacking
Guy Lapalme
RALI-DIRO
Université de Montréal
March 2025

pyrealb is a Python package which allows English and French sentence realization by programming language instructions that create internal data structures corresponding to the elements of the sentence. This data structure can be built incrementally. When needed, the realization process traverses this structure to produce an output string in the appropriate language.

Python functions build sentence structures from terminals and properly order words within a sentence. They perform the most common agreements between constituents and carry out other useful sentence organization tasks, such as managing coordination or applying sentence transformations. Additionally, it spells out numbers and expresses temporal expressions.

The function names for building syntactic structures were chosen to resemble the symbols typically used in linguistics for constituent syntax trees and for dependency structures. The following code demonstrates two pyrealb expressions that are realized as “He eats apples.” ’ when called as s.realize() or r.realize().

# CONSTITUENT notation
s = S(                         # Sentence
      Pro("him").c("nom"),     # Pronoun (citation form), nominative case
      VP(V("eat"),             # Verb at present tense by default
         NP(D("a"),            # Noun Phrase, Determiner
            N("apple").n("p")  # Noun plural
           )       
        )
    )
# DEPENDENCY notation
r = root(V("eat"),                  # Sentence with a verb as head, two dependents
         subj(Pro("him").c("nom")), # Subject with a pronoun as head
         comp(N("apple").n("p"),    # Complement with a noun as head
              det(D("a")))          # dependent with a determiner
    )

Previously, examples of pyrealb expressions were explicitly written in source programs. However, in some cases, programs can construct or modify these expressions by invoking Python functions. It is only when the .realize() function is called that realization decisions are made. Occasionally, such as when using negation or passive voice, it requires adding words or altering the sentence’s structure.

This document provides some tips on how to dynamically modify pyrealb structures.

Note: Although this guide details modifying pyrealb expressions, the same principles and methods apply to jsRealB, whose modification API is identical to that of pyrealb.

Constituent organization

In order to modify pyrealb constituents, it is important to understand how they are organized. The following table provides the names of the primary classes and factory functions that create their instances:

Class Functions
Constituent
Terminal N,A,Pro,D,V,Adv,P,C,DT,NO,Q
Phrase NP,AP,VP,AdvP,PP,CP,S,SP
Dependent root,subj,det,comp,mod,coord

This diagram depicts a simplified inheritance hierarchy, highlighting the pertinent attributes for structural modification. Each block consists of three components: the class name, the instance variables, and the methods. Additionally, the data types of fields, parameters, and method results are provided. When a value can be None, its type is indicated with a question mark.

pyrealb-classes

Internally, the class structure is more complex, including language-specific classes and auxiliary ones that are not shown here, as they are not relevant to structural changes.

Analyzing an expression

A pyrealb expression is a hierarchy of Constituent objects, each with a constType field indicating its nature and a feature dictionary that drives the generation process. A Terminal instance has an additional lemma field. Both Phrase and Dependent objects contain a list of child constituents. A Dependent instance also has a field for a Terminal that is its head.

The majority of the modifications involve the creation or deletion of child nodes in Phrase or Dependent. Typically, properties are altered with options using the dot-notation syntax.

Type checking

Showing Structure

Getting information about a Constituent

As with any Python object, the value of a field can be obtained with the dotted notation such as .lemma or .terminal. Changing these values is possible, but it is not recommended due to the potential unintended consequences of direct modification. Instead, use the documented methods to modify these fields. The primary ways to retrieve values are:

Cloning a Constituent

Modifying an expression

Before the final realization, an expression can be modified by adding or removing parts of it. This useful iwhen not all arguments to a phrase are known before starting to build it. For example, its subject and verb can be determined in one part of a program, but its complements only specified later. Coordinated constituents are often built incrementally.

To account for this possibility, pyrealb allows adding a new Constituent to an existing Phrase or a new Dependent to another Dependent at a given position within its children. It is also possible to remove a Constituent, although this is most often used internally during the realization process.

As these methods return the modified constituent, calls can be chained as in the following examples. In practice, such calls are seldom encountered because it would have been simpler to create the structure by calling the factory functions. Most often adding or removing constituents is done incrementally in different places during the course of execution of the program.

These dynamic modifications explain why most realization decisions in pyrealb are made at the very last moment (i.e., when .realize() is called), rather than during the structure’s construction. Under the hood, .add(...) is used by pyrealb to build constituent expressions.

An Alternative to Structure Modifications

Since pyrealb expressions are Python objects, they can be included in a list or tuple and processed with standard Python functions. This list can then serve as an input for pyrealb factory functions, which flatten their list or tuple arguments before constructing the structure. This example shows how to build the pyrealb expression ’s2’ equivalent to ’s1’ incrementally.

n = [D("a"),N("apple").n("p")]
n.append(A("red"))
vp = (VP(V("eat"),NP(n)))
selems = [Pro("him").c("nom"),vp]
selems.insert(0,Adv("new").a(","))
s2 = S(selems)

Conclusion

This note provides a detailed explanation of how to dynamically alter pyrealb structures before they are realized. The pyrealb documentation briefly mentions this process, but I thought it deserved a more detailed explanation, including a few techniques that I developed over the years.