use std::collections::{BTreeMap, BTreeSet};
use crate::concept_records::{ConceptRecord, concept_record, concept_records};
use crate::grammar::GRAMMAR_CONCEPTS;
use crate::link_network::{Link, LinkId, LinkMetadata, LinkNetwork, LinkType};
use crate::lino_serialization::LinoSerializationError;
mod semantic_lexicon;
use semantic_lexicon::{
SemanticLexiconConcept, is_wikidata_qid, is_wordnet_cili_id, semantic_lexicon,
};
const EXTERNAL_IDENTIFIER_VOCABULARY_PREFIX: &str = "external-identifier:";
const FORMER_EXTERNAL_IDENTIFIER_VOCABULARY_PREFIX: &str = "external-id:";
pub const FORMER_CONCEPT_ID_VOCABULARY: &str = "meta-language";
pub const FORMER_CONCEPT_IDS: &[(&str, &str)] = &[
("grammar.repetition", "grammar.counted-repetition"),
("grammar.zero-or-more", "grammar.zero-or-more-repetition"),
("grammar.one-or-more", "grammar.one-or-more-repetition"),
("grammar.optional", "grammar.optional-expression"),
("grammar.non-terminal", "grammar.nonterminal"),
("grammar.char-class", "grammar.character-class"),
("grammar.char-range", "grammar.character-range"),
("grammar.any-char", "grammar.any-character"),
("grammar.empty", "grammar.empty-expression"),
("grammar.boolean", "grammar.boolean-value"),
("sequence", "sequential-composition"),
("strong", "strong-emphasis"),
("blockquote", "block-quote"),
];
#[must_use]
pub fn current_concept_id(id: &str) -> &str {
FORMER_CONCEPT_IDS
.iter()
.find(|(former, _)| *former == id)
.map_or(id, |(_, current)| current)
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ConceptOntologyImportReport {
concepts: usize,
assigned: usize,
renamed: usize,
alias_links: usize,
syntax_mappings: usize,
}
impl ConceptOntologyImportReport {
#[must_use]
pub const fn concepts(self) -> usize {
self.concepts
}
#[must_use]
pub const fn assigned(self) -> usize {
self.assigned
}
#[must_use]
pub const fn renamed(self) -> usize {
self.renamed
}
#[must_use]
pub const fn alias_links(self) -> usize {
self.alias_links
}
#[must_use]
pub const fn syntax_mappings(self) -> usize {
self.syntax_mappings
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ConceptRecordSeedReport {
concepts: usize,
links: usize,
}
impl ConceptRecordSeedReport {
#[must_use]
pub const fn concepts(self) -> usize {
self.concepts
}
#[must_use]
pub const fn links(self) -> usize {
self.links
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ConceptOntologySeedReport {
lexicon_concepts: usize,
structural_concepts: usize,
grammar_concepts: usize,
formatting_concepts: usize,
alias_links: usize,
syntax_mappings: usize,
}
impl ConceptOntologySeedReport {
const fn new(
lexicon_concepts: usize,
structural_concepts: usize,
grammar_concepts: usize,
formatting_concepts: usize,
alias_links: usize,
syntax_mappings: usize,
) -> Self {
Self {
lexicon_concepts,
structural_concepts,
grammar_concepts,
formatting_concepts,
alias_links,
syntax_mappings,
}
}
#[must_use]
pub const fn lexicon_concepts(self) -> usize {
self.lexicon_concepts
}
#[must_use]
pub const fn structural_concepts(self) -> usize {
self.structural_concepts
}
#[must_use]
pub const fn grammar_concepts(self) -> usize {
self.grammar_concepts
}
#[must_use]
pub const fn formatting_concepts(self) -> usize {
self.formatting_concepts
}
#[must_use]
pub const fn alias_links(self) -> usize {
self.alias_links
}
#[must_use]
pub const fn syntax_mappings(self) -> usize {
self.syntax_mappings
}
}
struct StructuralConcept {
id: &'static str,
definition: &'static str,
syntax: &'static [(&'static str, &'static str)],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StatehoodConceptIds {
pub proposition: LinkId,
pub subject: LinkId,
pub object: LinkId,
}
const STATEHOOD_PROPOSITION_SYNTAX: &[(&str, &str)] = &[
("English", "Hawaii is a state."),
("en", "Hawaii is a state."),
("Russian", "Гавайи это штат."),
("ru", "Гавайи это штат."),
];
const HAWAII_ENTITY_SYNTAX: &[(&str, &str)] = &[
("English", "Hawaii"),
("en", "Hawaii"),
("Russian", "Гавайи"),
("ru", "Гавайи"),
];
const UNITED_STATES_STATE_SYNTAX: &[(&str, &str)] = &[
("English", "state"),
("en", "state"),
("Russian", "штат"),
("ru", "штат"),
];
const STRUCTURAL_CONCEPTS: &[StructuralConcept] = &[
StructuralConcept {
id: "function",
definition: "Reusable computation with parameters and a result boundary.",
syntax: &[
("Rust", "fn"),
("Python", "def"),
("JavaScript", "function"),
("C", "function"),
("C++", "function"),
("C#", "method"),
("Java", "method"),
("Visual Basic", "Function"),
("R", "function"),
("sql-ansi", "CREATE FUNCTION"),
("Delphi/Object Pascal", "function"),
],
},
StructuralConcept {
id: "binding",
definition: "Association between a name and a value or computation.",
syntax: &[
("Rust", "let"),
("Python", "="),
("JavaScript", "let"),
("C", "="),
("C++", "="),
("C#", "="),
("Java", "="),
("Visual Basic", "Dim"),
("R", "<-"),
("sql-ansi", "AS"),
("Delphi/Object Pascal", ":="),
],
},
StructuralConcept {
id: "application",
definition: "Application of a callable expression to arguments.",
syntax: &[
("Rust", "call(...)"),
("Python", "call(...)"),
("JavaScript", "call(...)"),
("C", "call(...)"),
("C++", "call(...)"),
("C#", "call(...)"),
("Java", "call(...)"),
("Visual Basic", "Call"),
("R", "call(...)"),
("sql-ansi", "CALL"),
("Delphi/Object Pascal", "call(...)"),
],
},
StructuralConcept {
id: "sequential-composition",
definition: "Ordered execution or evaluation of multiple operations.",
syntax: &[
("Rust", ";"),
("Python", "newline"),
("JavaScript", ";"),
("C", ";"),
("C++", ";"),
("C#", ";"),
("Java", ";"),
("Visual Basic", "newline"),
("R", ";"),
("sql-ansi", ";"),
("Delphi/Object Pascal", "begin ... end"),
],
},
StructuralConcept {
id: "branch",
definition: "Conditional selection among alternative operations.",
syntax: &[
("Rust", "if"),
("Python", "if"),
("JavaScript", "if"),
("C", "if"),
("C++", "if"),
("C#", "if"),
("Java", "if"),
("Visual Basic", "If"),
("R", "if"),
("sql-ansi", "CASE"),
("Delphi/Object Pascal", "if"),
],
},
StructuralConcept {
id: "loop",
definition: "Repeated execution or evaluation over a condition or iterable.",
syntax: &[
("Rust", "loop"),
("Python", "for"),
("JavaScript", "for"),
("C", "for"),
("C++", "for"),
("C#", "for"),
("Java", "for"),
("Visual Basic", "For"),
("R", "for"),
("sql-ansi", "WHILE"),
("Delphi/Object Pascal", "for"),
],
},
StructuralConcept {
id: "parameter",
definition: "Named input accepted by a function abstraction.",
syntax: &[
("Rust", "parameter"),
("Python", "parameter"),
("JavaScript", "parameter"),
("C", "parameter"),
("C++", "parameter"),
("C#", "parameter"),
("Java", "parameter"),
("Visual Basic", "parameter"),
("R", "parameter"),
("sql-ansi", "parameter"),
("Delphi/Object Pascal", "parameter"),
],
},
StructuralConcept {
id: "argument",
definition: "Concrete input supplied to a function application.",
syntax: &[
("Rust", "argument"),
("Python", "argument"),
("JavaScript", "argument"),
("C", "argument"),
("C++", "argument"),
("C#", "argument"),
("Java", "argument"),
("Visual Basic", "argument"),
("R", "argument"),
("sql-ansi", "argument"),
("Delphi/Object Pascal", "argument"),
],
},
StructuralConcept {
id: "return",
definition: "Transfer of a function result to its caller.",
syntax: &[
("Rust", "return"),
("Python", "return"),
("JavaScript", "return"),
("C", "return"),
("C++", "return"),
("C#", "return"),
("Java", "return"),
("Visual Basic", "Return"),
("R", "return"),
("sql-ansi", "RETURN"),
("Delphi/Object Pascal", "Result"),
],
},
StructuralConcept {
id: "assignment",
definition: "Update that stores a value into a named location.",
syntax: &[
("Rust", "="),
("Python", "="),
("JavaScript", "="),
("C", "="),
("C++", "="),
("C#", "="),
("Java", "="),
("Visual Basic", "="),
("R", "<-"),
("sql-ansi", "="),
("Delphi/Object Pascal", ":="),
],
},
];
impl LinkNetwork {
pub(crate) fn seed_statehood_worked_example(&mut self) -> StatehoodConceptIds {
let proposition = self.insert_typed_point(
"statehood",
LinkType::Concept,
Some("Statehood proposition connecting Hawaii (Q782) to U.S. state (Q35657)."),
);
let subject = self.insert_typed_point(
"Q782",
LinkType::Concept,
Some("Wikidata Q782; Hawaii; state of the United States."),
);
let object = self.insert_typed_point(
"Q35657",
LinkType::Concept,
Some("Wikidata Q35657; state of the United States."),
);
for (language, syntax) in STATEHOOD_PROPOSITION_SYNTAX {
self.insert_concept_syntax_mapping(proposition, "statehood", language, syntax, true);
}
for (language, syntax) in HAWAII_ENTITY_SYNTAX {
self.insert_concept_syntax_mapping(subject, "Q782", language, syntax, true);
}
for (language, syntax) in UNITED_STATES_STATE_SYNTAX {
self.insert_concept_syntax_mapping(object, "Q35657", language, syntax, true);
}
StatehoodConceptIds {
proposition,
subject,
object,
}
}
#[must_use]
pub fn seed_common_concept_ontology(&mut self) -> ConceptOntologySeedReport {
let lexicon = semantic_lexicon();
let mut alias_links = 0;
let mut syntax_mappings = 0;
for concept in &lexicon.concepts {
let definition = concept.definition();
let concept_link = self.intern_concept(concept.id(), Some(&definition));
alias_links += self.insert_external_aliases(concept_link, concept);
for entry in concept.syntax_entries() {
self.insert_concept_syntax_mapping(
concept_link,
concept.id(),
entry.language,
entry.syntax,
entry.canonical,
);
syntax_mappings += 1;
}
}
let mut structural_concepts = BTreeSet::new();
for concept in STRUCTURAL_CONCEPTS {
structural_concepts.insert(concept.id);
let concept_link = self.intern_concept(concept.id, Some(concept.definition));
alias_links += self.insert_former_concept_ids(concept_link, concept.id);
for (language, syntax) in concept.syntax {
self.insert_concept_syntax_mapping(
concept_link,
concept.id,
language,
syntax,
true,
);
syntax_mappings += 1;
}
}
let grammar_concepts = self.seed_grammar_concept_ontology();
syntax_mappings += GRAMMAR_CONCEPTS
.iter()
.map(|concept| concept.syntax.len())
.sum::<usize>();
let formatting = self.seed_document_formatting_concepts();
syntax_mappings += formatting.syntax_mappings();
let statehood = self.seed_statehood_worked_example();
for (concept_link, external_id) in
[(statehood.subject, "Q782"), (statehood.object, "Q35657")]
{
let (_alias, inserted) =
self.insert_concept_alias_link(concept_link, "Wikidata", external_id);
if inserted {
alias_links += 1;
}
}
syntax_mappings += STATEHOOD_PROPOSITION_SYNTAX.len()
+ HAWAII_ENTITY_SYNTAX.len()
+ UNITED_STATES_STATE_SYNTAX.len();
ConceptOntologySeedReport::new(
lexicon.concept_count,
structural_concepts.len(),
grammar_concepts,
formatting.concepts(),
alias_links,
syntax_mappings,
)
}
pub fn intern_concept(&mut self, exact_id: &str, definition: Option<&str>) -> LinkId {
self.insert_typed_point(exact_id, LinkType::Concept, definition)
}
pub fn insert_concept_expression(
&mut self,
concept: &str,
language: &str,
expression: &str,
) -> LinkId {
let concept_link = self.find_term(concept).unwrap_or_else(|| {
self.intern_concept(
concept,
Some("A language-free concept shared by exact interlingual id."),
)
});
self.insert_concept_syntax_mapping(concept_link, concept, language, expression, true)
}
pub fn insert_concept_mapping(
&mut self,
concept: &str,
language: &str,
syntax: &str,
) -> LinkId {
self.insert_concept_expression(concept, language, syntax)
}
pub fn insert_concept_alias(
&mut self,
concept_link: LinkId,
vocabulary: &str,
external_id: &str,
) -> LinkId {
self.insert_concept_alias_link(concept_link, vocabulary, external_id)
.0
}
pub fn import_concept_ontology_lino(
&mut self,
text: &str,
) -> Result<ConceptOntologyImportReport, LinoSerializationError> {
let source = Self::from_lino(text)?;
Ok(self.import_concept_ontology(&source))
}
pub fn import_concept_ontology(&mut self, source: &Self) -> ConceptOntologyImportReport {
let mut concept_links: BTreeMap<LinkId, (LinkId, String)> = BTreeMap::new();
let mut report = ConceptOntologyImportReport::default();
for link in source.links() {
if link.metadata().link_type() != Some(LinkType::Concept) {
continue;
}
let Some(term) = link.metadata().term() else {
continue;
};
let id = current_concept_id(term);
let concept_link = if let Some(record) = concept_record(id) {
report.assigned += 1;
self.insert_concept_record(record)
} else {
self.intern_concept(id, link.metadata().definition())
};
if id != term {
self.insert_concept_alias_link(concept_link, FORMER_CONCEPT_ID_VOCABULARY, term);
report.renamed += 1;
}
concept_links.insert(link.id(), (concept_link, id.to_string()));
report.concepts += 1;
}
for link in source.links() {
if link.metadata().link_type() != Some(LinkType::Semantic) {
continue;
}
let [source_concept, source_context] = link.references() else {
continue;
};
let Some((target_concept, concept_id)) = concept_links.get(source_concept) else {
continue;
};
let Some(context) = source.link(*source_context) else {
continue;
};
let Some(term) = link.metadata().term() else {
continue;
};
match context.metadata().link_type() {
Some(LinkType::Language) => {
if let Some(language) = link
.metadata()
.language()
.or_else(|| context.metadata().term())
{
self.insert_concept_syntax_mapping(
*target_concept,
concept_id,
language,
term,
false,
);
report.syntax_mappings += 1;
}
}
Some(LinkType::Type) => {
let vocabulary = link.metadata().language().or_else(|| {
context
.metadata()
.term()
.and_then(external_vocabulary_from_term)
});
if let Some(vocabulary) = vocabulary {
self.insert_concept_alias(*target_concept, vocabulary, term);
report.alias_links += 1;
}
}
_ => {}
}
}
report
}
pub fn insert_concept_record(&mut self, record: &ConceptRecord) -> LinkId {
let concept_link = self.intern_concept(&record.id, Some(&record.definition));
self.insert_concept_syntax_mapping(concept_link, &record.id, "en", &record.phrase, false);
for alias in &record.source_aliases {
self.insert_concept_alias_link(concept_link, &alias.source, &alias.name);
}
for former in &record.former_names {
self.insert_concept_alias_link(concept_link, FORMER_CONCEPT_ID_VOCABULARY, former);
}
concept_link
}
pub fn seed_concept_records(&mut self) -> ConceptRecordSeedReport {
let before = self.len();
for record in concept_records() {
self.insert_concept_record(record);
}
ConceptRecordSeedReport {
concepts: concept_records().len(),
links: self.len() - before,
}
}
fn insert_external_aliases(
&mut self,
concept_link: LinkId,
concept: &SemanticLexiconConcept,
) -> usize {
let mut aliases = BTreeSet::new();
if let Some(vocabulary) = external_vocabulary_for_id(concept.id()) {
aliases.insert((vocabulary, concept.id()));
}
if let Some(entity_id) = concept.entity_id.as_deref()
&& let Some(vocabulary) = external_vocabulary_for_id(entity_id)
{
aliases.insert((vocabulary, entity_id));
}
aliases
.into_iter()
.filter(|(vocabulary, external_id)| {
let (_alias, inserted) =
self.insert_concept_alias_link(concept_link, vocabulary, external_id);
inserted
})
.count()
}
pub(crate) fn insert_former_concept_ids(
&mut self,
concept_link: LinkId,
concept_id: &str,
) -> usize {
FORMER_CONCEPT_IDS
.iter()
.filter(|(former, current)| {
*current == concept_id
&& self
.insert_concept_alias_link(
concept_link,
FORMER_CONCEPT_ID_VOCABULARY,
former,
)
.1
})
.count()
}
fn insert_concept_alias_link(
&mut self,
concept_link: LinkId,
vocabulary: &str,
external_id: &str,
) -> (LinkId, bool) {
let vocabulary_term = external_vocabulary_term(vocabulary);
let vocabulary_link = self.insert_typed_point(
&vocabulary_term,
LinkType::Type,
Some("External concept identifier vocabulary."),
);
if let Some(existing) =
self.find_concept_alias(concept_link, vocabulary_link, vocabulary, external_id)
{
return (existing, false);
}
(
self.insert_link(
[concept_link, vocabulary_link],
LinkMetadata::new()
.with_link_type(LinkType::Semantic)
.with_named(true)
.with_term(external_id)
.with_language(vocabulary),
),
true,
)
}
pub(crate) fn insert_concept_syntax_mapping(
&mut self,
concept_link: LinkId,
concept: &str,
language: &str,
syntax: &str,
update_reconstruction: bool,
) -> LinkId {
let language_link = self.insert_typed_point(language, LinkType::Language, None);
self.cache_concept_syntax(concept, language, syntax, update_reconstruction);
if let Some(existing) =
self.find_concept_syntax_mapping(concept_link, language_link, syntax, language)
{
return existing;
}
self.insert_link(
[concept_link, language_link],
LinkMetadata::new()
.with_link_type(LinkType::Semantic)
.with_named(true)
.with_term(syntax)
.with_language(language),
)
}
fn find_concept_syntax_mapping(
&self,
concept_link: LinkId,
language_link: LinkId,
syntax: &str,
language: &str,
) -> Option<LinkId> {
self.links()
.find(|link| {
let references = link.references();
link.metadata().link_type() == Some(LinkType::Semantic)
&& references.len() == 2
&& references[0] == concept_link
&& references[1] == language_link
&& link.metadata().term() == Some(syntax)
&& link.metadata().language() == Some(language)
})
.map(Link::id)
}
fn find_concept_alias(
&self,
concept_link: LinkId,
vocabulary_link: LinkId,
vocabulary: &str,
external_id: &str,
) -> Option<LinkId> {
self.links()
.find(|link| {
let references = link.references();
link.metadata().link_type() == Some(LinkType::Semantic)
&& references.len() == 2
&& references[0] == concept_link
&& references[1] == vocabulary_link
&& link.metadata().term() == Some(external_id)
&& link.metadata().language() == Some(vocabulary)
})
.map(Link::id)
}
}
fn external_vocabulary_for_id(value: &str) -> Option<&'static str> {
if is_wikidata_qid(value) {
Some("Wikidata")
} else if is_wordnet_cili_id(value) {
Some("WordNet CILI")
} else {
None
}
}
fn external_vocabulary_term(vocabulary: &str) -> String {
format!("{EXTERNAL_IDENTIFIER_VOCABULARY_PREFIX}{vocabulary}")
}
fn external_vocabulary_from_term(term: &str) -> Option<&str> {
term.strip_prefix(EXTERNAL_IDENTIFIER_VOCABULARY_PREFIX)
.or_else(|| term.strip_prefix(FORMER_EXTERNAL_IDENTIFIER_VOCABULARY_PREFIX))
}