A subtle bug in A.pretty()

2019-04-12

Christiaan Erwich, Gyusang Jin and Cody Kingham spotted weird behaviour in displaying certain verses.

Look here in Psalm 18:49 :

ps18:49

The dashed circles enclose the words that are in this verse, the other words belong to the previous part.

When displaying the sentence atom, which started in the previous part, Text-Fabric forgot to remember to cutoff this sentence atom at the verse boundary.

And why was that?

The root cause is in a few lines in the generic TF display library, not in the BHSA TF-app.

      getBoundary(api, n) if d.condenseType is None else

Here TF computes the boundary slots of the node in question. The thing is, if there is a condense type active, we should not take the boundary of the node itself, but its container of the condense type.

However, in our case we have the situation that d.condenseType appeared to be verse. Yet, there is no condensing, because d.condensed is False.

So this part of the condition was wrong and should be:

      getBoundary(api, n) if not d.condensed or not d.condenseType else

See the bug on Github

This indeed fixed the bug, as you see below.

In [1]:
%load_ext autoreload
%autoreload 2
In [2]:
from tf.applib.helpers import dm
from tf.app import use

Note that we are developing/debugging code. We do not want to download code/data from github, and we want to look in our own github clone of the TF app.

Hence 'bhsa:clone' and checkout='local'.

In [3]:
A = use('bhsa:clone', checkout='local', hoist=globals())
Using TF-app in /Users/dirk/github/annotation/app-bhsa/code:
	repo clone offline under ~/github (local github)
Using data in /Users/dirk/text-fabric-data/etcbc/bhsa/tf/c:
	rv1.6 offline under ~/text-fabric-data (local release)
Using data in /Users/dirk/text-fabric-data/etcbc/phono/tf/c:
	r1.2 offline under ~/text-fabric-data (local release)
Using data in /Users/dirk/text-fabric-data/etcbc/parallels/tf/c:
	r1.2 offline under ~/text-fabric-data (local release)

Inspection

Before we knew what was the matter we made a lens to have a detailed view of our data:

We gathered all word nodes of Ps 18:47 - 49 and made a table of the objects they are contained in.

In [4]:
v0 = T.nodeFromSection(('Psalms', 18, 47))
v1 = T.nodeFromSection(('Psalms', 18, 49))
w0 = L.d(v0, otype='word')[0]
w1 = L.d(v1, otype='word')[-1]
allWords = range(w0, w1 + 1)

We are only interested in certain kind of objects.

In [5]:
skipTypes = {'book', 'chapter', 'half_verse', 'lex', 'subphrase', 'word'}
nodeTypes = [x[0] for x in C.levels.data if x[0] not in skipTypes]
nodeTypes
Out[5]:
['verse',
 'sentence',
 'sentence_atom',
 'clause',
 'clause_atom',
 'phrase',
 'phrase_atom']

We represent the nodes of different types in slightly different ways.

In [6]:
repNode = dict(
  verse=lambda n: '{} {}:{}'.format(*T.sectionFromNode(n)),
  sentence=lambda n: F.number.v(n),
  sentence_atom=lambda n: F.number.v(n),
  clause=lambda n: F.number.v(n),
  clause_atom=lambda n: F.number.v(n),
  phrase=lambda n: F.number.v(n),
  phrase_atom=lambda n: F.number.v(n),
)

And we make a markdown table.

In [7]:
rows = []
rows.append(' | '.join(nodeTypes))
rows.append(' | '.join(['---'] * len(nodeTypes)))


for w in allWords:
  parents = {F.otype.v(p): repNode[F.otype.v(p)](p) for p in L.u(w) if F.otype.v(p) in nodeTypes}
  rows.append(' | '.join(str(parents.get(nType, 'x')) for nType in nodeTypes))
  
dm('\n'.join(rows))
verse sentence sentence_atom clause clause_atom phrase phrase_atom
Psalms 18:47 93 511 1 761 1 1818
Psalms 18:47 93 511 1 761 2 1819
Psalms 18:47 94 512 1 762 1 1820
Psalms 18:47 94 512 1 762 2 1821
Psalms 18:47 94 512 1 762 3 1822
Psalms 18:47 95 513 1 763 1 1823
Psalms 18:47 95 513 1 763 2 1824
Psalms 18:47 95 513 1 763 3 1825
Psalms 18:47 95 513 1 763 3 1825
Psalms 18:48 93 514 2 764 1 1826
Psalms 18:48 93 514 2 764 1 1826
Psalms 18:48 93 514 3 765 1 1827
Psalms 18:48 93 514 3 765 2 1828
Psalms 18:48 93 514 3 765 3 1829
Psalms 18:48 93 514 3 765 4 1830
Psalms 18:48 93 514 4 766 1 1831
Psalms 18:48 93 514 4 766 2 1832
Psalms 18:48 93 514 4 766 3 1833
Psalms 18:48 93 514 4 766 4 1834
Psalms 18:49 93 514 5 767 1 1835
Psalms 18:49 93 514 5 767 2 1836
Psalms 18:49 93 514 5 767 2 1836
Psalms 18:49 96 515 1 768 1 1837
Psalms 18:49 96 515 1 768 1 1837
Psalms 18:49 96 515 1 768 1 1837
Psalms 18:49 96 515 1 768 2 1838
Psalms 18:49 97 516 1 769 1 1839
Psalms 18:49 97 516 1 769 1 1839
Psalms 18:49 97 516 1 769 1 1839
Psalms 18:49 97 516 1 769 2 1840

This gave us the clue that we had a bunch of objects that crossed the verse boundary.

After the fix

We show the verse after the fix.

In [8]:
n = T.nodeFromSection(('Psalms', 18, 49))
In [9]:
A.plain(n)
Psalms 18:49מְפַלְּטִ֗י מֵאֹ֫יְבָ֥י אַ֣ף מִן־קָ֭מַי תְּרֹומְמֵ֑נִי מֵאִ֥ישׁ חָ֝מָ֗ס תַּצִּילֵֽנִי׃
In [10]:
A.pretty(n)
sentence 93|514
clause Coor Ptcp
phrase PtcO VP
verb escape piel ptca
phrase Cmpl PP
prep from
subs be hostile qal ptca
sentence 96|515
clause xYq0
phrase Cmpl PP
prep from
subs arise qal ptca
phrase PreO VP
sentence 97|516
clause xYq0

Other example:

In [11]:
s1 = 1222671
A.show(((s1,),))

result 1

sentence 11|4672
clause Attr xQt0
phrase Rela CP
conj <relative>
phrase Adju NP
phrase Pred VP
verb draw qal perf
clause Coor ZQt0
phrase Pred VP
verb be dry qal perf
sentence 11|4672
clause Attr xQtX
phrase Rela CP
conj <relative>
phrase Nega NegP
phrase Pred VP
verb be full piel perf
phrase Subj NP
subs harvest qal ptca
clause Ellp
phrase Conj CP
conj and
phrase Subj NP
subs gather ears piel ptca
In [ ]: