1use super::{Kind, TextModel, TokenClass, class_of, is_quote, sentence_spans};
14use quarb::{NodeId, Value};
15use std::rc::Rc;
16
17const V: u64 = 1 << 55;
19const TK: u64 = 1 << 54;
21const ORD: u32 = 20;
23const ORD_MASK: u64 = (1 << ORD) - 1;
24const BLOCK_MASK: u64 = (1 << 34) - 1;
25pub(super) const MAX_PER_BLOCK: usize = ORD_MASK as usize;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub(super) enum Virt {
32 Sentence {
33 block: usize,
34 k: u32,
35 },
36 Token {
37 block: usize,
38 k: u32,
39 },
40 Ann {
42 row: u32,
43 },
44}
45
46pub(super) fn virt(id: NodeId) -> Option<Virt> {
47 if id.0 & V == 0 {
48 return None;
49 }
50 if let Some(row) = super::ann::ann_row(id) {
51 return Some(Virt::Ann { row });
52 }
53 let block = ((id.0 >> ORD) & BLOCK_MASK) as usize;
54 let k = (id.0 & ORD_MASK) as u32;
55 Some(if id.0 & TK != 0 {
56 Virt::Token { block, k }
57 } else {
58 Virt::Sentence { block, k }
59 })
60}
61
62pub(super) fn sentence_id(block: usize, k: u32) -> NodeId {
63 NodeId(V | ((block as u64) << ORD) | k as u64)
64}
65
66pub(super) fn token_id(block: usize, k: u32) -> NodeId {
67 NodeId(V | TK | ((block as u64) << ORD) | k as u64)
68}
69
70#[derive(Clone, Copy, Debug)]
73pub(super) struct Tok {
74 pub at: u32,
75 pub end: u32,
76 pub sent: u32,
77 pub class: TokenClass,
78}
79
80pub(super) struct Tier {
84 pub sents: Vec<(u32, u32)>,
85 pub toks: Vec<Tok>,
86 pub first: Vec<u32>,
87}
88
89impl Tier {
90 pub(super) fn build(prose: &str, bonds: Option<&syndesmos::Syndesmos>) -> Tier {
95 Tier::over(prose, sentence_spans(prose, bonds))
96 }
97
98 pub(super) fn given(prose: &str, spans: &[(u32, u32)]) -> Tier {
101 Tier::over(
102 prose,
103 spans
104 .iter()
105 .map(|&(a, b)| (a as usize, b as usize))
106 .collect(),
107 )
108 }
109
110 fn over(prose: &str, mut spans: Vec<(usize, usize)>) -> Tier {
111 use unicode_segmentation::UnicodeSegmentation;
112 if spans.is_empty() {
113 spans.push((0, prose.len()));
114 }
115 let sents: Vec<(u32, u32)> = spans.iter().map(|&(a, b)| (a as u32, b as u32)).collect();
116 let mut toks = Vec::new();
117 let mut s = 0usize;
118 for (at, seg) in prose.split_word_bound_indices() {
119 if seg.chars().all(char::is_whitespace) {
120 continue;
121 }
122 while s + 1 < spans.len() && at >= spans[s].1 {
123 s += 1;
124 }
125 if toks.len() >= MAX_PER_BLOCK {
126 break;
127 }
128 toks.push(Tok {
129 at: at as u32,
130 end: (at + seg.len()) as u32,
131 sent: s as u32,
132 class: class_of(seg),
133 });
134 }
135 let mut first = vec![0u32; sents.len() + 1];
136 let mut k = 0usize;
137 for (i, t) in toks.iter().enumerate() {
138 while k <= t.sent as usize {
139 first[k] = i as u32;
140 k += 1;
141 }
142 }
143 while k <= sents.len() {
144 first[k] = toks.len() as u32;
145 k += 1;
146 }
147 Tier { sents, toks, first }
148 }
149
150 pub(super) fn tokens_of(&self, k: u32) -> std::ops::Range<u32> {
152 let k = k as usize;
153 if k + 1 >= self.first.len() {
154 return 0..0;
155 }
156 self.first[k]..self.first[k + 1]
157 }
158}
159
160impl TextModel {
161 pub(super) fn virtual_block(&self, block: usize) -> bool {
165 self.tiered
166 && block < self.nodes.len()
167 && (self.is_prose_block(block) || self.heading_text(block).is_some())
168 && !(self.notes_omitted && self.in_body(NodeId(block as u64)).is_some())
171 }
172
173 pub(super) fn heading_text(&self, block: usize) -> Option<&str> {
180 let n = self.nodes.get(block)?;
181 match n.kind {
182 Kind::Section => n.lemma.as_deref().filter(|l| !l.is_empty()),
183 Kind::Document => self.head.title.as_deref().filter(|t| !t.is_empty()),
184 _ => None,
185 }
186 }
187
188 pub(super) fn tier_text(&self, block: usize) -> &str {
191 self.heading_text(block)
192 .unwrap_or_else(|| &self.nodes[block].prose)
193 }
194
195 pub(super) fn tier_blocks(&self) -> Vec<usize> {
199 let mut headings: Vec<usize> = (0..self.nodes.len())
200 .filter(|&i| self.heading_text(i).is_some())
201 .collect();
202 headings.reverse();
203 let mut out = Vec::new();
204 for b in self.prose_blocks() {
205 let block = b.0 as usize;
206 while let Some(&h) = headings.last()
207 && h < block
208 {
209 out.push(h);
210 headings.pop();
211 }
212 out.push(block);
213 }
214 while let Some(h) = headings.pop() {
215 out.push(h);
216 }
217 out
218 }
219
220 pub(super) fn tier(&self, block: usize) -> Rc<Tier> {
223 if let Some(t) = self.tiers.borrow().get(&block) {
224 return t.clone();
225 }
226 let text = self.tier_text(block);
227 let t = Rc::new(match self.given.get(&block) {
228 Some(spans) => Tier::given(text, spans),
229 None if self.heading_text(block).is_some() => {
232 let lo = text.len() - text.trim_start().len();
233 let hi = text.trim_end().len();
234 Tier::given(text, &[(lo as u32, hi as u32)])
235 }
236 None => Tier::build(text, self.bonds.as_ref()),
237 });
238 self.tiers.borrow_mut().insert(block, t.clone());
239 t
240 }
241
242 pub fn tokenize_given(&mut self, sentences: &str) -> Result<(), String> {
250 self.mutated();
251 if self.is_tokenized() && !self.tiered {
252 return Err("the document is already tokenized".into());
253 }
254 let lines: Vec<(usize, &str)> = sentences
255 .lines()
256 .enumerate()
257 .map(|(i, l)| (i + 1, l.trim()))
258 .filter(|(_, l)| !l.is_empty())
259 .collect();
260 let mut given: std::collections::HashMap<usize, Vec<(u32, u32)>> = Default::default();
261 let mut pos = 0usize;
262 self.tiered = true;
263 self.bonds = None;
264 for block in self.tier_blocks() {
265 let text = self.tier_text(block).to_string();
266 let mut spans = Vec::new();
267 let mut cursor = 0usize;
268 loop {
269 while let Some(c) = text[cursor..].chars().next()
271 && c.is_whitespace()
272 {
273 cursor += c.len_utf8();
274 }
275 if cursor >= text.len() {
276 break;
277 }
278 let Some(&(lineno, line)) = lines.get(pos) else {
279 return Err(format!(
280 "the sentences file ends before {} is covered (at {:?})",
281 self.locator(NodeId(block as u64)),
282 text[cursor..].chars().take(24).collect::<String>()
283 ));
284 };
285 match match_line(&text, cursor, line) {
286 Some(end) => {
287 spans.push((cursor as u32, end as u32));
288 cursor = end;
289 pos += 1;
290 }
291 None => {
292 return Err(format!(
293 "sentences line {lineno} does not match the text at {} ({:?}): the text reads {:?}",
294 self.locator(NodeId(block as u64)),
295 line.chars().take(24).collect::<String>(),
296 text[cursor..].chars().take(24).collect::<String>()
297 ));
298 }
299 }
300 }
301 given.insert(block, spans);
302 }
303 if let Some(&(lineno, line)) = lines.get(pos) {
304 return Err(format!(
305 "sentences line {lineno} ({:?}) has no text left to match",
306 line.chars().take(24).collect::<String>()
307 ));
308 }
309 self.given = given;
310 self.tiers.borrow_mut().clear();
311 self.token_index = Default::default();
312 self.attribute_spans();
313 Ok(())
314 }
315
316 pub(super) fn tier_sentences(&self, block: usize) -> Vec<NodeId> {
318 let t = self.tier(block);
319 (0..t.sents.len() as u32)
320 .map(|k| sentence_id(block, k))
321 .collect()
322 }
323
324 pub(super) fn tier_words(&self) -> Vec<(NodeId, String, NodeId)> {
328 let mut out = Vec::new();
329 for block in self.tier_blocks() {
330 let t = self.tier(block);
331 for (k, tok) in t.toks.iter().enumerate() {
332 if tok.class != TokenClass::Word {
333 continue;
334 }
335 out.push((
336 token_id(block, k as u32),
337 self.form(block, tok).to_string(),
338 sentence_id(block, tok.sent),
339 ));
340 }
341 }
342 out
343 }
344
345 fn ords(&self) -> &std::collections::HashMap<usize, (u32, u32)> {
349 self.ords.get_or_init(|| {
350 let mut map = std::collections::HashMap::new();
351 let (mut n, mut s) = (0u32, 0u32);
352 for block in self.tier_blocks() {
353 map.insert(block, (n, s));
354 let t = self.tier(block);
355 n += t.toks.len() as u32;
356 s += t.sents.len() as u32;
357 }
358 map
359 })
360 }
361
362 pub(super) fn form(&self, block: usize, t: &Tok) -> &str {
363 &self.tier_text(block)[t.at as usize..t.end as usize]
364 }
365
366 fn sentence_text(&self, block: usize, k: u32) -> Option<String> {
367 let t = self.tier(block);
368 let (lo, hi) = *t.sents.get(k as usize)?;
369 self.tier_text(block)
370 .get(lo as usize..hi as usize)
371 .map(|s| s.trim().to_string())
372 }
373
374 pub(super) fn chars_to(&self, block: usize, byte: u32) -> i64 {
376 self.tier_text(block)
377 .get(..byte as usize)
378 .map_or(0, |s| s.chars().count() as i64)
379 }
380
381 fn tok(&self, block: usize, k: u32) -> Option<Tok> {
382 self.tier(block).toks.get(k as usize).copied()
383 }
384
385 pub(super) fn any_parent(&self, id: NodeId) -> Option<NodeId> {
387 match virt(id) {
388 Some(v) => self.v_parent(v),
389 None => self.nodes[id.0 as usize].parent,
390 }
391 }
392
393 pub(super) fn v_locator(&self, v: Virt) -> String {
397 let (block, k) = match v {
398 Virt::Sentence { block, k } | Virt::Token { block, k } => (block, k),
399 Virt::Ann { row } => return self.a_locator(row),
400 };
401 let t = self.tier(block);
402 let base = self.locator(NodeId(block as u64));
403 let seg = |name: &str, i: usize, n: usize| {
404 if n > 1 {
405 format!("{name}[{}]", i + 1)
406 } else {
407 name.to_string()
408 }
409 };
410 match v {
411 Virt::Ann { row } => self.a_locator(row),
412 Virt::Sentence { .. } => {
413 format!("{base}/{}", seg("sentence", k as usize, t.sents.len()))
414 }
415 Virt::Token { .. } => {
416 let Some(tok) = t.toks.get(k as usize) else {
417 return base;
418 };
419 let range = t.tokens_of(tok.sent);
420 format!(
421 "{base}/{}/{}",
422 seg("sentence", tok.sent as usize, t.sents.len()),
423 seg("token", (k - range.start) as usize, range.len())
424 )
425 }
426 }
427 }
428
429 pub(super) fn v_children(&self, v: Virt) -> Vec<NodeId> {
430 match v {
431 Virt::Ann { .. } => Vec::new(),
432 Virt::Sentence { block, k } => {
433 let t = self.tier(block);
434 t.tokens_of(k).map(|i| token_id(block, i)).collect()
435 }
436 Virt::Token { .. } => Vec::new(),
437 }
438 }
439
440 pub(super) fn v_parent(&self, v: Virt) -> Option<NodeId> {
441 match v {
442 Virt::Ann { row } => self.a_parent(row),
443 Virt::Sentence { block, .. } => Some(NodeId(block as u64)),
444 Virt::Token { block, k } => self.tok(block, k).map(|t| sentence_id(block, t.sent)),
445 }
446 }
447
448 pub(super) fn v_name(v: Virt) -> Option<String> {
449 Some(
450 match v {
451 Virt::Sentence { .. } => "sentence",
452 Virt::Token { .. } | Virt::Ann { .. } => "token",
453 }
454 .to_string(),
455 )
456 }
457
458 pub(super) fn v_traits(&self, v: Virt) -> Vec<String> {
459 match v {
460 Virt::Ann { row } => self.a_traits(row),
461 Virt::Sentence { .. } => vec!["derived".to_string()],
462 Virt::Token { block, k } => {
463 let Some(t) = self.tok(block, k) else {
464 return Vec::new();
465 };
466 let mut out = vec![t.class.name().to_string()];
467 if t.class == TokenClass::Punct && is_quote(self.form(block, &t)) {
468 out.push("quote".to_string());
469 }
470 out
471 }
472 }
473 }
474
475 pub(super) fn v_default_value(&self, v: Virt) -> Option<Value> {
476 match v {
477 Virt::Ann { row } => self.a_default_value(row),
478 Virt::Sentence { block, k } => self.sentence_text(block, k).map(Value::Str),
479 Virt::Token { block, k } => {
480 let t = self.tok(block, k)?;
481 Some(Value::Str(self.form(block, &t).to_string()))
482 }
483 }
484 }
485
486 fn v_cite(&self, v: Virt) -> Option<String> {
489 let (block, at) = match v {
490 Virt::Token { block, k } => (block, self.tok(block, k)?.at),
491 Virt::Sentence { block, k } => {
492 let t = self.tier(block);
493 let at = t
494 .tokens_of(k)
495 .next()
496 .and_then(|i| t.toks.get(i as usize))
497 .map(|t| t.at)
498 .unwrap_or(0);
499 (block, at)
500 }
501 Virt::Ann { row } => return self.a_cite_row(row),
502 };
503 self.cite_in_block(block, at)
504 }
505
506 pub(super) fn cite_in_block(&self, block: usize, at: u32) -> Option<String> {
510 let b = NodeId(block as u64);
511 let caches = self.caches();
512 if caches.cite_table.is_empty() {
513 return self.structural_cite(b);
514 }
515 let n = &self.nodes[block];
517 let body = if matches!(n.kind, Kind::Footnote | Kind::Endnote | Kind::Aside) && !n.deixis {
518 Some(b)
519 } else {
520 self.note_body_of(b)
521 };
522 if let Some(body) = body {
523 let callout = caches.callout_of_body.get(&body).copied();
524 return callout.and_then(|c| self.cite(c));
525 }
526 self.cite_at(b, at, b)
527 }
528
529 fn v_sentence_ordinal(&self, block: usize, k: u32) -> Option<i64> {
531 let (_, s) = *self.ords().get(&block)?;
532 Some(s as i64 + k as i64 + 1)
533 }
534
535 pub(super) fn v_property(&self, v: Virt, name: &str) -> Option<Value> {
536 if let Virt::Ann { row } = v {
537 return self.a_property(row, name);
538 }
539 if name == "cite" {
540 return self.v_cite(v).map(Value::Str);
541 }
542 match v {
543 Virt::Ann { row } => self.a_property(row, name),
544 Virt::Sentence { block, k } => match name {
545 "text" => self.sentence_text(block, k).map(Value::Str),
546 "taxis" | "ord" => self.v_sentence_ordinal(block, k).map(Value::Int),
547 _ => None,
548 },
549 Virt::Token { block, k } => {
550 let t = self.tok(block, k)?;
551 match name {
552 "text" => Some(Value::Str(self.form(block, &t).to_string())),
553 "lower" => Some(Value::Str(self.lower(self.form(block, &t)))),
554 "modern" => Some(Value::Str(self.modern(self.form(block, &t)))),
555 "class" => Some(Value::Str(t.class.name().to_string())),
556 "sentence" => self.sentence_text(block, t.sent).map(Value::Str),
557 "lemma" | "title" | "onym" | "hypograph" | "attribution" | "taxis" | "ord"
558 | "grammata" | "body" | "mention" | "entity" => None,
559 _ => self.v_token_field(v, block, &t, name),
560 }
561 }
562 }
563 }
564
565 fn v_token_field(&self, v: Virt, block: usize, t: &Tok, name: &str) -> Option<Value> {
569 let id = match v {
570 Virt::Token { block, k } => token_id(block, k),
571 Virt::Sentence { .. } | Virt::Ann { .. } => return None,
572 };
573 if let Some(f) = self.virt_fields.get(&id)
574 && let Some(val) = super::field_values(f, name)
575 {
576 return Some(val);
577 }
578 for s in self.covering_spans(block, t.at).into_iter().rev() {
579 if let Some(val) = super::field_values(&self.nodes[s.0 as usize].fields, name) {
580 return Some(val);
581 }
582 }
583 let mut at = Some(NodeId(block as u64));
584 while let Some(p) = at {
585 let n = &self.nodes[p.0 as usize];
586 if let Some(val) = super::field_values(&n.fields, name) {
587 return Some(val);
588 }
589 at = n.parent;
590 }
591 None
592 }
593
594 pub(super) fn covering_spans(&self, block: usize, at: u32) -> Vec<NodeId> {
597 let mut out = Vec::new();
598 let mut stack: Vec<NodeId> = self.nodes[block].children.iter().rev().copied().collect();
599 while let Some(c) = stack.pop() {
600 let n = &self.nodes[c.0 as usize];
601 if n.kind == Kind::Span {
602 if let Some((lo, hi)) = n.at
603 && lo <= at
604 && at < hi
605 {
606 out.push(c);
607 }
608 stack.extend(n.children.iter().rev().copied());
609 }
610 }
611 out
612 }
613
614 pub(super) fn v_metadata(&self, v: Virt, key: &str) -> Option<Value> {
615 match v {
616 Virt::Ann { row } => self.a_metadata(row, key),
617 Virt::Sentence { block, k } => match key {
621 "n" | "sentence" => self.v_sentence_ordinal(block, k).map(Value::Int),
622 "at" | "end" => {
626 let t = self.tier(block);
627 let (lo, hi) = *t.sents.get(k as usize)?;
628 let text = self.tier_text(block);
629 let span = text.get(lo as usize..hi as usize)?;
630 let lead = (span.len() - span.trim_start().len()) as u32;
631 let trail = (span.len() - span.trim_end().len()) as u32;
632 Some(Value::Int(self.chars_to(
633 block,
634 if key == "at" { lo + lead } else { hi - trail },
635 )))
636 }
637 _ => None,
638 },
639 Virt::Token { block, k } => {
640 let t = self.tok(block, k)?;
641 match key {
642 "n" => {
643 let (n, _) = *self.ords().get(&block)?;
644 Some(Value::Int(n as i64 + k as i64 + 1))
645 }
646 "sentence" => {
647 let (_, s) = *self.ords().get(&block)?;
648 Some(Value::Int(s as i64 + t.sent as i64 + 1))
649 }
650 "at" => Some(Value::Int(self.chars_to(block, t.at))),
651 "end" => Some(Value::Int(self.chars_to(block, t.end))),
652 _ => None,
653 }
654 }
655 }
656 }
657
658 pub(super) fn v_links(&self, v: Virt) -> Vec<(String, NodeId)> {
659 let id = match v {
660 Virt::Token { block, k } => token_id(block, k),
661 Virt::Ann { row } => return self.a_links(row),
662 Virt::Sentence { .. } => return Vec::new(),
663 };
664 let mut out = Vec::new();
665 if let Some(c) = self.virt_character.get(&id) {
666 out.push(("character".to_string(), *c));
667 }
668 if let Some(p) = self.virt_place.get(&id) {
669 out.push(("place".to_string(), *p));
670 }
671 out
672 }
673
674 pub(super) fn attribute_spans_virtual(&mut self) {
677 let spans: Vec<usize> = (0..self.nodes.len())
678 .filter(|&i| self.nodes[i].kind == Kind::Span && self.nodes[i].at.is_some())
679 .collect();
680 for span in spans {
681 let (lo, hi) = self.nodes[span].at.expect("filtered");
682 let mut block = span;
683 while self.nodes[block].kind == Kind::Span
684 && let Some(p) = self.nodes[block].parent
685 {
686 block = p.0 as usize;
687 }
688 if !self.virtual_block(block) {
689 continue;
690 }
691 let t = self.tier(block);
692 let covered: Vec<NodeId> = t
693 .toks
694 .iter()
695 .enumerate()
696 .filter(|(_, tok)| lo <= tok.at && tok.at < hi)
697 .map(|(k, _)| token_id(block, k as u32))
698 .collect();
699 self.nodes[span].span_tokens = covered;
700 }
701 }
702
703 pub(super) fn push_token_field(&mut self, id: NodeId, key: &str, value: String) {
706 match virt(id) {
707 Some(_) => {
708 let f = self.virt_fields.entry(id).or_default();
709 if !f.iter().any(|(k, _)| k == key) {
710 f.push((key.to_string(), value));
711 }
712 }
713 None => {
714 let f = &mut self.nodes[id.0 as usize].fields;
715 if !f.iter().any(|(k, _)| k == key) {
716 f.push((key.to_string(), value));
717 }
718 }
719 }
720 }
721
722 pub(super) fn has_token_field(&self, id: NodeId, key: &str) -> bool {
723 match virt(id) {
724 Some(_) => self
725 .virt_fields
726 .get(&id)
727 .is_some_and(|f| f.iter().any(|(k, _)| k == key)),
728 None => self.nodes[id.0 as usize]
729 .fields
730 .iter()
731 .any(|(k, _)| k == key),
732 }
733 }
734
735 pub(super) fn tier_index(&self) -> std::collections::HashMap<String, Vec<NodeId>> {
738 let mut map: std::collections::HashMap<String, Vec<NodeId>> = Default::default();
739 for block in self.tier_blocks() {
740 let t = self.tier(block);
741 for (k, tok) in t.toks.iter().enumerate() {
742 map.entry(self.lower(self.form(block, tok)))
743 .or_default()
744 .push(token_id(block, k as u32));
745 }
746 }
747 map
748 }
749}
750
751fn match_line(text: &str, cursor: usize, line: &str) -> Option<usize> {
755 let mut want = line.chars().filter(|c| !c.is_whitespace());
756 let mut end = cursor;
757 let mut rest = text[cursor..].char_indices();
758 let mut next = want.next();
759 while let Some(w) = next {
760 let (i, c) = rest.next()?;
761 if c.is_whitespace() {
762 continue;
763 }
764 if c != w {
765 return None;
766 }
767 end = cursor + i + c.len_utf8();
768 next = want.next();
769 }
770 Some(end)
771}