1use std::borrow::Cow;
2
3use either::Either;
4use flams_ontology::{
5 content::{
6 declarations::{
7 morphisms::Morphism,
8 structures::{Extension, MathStructure},
9 symbols::{ArgSpec, AssocType, Symbol},
10 OpenDeclaration,
11 },
12 modules::{NestedModule, OpenModule},
13 terms::{Term, Var},
14 },
15 languages::Language,
16 narration::{
17 notations::Notation,
18 paragraphs::{LogicalParagraph, ParagraphFormatting, ParagraphKind},
19 problems::{
20 ChoiceBlock, FillInSol, FillInSolOption, GradingNote, Problem, SolutionData, Solutions,
21 },
22 sections::{Section, SectionLevel},
23 variables::Variable,
24 DocumentElement,
25 },
26 uris::{
27 ContentURI, DocumentElementURI, DocumentURI, ModuleURI, Name, SymbolURI, URIOrRefTrait,
28 },
29};
30use smallvec::SmallVec;
31use terms::{OpenArg, PreVar, VarOrSym};
32
33#[cfg(feature = "rdf")]
34use flams_ontology::triple;
35
36use crate::{
37 errors::FTMLError,
38 prelude::{FTMLExtractor, FTMLNode, NotationState, ParagraphState, ProblemState},
39 rules::FTMLElements,
40};
41
42pub mod terms;
43#[allow(clippy::large_enum_variant)]
44#[derive(Debug, Clone)]
45pub enum OpenFTMLElement {
46 Invisible,
47 SetSectionLevel(SectionLevel),
48 ImportModule(ModuleURI),
49 UseModule(ModuleURI),
50 Slide(DocumentElementURI),
51 SlideNumber,
52 ProofBody,
53 Module {
54 uri: ModuleURI,
55 meta: Option<ModuleURI>,
56 signature: Option<Language>,
57 },
58 MathStructure {
59 uri: SymbolURI,
60 macroname: Option<Box<str>>,
61 },
62 Morphism {
63 uri: SymbolURI,
64 domain: ModuleURI,
65 total: bool,
66 },
67 Assign(SymbolURI),
68 Section {
69 lvl: SectionLevel,
70 uri: DocumentElementURI,
71 },
72 SkipSection,
73 Paragraph {
74 uri: DocumentElementURI,
75 kind: ParagraphKind,
76 formatting: ParagraphFormatting,
77 styles: Box<[Name]>,
78 },
79 Problem {
80 uri: DocumentElementURI,
81 styles: Box<[Name]>,
82 autogradable: bool,
83 points: Option<f32>,
84 sub_problem: bool,
85 },
86 Doctitle,
87 Title,
88 ProofTitle,
89 SubproofTitle,
90 Symdecl {
91 uri: SymbolURI,
92 arity: ArgSpec,
93 macroname: Option<Box<str>>,
94 role: Box<[Box<str>]>,
95 assoctype: Option<AssocType>,
96 reordering: Option<Box<str>>,
97 },
98 Vardecl {
99 uri: DocumentElementURI,
100 arity: ArgSpec,
101 bind: bool,
102 macroname: Option<Box<str>>,
103 role: Box<[Box<str>]>,
104 assoctype: Option<AssocType>,
105 reordering: Option<Box<str>>,
106 is_seq: bool,
107 },
108 Notation {
109 id: Box<str>,
110 symbol: VarOrSym,
111 precedence: isize,
112 argprecs: SmallVec<isize, 9>,
113 },
114 NotationComp,
115 NotationOpComp,
116 Definiendum(SymbolURI),
117 Type,
118 Conclusion {
119 uri: SymbolURI,
120 in_term: bool,
121 },
122 Definiens {
123 uri: Option<SymbolURI>,
124 in_term: bool,
125 },
126 OpenTerm {
127 term: terms::OpenTerm,
128 is_top: bool,
129 },
130 ClosedTerm(Term),
131 MMTRule(Box<str>),
132 ArgSep,
133 ArgMap,
134 ArgMapSep,
135 HeadTerm,
136 ProblemHint,
137 ProblemSolution(Option<Box<str>>),
138 ProblemGradingNote,
139 AnswerClass,
140 AnswerClassFeedback,
141 ChoiceBlock {
142 multiple: bool,
143 inline: bool,
144 },
145 ProblemChoice,
146 ProblemChoiceVerdict,
147 ProblemChoiceFeedback,
148 Fillinsol(Option<f32>),
149 FillinsolCase,
150
151 Inputref {
152 uri: DocumentURI,
153 id: Box<str>,
154 },
155 IfInputref(bool),
156
157 Comp,
158 MainComp,
159 DefComp,
160 Arg(OpenArg),
161}
162
163impl OpenFTMLElement {
164 #[allow(clippy::too_many_lines)]
165 #[allow(clippy::cognitive_complexity)]
166 pub(crate) fn close<E: FTMLExtractor, N: FTMLNode>(
167 self,
168 previous: &mut FTMLElements,
169 next: &mut FTMLElements,
170 extractor: &mut E,
171 node: &N,
172 ) -> Option<Self> {
173 match self {
175 Self::Invisible => {
176 if !extractor.in_term() && !extractor.in_notation() {
177 node.delete();
178 }
179 }
180 Self::SetSectionLevel(lvl) => {
181 extractor.add_document_element(DocumentElement::SetSectionLevel(lvl))
182 }
183 Self::ImportModule(uri) => Self::close_importmodule(extractor, uri),
184 Self::UseModule(uri) => Self::close_usemodule(extractor, uri),
185 Self::Module {
186 uri,
187 meta,
188 signature,
189 } => Self::close_module(extractor, node, uri, meta, signature),
190 Self::MathStructure { uri, macroname } => {
191 Self::close_structure(extractor, node, uri, macroname)
192 }
193 Self::Morphism { uri, domain, total } => {
194 Self::close_morphism(extractor, node, uri, domain, total)
195 }
196
197 Self::Assign(_sym) => {
198 if extractor.close_complex_term().is_some() {}
199 }
201 Self::SkipSection => {
202 if let Some((_, _, children)) = extractor.close_section() {
203 extractor.add_document_element(DocumentElement::SkipSection(children));
204 } else {
205 extractor.add_error(FTMLError::NotInNarrative);
206 };
207 }
208
209 Self::Section { lvl, uri } => Self::close_section(extractor, node, lvl, uri),
210 Self::Slide(uri) => {
211 if let Some(children) = extractor.close_slide() {
212 extractor.add_document_element(DocumentElement::Slide {
213 range: node.range(),
214 uri,
215 children,
216 });
217 } else {
218 extractor.add_error(FTMLError::NotInNarrative);
219 };
220 }
221 Self::Paragraph {
222 kind,
223 formatting,
224 styles,
225 uri,
226 } => Self::close_paragraph(extractor, node, kind, formatting, styles, uri),
227 Self::Problem {
228 uri,
229 styles,
230 autogradable,
231 points,
232 sub_problem,
233 } => Self::close_problem(
234 extractor,
235 node,
236 uri,
237 styles,
238 autogradable,
239 points,
240 sub_problem,
241 ),
242
243 Self::Doctitle => {
244 extractor.set_document_title(node.inner_string().into_boxed_str());
245 }
246
247 Self::Title => {
248 if extractor.add_title(node.inner_range()).is_err() {
249 extractor.add_error(FTMLError::NotInNarrative);
250 }
251 }
252 Self::Symdecl {
253 uri,
254 arity,
255 macroname,
256 role,
257 assoctype,
258 reordering,
259 } => Self::close_symdecl(
260 extractor, uri, arity, macroname, role, assoctype, reordering,
261 ),
262 Self::Vardecl {
263 uri,
264 arity,
265 bind,
266 macroname,
267 role,
268 assoctype,
269 reordering,
270 is_seq,
271 } => Self::close_vardecl(
272 extractor, uri, bind, arity, macroname, role, assoctype, reordering, is_seq,
273 ),
274 Self::Notation {
275 id,
276 symbol,
277 precedence,
278 argprecs,
279 } => Self::close_notation(extractor, id, symbol, precedence, argprecs),
280 Self::NotationComp => {
281 if let Some(n) = node.as_notation() {
282 if extractor.add_notation(n).is_err() {
283 extractor.add_error(FTMLError::NotInNarrative);
284 }
285 } else {
286 extractor.add_error(FTMLError::NotInNarrative);
287 }
288 }
289 Self::NotationOpComp => {
290 if let Some(n) = node.as_op_notation() {
291 if extractor.add_op_notation(n).is_err() {
292 extractor.add_error(FTMLError::NotInNarrative);
293 }
294 } else {
295 extractor.add_error(FTMLError::NotInNarrative);
296 }
297 }
298 Self::Type => {
299 extractor.set_in_term(false);
300 let tm = Self::as_term(next, node);
301 if extractor.add_type(tm).is_err() {
302 extractor.add_error(FTMLError::NotInContent);
303 }
304 }
305 Self::Conclusion { uri, in_term } => {
306 extractor.set_in_term(in_term);
307 let tm = Self::as_term(next, node);
308 if extractor.add_term(Some(uri), tm).is_err() {
309 extractor.add_error(FTMLError::NotInContent);
310 }
311 }
312 Self::Definiens { uri, in_term } => {
313 extractor.set_in_term(in_term);
314 let tm = Self::as_term(next, node);
315 if extractor.add_term(uri, tm).is_err() {
316 extractor.add_error(FTMLError::NotInContent);
317 }
318 }
319 Self::OpenTerm { term, is_top: true } => {
320 let term = term.close(extractor);
321 let uri = match extractor.get_narrative_uri()
322 & &*extractor.new_id(Cow::Borrowed("term"))
323 {
324 Ok(uri) => uri,
325 Err(_) => {
326 extractor
327 .add_error(FTMLError::InvalidURI("(should be impossible)".to_string()));
328 return None;
329 }
330 };
331 extractor.set_in_term(false);
332 if !matches!(term, Term::OMID { .. } | Term::OMV { .. }) {
333 extractor.add_document_element(DocumentElement::TopTerm { uri, term });
334 }
335 }
336 Self::OpenTerm {
337 term,
338 is_top: false,
339 } => {
340 let term = term.close(extractor);
341 return Some(Self::ClosedTerm(term));
342 }
343 Self::MMTRule(_id) => {
344 let _ = extractor.close_args();
345 }
347 Self::ArgSep => {
348 return Some(Self::ArgSep);
349 }
350 Self::ArgMap => {
351 return Some(Self::ArgMap);
352 }
353 Self::ArgMapSep => {
354 return Some(Self::ArgMapSep);
355 }
356 Self::Arg(a) => {
357 if extractor.in_notation() {
358 return Some(self);
359 }
360 let t = node.as_term();
361 let pos = match a.index {
362 Either::Left(u) => (u, None),
363 Either::Right((a, b)) => (a, Some(b)),
364 };
365 if extractor.add_arg(pos, t, a.mode).is_err() {
366 extractor.add_error(FTMLError::IncompleteArgs(3));
368 }
369 }
370 Self::HeadTerm => {
371 let tm = node.as_term();
372 if extractor.add_term(None, tm).is_err() {
373 extractor.add_error(FTMLError::IncompleteArgs(4));
375 }
376 }
377
378 Self::Comp | Self::MainComp if extractor.in_notation() => {
379 return Some(self);
380 }
381 Self::DefComp if extractor.in_notation() => {
382 return Some(Self::Comp);
383 }
384 Self::ClosedTerm(_) => return Some(self),
385
386 Self::Inputref { uri, id } => {
387 let top = extractor.get_narrative_uri();
388 #[cfg(feature = "rdf")]
389 if E::RDF {
390 extractor.add_triples([triple!(<(top.to_iri())> dc:HAS_PART <(uri.to_iri())>)]);
391 }
392 extractor.add_document_element(DocumentElement::DocumentReference {
393 id: match top & &*id {
394 Ok(id) => id,
395 Err(_) => {
396 extractor.add_error(FTMLError::InvalidURI(format!("5: {id}")));
397 return None;
398 }
399 },
400 range: node.range(),
401 target: uri,
402 });
403 previous.elems.retain(|e| !matches!(e, Self::Invisible));
404 }
405 Self::ProblemHint => {
406 if extractor
407 .with_problem(|ex| ex.hints.push(node.inner_range()))
408 .is_none()
409 {
410 extractor.add_error(FTMLError::NotInProblem("a"));
411 }
412 }
413 Self::ProblemSolution(id) => {
414 let range = node.range();
415 extractor.with_problem(|ex| {
417 ex.children.retain(|e| match e {
418 DocumentElement::Paragraph(LogicalParagraph { range: rng, .. })
419 | DocumentElement::Slide { range: rng, .. }
420 | DocumentElement::Problem(Problem { range: rng, .. }) => {
421 rng.end < range.start || rng.start > range.end
422 }
423
424 _ => true,
425 });
426 });
427 let s = node.inner_string().into_boxed_str();
428 node.delete_children();
429 if extractor
430 .with_problem(|ex| {
431 ex.solutions.push(SolutionData::Solution {
432 html: s,
433 answer_class: id,
434 });
435 })
436 .is_none()
437 {
438 extractor.add_error(FTMLError::NotInProblem("b"));
439 }
440 }
441 Self::ProblemGradingNote => {
442 let s = node.inner_string().into_boxed_str();
443 node.delete_children();
444 if let Some(gnote) = extractor.close_gnote() {
445 let gnote = GradingNote {
446 answer_classes: gnote.answer_classes,
447 html: s,
448 };
449 let r = extractor.add_resource(&gnote);
450 if extractor.with_problem(|ex| ex.gnotes.push(r)).is_none() {
451 extractor.add_error(FTMLError::NotInProblem("c"));
452 }
453 } else {
454 extractor.add_error(FTMLError::NotInProblem("d"));
455 }
456 }
457 Self::ChoiceBlock { .. } => {
458 let range = node.range();
459 if let Some(cb) = extractor.close_choice_block() {
460 if extractor
461 .with_problem(|ex| {
462 ex.solutions.push(SolutionData::ChoiceBlock(ChoiceBlock {
463 multiple: cb.multiple,
464 inline: cb.inline,
465 range,
466 styles: cb.styles,
467 choices: cb.choices,
468 }))
469 })
470 .is_none()
471 {
472 extractor.add_error(FTMLError::NotInProblem("e"));
473 }
474 } else {
475 extractor.add_error(FTMLError::NotInProblem("f"));
476 }
477 }
478 Self::AnswerClassFeedback => {
479 let s = node.string().into_boxed_str();
480 node.delete();
481 if !extractor
482 .with_problem(|ex| {
483 if let Some(n) = &mut ex.gnote {
484 if let Some(ac) = n.answer_classes.last_mut() {
485 ac.feedback = s;
486 true
487 } else {
488 false
489 }
490 } else {
491 false
492 }
493 })
494 .unwrap_or_default()
495 {
496 extractor.add_error(FTMLError::NotInProblem("g"));
497 }
498 }
499 Self::ProblemChoiceVerdict => {
500 let s = node.string().into_boxed_str();
501 node.delete();
502 if !extractor
503 .with_problem(|ex| {
504 if let Some(n) = &mut ex.choice_block {
505 if let Some(ac) = n.choices.last_mut() {
506 ac.verdict = s;
507 true
508 } else {
509 false
510 }
511 } else {
512 false
513 }
514 })
515 .unwrap_or_default()
516 {
517 extractor.add_error(FTMLError::NotInProblem("h"));
518 }
519 }
520 Self::ProblemChoiceFeedback => {
521 let s = node.string().into_boxed_str();
522 node.delete();
523 if !extractor
524 .with_problem(|ex| {
525 if let Some(n) = &mut ex.choice_block {
526 if let Some(ac) = n.choices.last_mut() {
527 ac.feedback = s;
528 true
529 } else {
530 false
531 }
532 } else {
533 false
534 }
535 })
536 .unwrap_or_default()
537 {
538 extractor.add_error(FTMLError::NotInProblem("i"));
539 }
540 }
541 Self::Fillinsol(width) => {
542 if !extractor
543 .with_problem(|ex| {
544 if let Some(n) = std::mem::take(&mut ex.fillinsol) {
545 ex.solutions.push(SolutionData::FillInSol(FillInSol {
546 width,
547 opts: n.cases,
548 }));
549 true
550 } else {
551 false
552 }
553 })
554 .unwrap_or_default()
555 {
556 extractor.add_error(FTMLError::NotInProblem("j"));
557 }
558 node.delete_children();
559 }
560 Self::FillinsolCase => {
561 let s = node.inner_string().into_boxed_str();
562 node.delete();
563 if !extractor
564 .with_problem(|ex| {
565 if let Some(n) = &mut ex.fillinsol {
566 n.cases.last_mut().is_some_and(|n| match n {
567 FillInSolOption::Exact { feedback, .. }
568 | FillInSolOption::NumericalRange { feedback, .. }
569 | FillInSolOption::Regex { feedback, .. } => {
570 *feedback = s;
571 true
572 }
573 })
574 } else {
575 false
576 }
577 })
578 .unwrap_or_default()
579 {
580 extractor.add_error(FTMLError::NotInProblem("k"));
581 }
582 }
583 Self::IfInputref(_)
584 | Self::Definiendum(_)
585 | Self::Comp
586 | Self::MainComp
587 | Self::DefComp
588 | Self::AnswerClass
589 | Self::ProblemChoice
590 | Self::SlideNumber
591 | Self::ProofBody
592 | Self::ProofTitle
593 | Self::SubproofTitle => (),
594 }
595 None
596 }
597
598 fn as_term<N: FTMLNode>(next: &mut FTMLElements, node: &N) -> Term {
599 if let Some(i) = next.iter().position(|e| matches!(e, Self::ClosedTerm(_))) {
600 let Self::ClosedTerm(t) = next.elems.remove(i) else {
601 unreachable!()
602 };
603 return t;
604 }
605 node.as_term()
606 }
607
608 fn close_importmodule<E: FTMLExtractor>(extractor: &mut E, uri: ModuleURI) {
609 #[cfg(feature = "rdf")]
610 if E::RDF {
611 if let Some(m) = extractor.get_content_iri() {
612 extractor.add_triples([triple!(<(m)> ulo:IMPORTS <(uri.to_iri())>)]);
613 }
614 }
615 extractor.add_document_element(DocumentElement::ImportModule(uri.clone()));
616 if extractor
617 .add_content_element(OpenDeclaration::Import(uri))
618 .is_err()
619 {
620 extractor.add_error(FTMLError::NotInContent);
621 }
622 }
623
624 fn close_usemodule<E: FTMLExtractor>(extractor: &mut E, uri: ModuleURI) {
625 #[cfg(feature = "rdf")]
626 if E::RDF {
627 extractor.add_triples([
628 triple!(<(extractor.get_document_iri())> dc:REQUIRES <(uri.to_iri())>),
629 ]);
630 }
631 extractor.add_document_element(DocumentElement::UseModule(uri));
632 }
633
634 fn close_module<E: FTMLExtractor, N: FTMLNode>(
635 extractor: &mut E,
636 node: &N,
637 uri: ModuleURI,
638 meta: Option<ModuleURI>,
639 signature: Option<Language>,
640 ) {
641 let Some((_, narrative)) = extractor.close_narrative() else {
642 extractor.add_error(FTMLError::NotInNarrative);
643 return;
644 };
645 let Some((_, mut content)) = extractor.close_content() else {
646 extractor.add_error(FTMLError::NotInContent);
647 return;
648 };
649
650 #[cfg(feature = "rdf")]
651 if E::RDF {
652 let iri = uri.to_iri();
653 extractor.add_triples([
654 triple!(<(iri.clone())> : ulo:THEORY),
655 triple!(<(extractor.get_document_iri())> ulo:CONTAINS <(iri)>),
656 ]);
657 }
658
659 extractor.add_document_element(DocumentElement::Module {
660 range: node.range(),
661 module: uri.clone(),
662 children: narrative,
663 });
664
665 if uri.name().is_simple() {
666 extractor.add_module(OpenModule {
667 uri,
668 meta,
669 signature,
670 elements: content,
671 });
672 } else {
673 let Some(sym) = uri.into_symbol() else {
675 unreachable!()
676 };
677 #[cfg(feature = "rdf")]
678 if E::RDF {
679 if let Some(m) = extractor.get_content_iri() {
680 extractor.add_triples([triple!(<(m)> ulo:CONTAINS <(sym.to_iri())>)]);
681 }
682 }
683 if extractor
684 .add_content_element(OpenDeclaration::NestedModule(NestedModule {
685 uri: sym,
686 elements: std::mem::take(&mut content),
687 }))
688 .is_err()
689 {
690 extractor.add_error(FTMLError::NotInContent);
691 }
692 }
693 }
694
695 fn close_structure<E: FTMLExtractor, N: FTMLNode>(
696 extractor: &mut E,
697 node: &N,
698 uri: SymbolURI,
699 macroname: Option<Box<str>>,
700 ) {
701 let Some((_, narrative)) = extractor.close_narrative() else {
702 extractor.add_error(FTMLError::NotInNarrative);
703 return;
704 };
705 let Some((_, content)) = extractor.close_content() else {
706 extractor.add_error(FTMLError::NotInContent);
707 return;
708 };
709
710 #[cfg(feature = "rdf")]
711 if E::RDF {
712 if let Some(cont) = extractor.get_content_iri() {
713 let iri = uri.to_iri();
714 extractor.add_triples([
715 triple!(<(iri.clone())> : ulo:STRUCTURE),
716 triple!(<(cont)> ulo:CONTAINS <(iri)>),
717 ]);
718 }
719 }
720
721 if uri.name().last_name().as_ref().starts_with("EXTSTRUCT") {
722 let Some(target) = content.iter().find_map(|d| match d {
723 OpenDeclaration::Import(uri)
724 if !uri.name().last_name().as_ref().starts_with("EXTSTRUCT") =>
725 {
726 Some(uri)
727 }
728 _ => None,
729 }) else {
730 extractor.add_error(FTMLError::NotInContent);
731 return;
732 };
733 let Some(target) = target.clone().into_symbol() else {
734 extractor.add_error(FTMLError::NotInContent);
735 return;
736 };
737
738 #[cfg(feature = "rdf")]
739 if E::RDF {
740 extractor.add_triples([triple!(<(uri.to_iri())> ulo:EXTENDS <(target.to_iri())>)]);
741 }
742 extractor.add_document_element(DocumentElement::Extension {
743 range: node.range(),
744 extension: uri.clone(),
745 target: target.clone(),
746 children: narrative,
747 });
748 if extractor
749 .add_content_element(OpenDeclaration::Extension(Extension {
750 uri,
751 elements: content,
752 target,
753 }))
754 .is_err()
755 {
756 extractor.add_error(FTMLError::NotInContent);
757 }
758 } else {
759 extractor.add_document_element(DocumentElement::MathStructure {
760 range: node.range(),
761 structure: uri.clone(),
762 children: narrative,
763 });
764 if extractor
765 .add_content_element(OpenDeclaration::MathStructure(MathStructure {
766 uri,
767 elements: content,
768 macroname,
769 }))
770 .is_err()
771 {
772 extractor.add_error(FTMLError::NotInContent);
773 }
774 }
775 }
776
777 fn close_morphism<E: FTMLExtractor, N: FTMLNode>(
778 extractor: &mut E,
779 node: &N,
780 uri: SymbolURI,
781 domain: ModuleURI,
782 total: bool,
783 ) {
784 let Some((_, narrative)) = extractor.close_narrative() else {
785 extractor.add_error(FTMLError::NotInNarrative);
786 return;
787 };
788 let Some((_, content)) = extractor.close_content() else {
789 extractor.add_error(FTMLError::NotInContent);
790 return;
791 };
792
793 #[cfg(feature = "rdf")]
794 if E::RDF {
795 if let Some(cont) = extractor.get_content_iri() {
796 let iri = uri.to_iri(); extractor.add_triples([
798 triple!(<(iri.clone())> : ulo:MORPHISM),
799 triple!(<(iri.clone())> rdfs:DOMAIN <(domain.to_iri())>),
800 triple!(<(cont)> ulo:CONTAINS <(iri)>),
801 ]);
802 }
803 }
804
805 extractor.add_document_element(DocumentElement::Morphism {
806 range: node.range(),
807 morphism: uri.clone(),
808 children: narrative,
809 });
810 if extractor
811 .add_content_element(OpenDeclaration::Morphism(Morphism {
812 uri,
813 domain,
814 total,
815 elements: content,
816 }))
817 .is_err()
818 {
819 extractor.add_error(FTMLError::NotInContent);
820 }
821 }
822
823 fn close_section<E: FTMLExtractor, N: FTMLNode>(
824 extractor: &mut E,
825 node: &N,
826 lvl: SectionLevel,
827 uri: DocumentElementURI,
828 ) {
829 let Some((_, title, children)) = extractor.close_section() else {
830 extractor.add_error(FTMLError::NotInNarrative);
831 return;
832 };
833
834 #[cfg(feature = "rdf")]
835 if E::RDF {
836 let doc = extractor.get_document_iri();
837 let iri = uri.to_iri();
838 extractor.add_triples([
839 triple!(<(iri.clone())> : ulo:SECTION),
840 triple!(<(doc)> ulo:CONTAINS <(iri)>),
841 ]);
842 }
843
844 extractor.add_document_element(DocumentElement::Section(Section {
845 range: node.range(),
846 level: lvl,
847 title,
848 uri,
849 children,
850 }));
851 }
852
853 fn close_paragraph<E: FTMLExtractor, N: FTMLNode>(
854 extractor: &mut E,
855 node: &N,
856 kind: ParagraphKind,
857 formatting: ParagraphFormatting,
858 styles: Box<[Name]>,
859 uri: DocumentElementURI,
860 ) {
861 let Some(ParagraphState {
862 children,
863 fors,
864 title,
865 ..
866 }) = extractor.close_paragraph()
867 else {
868 extractor.add_error(FTMLError::NotInParagraph);
869 return;
870 };
871
872 #[cfg(feature = "rdf")]
873 if E::RDF {
874 let doc = extractor.get_document_iri();
875 let iri = uri.to_iri();
876 if kind.is_definition_like(&styles) {
877 for (f, _) in fors.iter() {
878 extractor.add_triples([triple!(<(iri.clone())> ulo:DEFINES <(f.to_iri())>)]);
879 }
880 } else if kind == ParagraphKind::Example {
881 for (f, _) in fors.iter() {
882 extractor
883 .add_triples([triple!(<(iri.clone())> ulo:EXAMPLE_FOR <(f.to_iri())>)]);
884 }
885 }
886 extractor.add_triples([
887 triple!(<(iri.clone())> : <(kind.rdf_type().into_owned())>),
888 triple!(<(doc)> ulo:CONTAINS <(iri)>),
889 ]);
890 }
891
892 extractor.add_document_element(DocumentElement::Paragraph(LogicalParagraph {
893 range: node.range(),
894 kind,
895 formatting,
896 styles,
897 fors,
898 uri,
899 children,
900 title,
901 }));
902 }
903
904 fn close_problem<E: FTMLExtractor, N: FTMLNode>(
905 extractor: &mut E,
906 node: &N,
907 uri: DocumentElementURI,
908 styles: Box<[Name]>,
909 autogradable: bool,
910 points: Option<f32>,
911 sub_problem: bool,
912 ) {
913 let Some(ProblemState {
914 solutions,
915 hints,
916 notes,
917 gnotes,
918 title,
919 children,
920 preconditions,
921 objectives,
922 ..
923 }) = extractor.close_problem()
924 else {
925 extractor.add_error(FTMLError::NotInProblem("l"));
926 return;
927 };
928
929 #[cfg(feature = "rdf")]
930 if E::RDF {
931 let doc = extractor.get_document_iri();
932 let iri = uri.to_iri();
933 for (d, s) in &preconditions {
934 let b = flams_ontology::rdf::BlankNode::default();
935 extractor.add_triples([
936 triple!(<(iri.clone())> ulo:PRECONDITION (b.clone())!),
937 triple!((b.clone())! ulo:COGDIM <(d.to_iri().into_owned())>),
938 triple!((b)! ulo:POSYMBOL <(s.to_iri())>),
939 ]);
940 }
941 for (d, s) in &objectives {
942 let b = flams_ontology::rdf::BlankNode::default();
943 extractor.add_triples([
944 triple!(<(iri.clone())> ulo:OBJECTIVE (b.clone())!),
945 triple!((b.clone())! ulo:COGDIM <(d.to_iri().into_owned())>),
946 triple!((b)! ulo:POSYMBOL <(s.to_iri())>),
947 ]);
948 }
949
950 extractor.add_triples([
951 if sub_problem {
952 triple!(<(iri.clone())> : ulo:SUBPROBLEM)
953 } else {
954 triple!(<(iri.clone())> : ulo:PROBLEM)
955 },
956 triple!(<(doc)> ulo:CONTAINS <(iri)>),
957 ]);
958 }
959 let solutions =
960 extractor.add_resource(&Solutions::from_solutions(solutions.into_boxed_slice()));
961
962 extractor.add_document_element(DocumentElement::Problem(Problem {
963 range: node.range(),
964 uri,
965 styles,
966 autogradable,
967 points,
968 sub_problem,
969 gnotes,
970 solutions,
971 hints,
972 notes,
973 title,
974 children,
975 preconditions,
976 objectives,
977 }));
978 }
979
980 fn close_symdecl<E: FTMLExtractor>(
981 extractor: &mut E,
982 uri: SymbolURI,
983 arity: ArgSpec,
984 macroname: Option<Box<str>>,
985 role: Box<[Box<str>]>,
986 assoctype: Option<AssocType>,
987 reordering: Option<Box<str>>,
988 ) {
989 let Some((tp, df)) = extractor.close_decl() else {
990 extractor.add_error(FTMLError::NotInContent);
991 return;
992 };
993 #[cfg(feature = "rdf")]
994 if E::RDF {
995 if let Some(m) = extractor.get_content_iri() {
996 let iri = uri.to_iri();
997 extractor.add_triples([
998 triple!(<(iri.clone())> : ulo:DECLARATION),
999 triple!(<(m)> ulo:DECLARES <(iri)>),
1000 ]);
1001 }
1002 }
1003 extractor.add_document_element(DocumentElement::SymbolDeclaration(uri.clone()));
1004 if extractor
1005 .add_content_element(OpenDeclaration::Symbol(Symbol {
1006 uri,
1007 arity,
1008 macroname,
1009 role,
1010 tp,
1011 df,
1012 assoctype,
1013 reordering,
1014 }))
1015 .is_err()
1016 {
1017 extractor.add_error(FTMLError::NotInContent);
1018 }
1019 }
1020
1021 #[allow(clippy::too_many_arguments)]
1022 fn close_vardecl<E: FTMLExtractor>(
1023 extractor: &mut E,
1024 uri: DocumentElementURI,
1025 bind: bool,
1026 arity: ArgSpec,
1027 macroname: Option<Box<str>>,
1028 role: Box<[Box<str>]>,
1029 assoctype: Option<AssocType>,
1030 reordering: Option<Box<str>>,
1031 is_seq: bool,
1032 ) {
1033 let Some((tp, df)) = extractor.close_decl() else {
1034 extractor.add_error(FTMLError::NotInContent);
1035 return;
1036 };
1037
1038 #[cfg(feature = "rdf")]
1039 if E::RDF {
1040 let iri = uri.to_iri();
1041 extractor.add_triples([
1042 triple!(<(iri.clone())> : ulo:VARIABLE),
1043 triple!(<(extractor.get_document_iri())> ulo:DECLARES <(iri)>),
1044 ]);
1045 }
1046
1047 extractor.add_document_element(DocumentElement::Variable(Variable {
1048 uri,
1049 arity,
1050 macroname,
1051 bind,
1052 role,
1053 tp,
1054 df,
1055 assoctype,
1056 reordering,
1057 is_seq,
1058 }));
1059 }
1060
1061 fn close_notation<E: FTMLExtractor>(
1062 extractor: &mut E,
1063 id: Box<str>,
1064 symbol: VarOrSym,
1065 precedence: isize,
1066 argprecs: SmallVec<isize, 9>,
1067 ) {
1068 let Some(NotationState {
1069 attribute_index,
1070 inner_index,
1071 is_text,
1072 components,
1073 op,
1074 }) = extractor.close_notation()
1075 else {
1076 extractor.add_error(FTMLError::NotInNarrative);
1077 return;
1078 };
1079 if attribute_index == 0 {
1080 extractor.add_error(FTMLError::NotInNarrative);
1081 return;
1082 }
1083 let uri = match extractor.get_narrative_uri() & &*id {
1084 Ok(uri) => uri,
1085 Err(_) => {
1086 extractor.add_error(FTMLError::InvalidURI(format!("6: {id}")));
1087 return;
1088 }
1089 };
1090 let notation = extractor.add_resource(&Notation {
1091 attribute_index,
1092 is_text,
1093 inner_index,
1094 components,
1095 op,
1096 precedence,
1097 id,
1098 argprecs,
1099 });
1100 match symbol {
1101 VarOrSym::S(ContentURI::Symbol(symbol)) => {
1102 #[cfg(feature = "rdf")]
1103 if E::RDF {
1104 let iri = uri.to_iri();
1105 extractor.add_triples([
1106 triple!(<(iri.clone())> : ulo:NOTATION),
1107 triple!(<(iri.clone())> ulo:NOTATION_FOR <(symbol.to_iri())>),
1108 triple!(<(extractor.get_document_iri())> ulo:DECLARES <(iri)>),
1109 ]);
1110 }
1111 extractor.add_document_element(DocumentElement::Notation {
1112 symbol,
1113 id: uri,
1114 notation,
1115 });
1116 }
1117 VarOrSym::S(_) => unreachable!(),
1118 VarOrSym::V(PreVar::Resolved(variable)) => {
1119 extractor.add_document_element(DocumentElement::VariableNotation {
1120 variable,
1121 id: uri,
1122 notation,
1123 })
1124 }
1125 VarOrSym::V(PreVar::Unresolved(name)) => match extractor.resolve_variable_name(name) {
1126 Var::Name(name) => extractor.add_error(FTMLError::UnresolvedVariable(name)),
1127 Var::Ref { declaration, .. } => {
1128 extractor.add_document_element(DocumentElement::VariableNotation {
1129 variable: declaration,
1130 id: uri,
1131 notation,
1132 })
1133 }
1134 },
1135 }
1136 }
1137}