use crate::ast::Node;
use std::collections::{HashMap, HashSet};
use texform_argspec::parse_arg_specs;
use texform_interface::syntax_node::ContentMode;
use texform_knowledge::builtin::{BuiltinPackage, PackageName};
use crate::parse::{CommandItem, ContextItem, DelimiterControlItem, EnvironmentItem};
pub use texform_argspec::{
ArgForm, ArgSpec, ArgSpecParseError, DelimiterToken, ParsedArgSpec, ValueKind,
};
use texform_knowledge::specs::CharacterAttributes;
pub use texform_knowledge::specs::{
ActiveCharacterRecord, ActiveCommandRecord, ActiveDelimiterRecord, ActiveEnvironmentRecord,
AllowedMode, BuiltinCharacterRecord, BuiltinCommandRecord, BuiltinDelimiterRecord,
BuiltinEnvironmentRecord, CommandKind,
};
#[cfg(test)]
use texform_knowledge::specs::{
CharacterSpec, CommandSpec, DelimiterSpec, EnvironmentSpec, PackageSpecs,
};
const RUNTIME_PACKAGE_NAME: &str = "runtime";
#[cfg(test)]
const UNKNOWN_PACKAGE_NAME: &str = "unknown";
const DEFAULT_PACKAGE_NAMES: [&str; 6] = [
"base",
"ams",
"physics",
"textmacros",
"bboldx",
"boldsymbol",
];
const PHYSICS_COMMAND_MERGE_DENYLIST: [&str; 3] = ["Pr", "det", "exp"];
pub fn default_package_names() -> &'static [&'static str] {
&DEFAULT_PACKAGE_NAMES
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PackageLoadError {
UnknownPackage { name: String },
}
impl std::fmt::Display for PackageLoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PackageLoadError::UnknownPackage { name } => {
write!(f, "unknown package: {name}")
}
}
}
}
impl std::error::Error for PackageLoadError {}
#[derive(Debug, Clone)]
pub struct KnowledgeBase {
commands: Vec<ActiveCommandRecord>,
command_idx_by_name: HashMap<&'static str, usize>,
characters: Vec<ActiveCharacterRecord>,
character_idx_by_name: HashMap<String, usize>,
delimiters: Vec<ActiveDelimiterRecord>,
delimiter_idx_by_key: HashMap<(String, bool), usize>,
character_command_views: Vec<ActiveCommandRecord>,
active_command_idx_by_name: HashMap<String, ActiveCommandSource>,
suppressed_command_names: HashSet<String>,
envs: Vec<ActiveEnvironmentRecord>,
env_idx_by_name: HashMap<&'static str, usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ActiveCommandSource {
Explicit(usize),
Character(usize),
}
impl KnowledgeBase {
fn new() -> Self {
Self {
commands: Vec::new(),
command_idx_by_name: HashMap::new(),
characters: Vec::new(),
character_idx_by_name: HashMap::new(),
delimiters: Vec::new(),
delimiter_idx_by_key: HashMap::new(),
character_command_views: Vec::new(),
active_command_idx_by_name: HashMap::new(),
suppressed_command_names: HashSet::new(),
envs: Vec::new(),
env_idx_by_name: HashMap::new(),
}
}
pub fn empty() -> Self {
Self::new()
}
pub fn build_from_packages(packages: &[&str]) -> Self {
Self::try_build_from_packages(packages).unwrap_or_else(|error| panic!("{error}"))
}
pub fn try_build_from_packages(packages: &[&str]) -> Result<Self, PackageLoadError> {
let mut kb = KnowledgeBase::new();
let to_load = canonical_package_import_order(packages);
import_package_names(&mut kb, to_load.as_slice())?;
Ok(kb)
}
pub fn try_build_from_packages_for_mode(
packages: &[&str],
target_mode: ContentMode,
) -> Result<Self, PackageLoadError> {
let mut kb = KnowledgeBase::new();
let to_load = canonical_package_import_order(packages);
import_package_names_for_mode(&mut kb, to_load.as_slice(), target_mode)?;
Ok(kb)
}
pub fn lookup_command(&self, name: &str) -> Option<&ActiveCommandRecord> {
if self.suppressed_command_names.contains(name) {
return None;
}
match self.active_command_idx_by_name.get(name).copied()? {
ActiveCommandSource::Explicit(idx) => Some(&self.commands[idx]),
ActiveCommandSource::Character(idx) => Some(&self.character_command_views[idx]),
}
}
pub fn lookup_explicit_command(&self, name: &str) -> Option<&ActiveCommandRecord> {
self.command_idx_by_name
.get(name)
.copied()
.map(|idx| &self.commands[idx])
}
pub fn lookup_character(&self, name: &str) -> Option<&ActiveCharacterRecord> {
self.character_idx_by_name
.get(name)
.copied()
.map(|idx| &self.characters[idx])
}
pub fn lookup_env(&self, name: &str) -> Option<&ActiveEnvironmentRecord> {
self.env_idx_by_name
.get(name)
.copied()
.map(|idx| &self.envs[idx])
}
pub fn lookup_delimiter(
&self,
name: &str,
is_control_sequence: bool,
) -> Option<&ActiveDelimiterRecord> {
self.delimiter_idx_by_key
.get(&(name.to_string(), is_control_sequence))
.copied()
.map(|idx| &self.delimiters[idx])
}
pub fn is_delimiter_control(&self, name: &str) -> bool {
self.lookup_delimiter(name, true).is_some()
}
pub fn lookup_delimiter_control(&self, name: &str) -> Option<&'static str> {
self.lookup_delimiter(name, true).map(|record| record.name)
}
pub fn insert_item(&mut self, item: impl Into<ContextItem>) -> Result<(), ArgSpecParseError> {
match item.into() {
ContextItem::Command(item) => self.insert_command(item),
ContextItem::Environment(item) => self.insert_environment(item),
ContextItem::DelimiterControl(item) => {
self.insert_delimiter_control(item);
Ok(())
}
}
}
pub(crate) fn insert_command(&mut self, item: CommandItem) -> Result<(), ArgSpecParseError> {
let meta = command_item_into_meta(item, vec![RUNTIME_PACKAGE_NAME.to_string()])?;
let name = meta.name;
let idx = self.append_command_meta(meta);
self.suppressed_command_names.remove(name);
self.set_active_command_source(name, ActiveCommandSource::Explicit(idx));
Ok(())
}
pub fn remove_item(&mut self, item: impl Into<ContextItem>) -> bool {
match item.into() {
ContextItem::Command(item) => self.remove_command_by_name(item.name.as_str()),
ContextItem::Environment(item) => self.remove_environment_by_name(item.name.as_str()),
ContextItem::DelimiterControl(item) => {
self.remove_delimiter_by_key(item.name.as_str(), true)
}
}
}
pub fn insert_environment(&mut self, item: EnvironmentItem) -> Result<(), ArgSpecParseError> {
let meta = environment_item_into_meta(item, vec![RUNTIME_PACKAGE_NAME.to_string()])?;
self.append_env_meta(meta);
Ok(())
}
pub fn insert_delimiter_control(&mut self, item: DelimiterControlItem) {
if self.lookup_delimiter(item.name.as_str(), true).is_some() {
return;
}
let name: &'static str = Box::leak(item.name.into_boxed_str());
self.upsert_delimiter_meta(ActiveDelimiterRecord {
name,
is_control_sequence: true,
allowed_mode: AllowedMode::Both,
unicode_value: String::new(),
attributes: CharacterAttributes::default(),
package: RUNTIME_PACKAGE_NAME.to_string(),
});
}
pub(crate) fn remove_command_by_name(&mut self, name: &str) -> bool {
let explicit_removed = self.command_idx_by_name.remove(name).is_some();
let active_removed = self.active_command_idx_by_name.remove(name).is_some();
if explicit_removed || active_removed {
self.suppressed_command_names.insert(name.to_string());
return true;
}
false
}
pub(crate) fn remove_environment_by_name(&mut self, name: &str) -> bool {
self.env_idx_by_name.remove(name).is_some()
}
fn remove_delimiter_by_key(&mut self, name: &str, is_control_sequence: bool) -> bool {
self.delimiter_idx_by_key
.remove(&(name.to_string(), is_control_sequence))
.is_some()
}
fn set_active_command_source(&mut self, name: impl Into<String>, source: ActiveCommandSource) {
self.active_command_idx_by_name.insert(name.into(), source);
}
fn append_command_meta(&mut self, meta: ActiveCommandRecord) -> usize {
let idx = self.commands.len();
let name = meta.name;
self.commands.push(meta);
self.command_idx_by_name.insert(name, idx);
idx
}
fn append_env_meta(&mut self, meta: ActiveEnvironmentRecord) {
let idx = self.envs.len();
let name = meta.name;
self.envs.push(meta);
self.env_idx_by_name.insert(name, idx);
}
fn upsert_character_meta(&mut self, meta: ActiveCharacterRecord) -> usize {
let idx = self.characters.len();
let name = meta.name.clone();
self.characters.push(meta);
self.character_idx_by_name.insert(name, idx);
idx
}
fn upsert_character_command_view(&mut self, meta: ActiveCommandRecord) -> usize {
let idx = self.character_command_views.len();
self.character_command_views.push(meta);
idx
}
fn upsert_delimiter_meta(&mut self, meta: ActiveDelimiterRecord) -> usize {
let idx = self.delimiters.len();
let key = (meta.name.to_string(), meta.is_control_sequence);
self.delimiters.push(meta);
self.delimiter_idx_by_key.insert(key, idx);
idx
}
#[cfg(test)]
fn insert_character_with_package(&mut self, character: CharacterSpec, package: &str) {
let CharacterSpec {
name,
allowed_mode,
unicode_value,
attributes,
} = character;
self.upsert_character_meta(ActiveCharacterRecord {
name: name.clone(),
allowed_mode,
unicode_value,
attributes,
package: package.to_string(),
});
let view_idx = self.upsert_character_command_view(make_command_meta(
name.clone(),
CommandKind::Prefix,
allowed_mode,
vec![],
vec![],
String::new(),
vec![package.to_string()],
));
self.set_active_command_source(name, ActiveCommandSource::Character(view_idx));
}
fn insert_builtin_character_with_package(
&mut self,
character: &'static BuiltinCharacterRecord,
package: &str,
) {
self.upsert_character_meta(ActiveCharacterRecord {
name: character.name.to_string(),
allowed_mode: character.allowed_mode,
unicode_value: character.unicode_value.to_string(),
attributes: character.attributes.into(),
package: package.to_string(),
});
let view_idx = self.upsert_character_command_view(ActiveCommandRecord {
name: character.name,
kind: CommandKind::Prefix,
allowed_mode: character.allowed_mode,
argspec: texform_knowledge::argspec!(""),
tags: &[],
from_packages: leak_string_array(vec![package.to_string()]),
});
self.set_active_command_source(character.name, ActiveCommandSource::Character(view_idx));
}
fn insert_builtin_delimiter_with_package(
&mut self,
delimiter: &'static BuiltinDelimiterRecord,
package: &str,
) {
self.upsert_delimiter_meta(ActiveDelimiterRecord {
name: delimiter.name,
is_control_sequence: delimiter.is_control_sequence,
allowed_mode: delimiter.allowed_mode,
unicode_value: delimiter.unicode_value.to_string(),
attributes: delimiter.attributes.into(),
package: package.to_string(),
});
}
#[cfg(test)]
fn insert_delimiter_with_package(&mut self, delimiter: DelimiterSpec, package: &str) {
let name = leak_string(delimiter.name);
self.upsert_delimiter_meta(ActiveDelimiterRecord {
name,
is_control_sequence: delimiter.is_control_sequence,
allowed_mode: delimiter.allowed_mode,
unicode_value: delimiter.unicode_value,
attributes: delimiter.attributes,
package: package.to_string(),
});
}
#[cfg(test)]
pub(crate) fn insert_or_override_command(&mut self, spec: CommandSpec) {
self.insert_or_override_command_with_package(spec, UNKNOWN_PACKAGE_NAME);
}
#[cfg(test)]
fn insert_or_override_command_with_package(&mut self, spec: CommandSpec, package: &str) {
let meta = command_spec_into_meta(spec, vec![package.to_string()]);
let idx = self.append_command_meta(meta);
let name = self.commands[idx].name;
self.set_active_command_source(name, ActiveCommandSource::Explicit(idx));
}
#[cfg(test)]
fn import_or_merge_command_with_package(&mut self, spec: CommandSpec, package: &str) {
let incoming = command_spec_into_meta(spec, vec![package.to_string()]);
if let Some(existing_idx) = self.command_idx_by_name.get(incoming.name).copied() {
let existing = &self.commands[existing_idx];
if should_merge_command(existing, &incoming) {
let merged = merge_command_meta(existing, &incoming);
let idx = self.append_command_meta(merged);
let name = self.commands[idx].name;
self.set_active_command_source(name, ActiveCommandSource::Explicit(idx));
return;
}
}
let idx = self.append_command_meta(incoming);
let name = self.commands[idx].name;
self.set_active_command_source(name, ActiveCommandSource::Explicit(idx));
}
fn import_or_merge_builtin_command_with_package(
&mut self,
record: &'static BuiltinCommandRecord,
package: &str,
) {
let incoming = builtin_command_into_meta(record, vec![package.to_string()]);
if let Some(existing_idx) = self.command_idx_by_name.get(incoming.name).copied() {
let existing = &self.commands[existing_idx];
if should_merge_command(existing, &incoming) {
let merged = merge_command_meta(existing, &incoming);
let idx = self.append_command_meta(merged);
let name = self.commands[idx].name;
self.set_active_command_source(name, ActiveCommandSource::Explicit(idx));
return;
}
}
let idx = self.append_command_meta(incoming);
let name = self.commands[idx].name;
self.set_active_command_source(name, ActiveCommandSource::Explicit(idx));
}
#[cfg(test)]
fn insert_or_override_environment(&mut self, spec: EnvironmentSpec) {
self.insert_or_override_environment_with_package(spec, UNKNOWN_PACKAGE_NAME);
}
#[cfg(test)]
fn insert_or_override_environment_with_package(
&mut self,
spec: EnvironmentSpec,
package: &str,
) {
let meta = environment_spec_into_meta(spec, vec![package.to_string()]);
self.append_env_meta(meta);
}
#[cfg(test)]
fn import_or_merge_environment_with_package(&mut self, spec: EnvironmentSpec, package: &str) {
let incoming = environment_spec_into_meta(spec, vec![package.to_string()]);
if let Some(existing_idx) = self.env_idx_by_name.get(incoming.name).copied() {
let existing = &self.envs[existing_idx];
if should_merge_environment(existing, &incoming) {
self.append_env_meta(merge_environment_meta(existing, &incoming));
return;
}
}
self.append_env_meta(incoming);
}
fn import_or_merge_builtin_environment_with_package(
&mut self,
record: &'static BuiltinEnvironmentRecord,
package: &str,
) {
let incoming = builtin_environment_into_meta(record, vec![package.to_string()]);
if let Some(existing_idx) = self.env_idx_by_name.get(incoming.name).copied() {
let existing = &self.envs[existing_idx];
if should_merge_environment(existing, &incoming) {
self.append_env_meta(merge_environment_meta(existing, &incoming));
return;
}
}
self.append_env_meta(incoming);
}
#[cfg(test)]
pub(crate) fn import_package(&mut self, specs: PackageSpecs) {
self.import_package_with_name(UNKNOWN_PACKAGE_NAME, specs);
}
#[cfg(test)]
fn import_package_with_name(&mut self, package: &str, specs: PackageSpecs) {
for character in specs.characters {
self.insert_character_with_package(character, package);
}
for delimiter in specs.delimiters {
self.insert_delimiter_with_package(delimiter, package);
}
for cmd in specs.commands {
self.import_or_merge_command_with_package(cmd, package);
}
for env in specs.environments {
self.import_or_merge_environment_with_package(env, package);
}
}
fn import_builtin_package(&mut self, package: &'static BuiltinPackage) {
for character in package.characters {
self.insert_builtin_character_with_package(character, package.name);
}
for delimiter in package.delimiters {
self.insert_builtin_delimiter_with_package(delimiter, package.name);
}
for command in package.commands {
self.import_or_merge_builtin_command_with_package(command, package.name);
}
for environment in package.environments {
self.import_or_merge_builtin_environment_with_package(environment, package.name);
}
}
fn import_builtin_package_for_mode(
&mut self,
package: &'static BuiltinPackage,
target_mode: ContentMode,
) {
for character in package.characters {
if character.allowed_mode.allows(target_mode) {
self.insert_builtin_character_with_package(character, package.name);
}
}
for delimiter in package.delimiters {
if delimiter.allowed_mode.allows(target_mode) {
self.insert_builtin_delimiter_with_package(delimiter, package.name);
}
}
for command in package.commands {
if command.allowed_mode.allows(target_mode) {
self.import_or_merge_builtin_command_with_package(command, package.name);
}
}
for environment in package.environments {
if environment.allowed_mode.allows(target_mode) {
self.import_or_merge_builtin_environment_with_package(environment, package.name);
}
}
}
}
fn make_command_meta(
name: String,
kind: CommandKind,
allowed_mode: AllowedMode,
args: Vec<ArgSpec>,
tags: Vec<String>,
source: String,
from_packages: Vec<String>,
) -> ActiveCommandRecord {
ActiveCommandRecord {
name: leak_string(name),
kind,
allowed_mode,
argspec: ParsedArgSpec {
args: leak_arg_specs(args),
source: leak_string(source),
},
tags: leak_tags(tags),
from_packages: leak_string_array(from_packages),
}
}
fn command_item_into_meta(
item: CommandItem,
from_packages: Vec<String>,
) -> Result<ActiveCommandRecord, ArgSpecParseError> {
let context = format!("command {}", item.name);
let args = parse_arg_specs(item.spec.as_str(), context.as_str())?;
Ok(make_command_meta(
item.name,
item.kind,
item.allowed_mode,
args,
item.tags,
item.spec,
from_packages,
))
}
fn make_env_meta(
name: String,
allowed_mode: AllowedMode,
args: Vec<ArgSpec>,
body_mode: ContentMode,
tags: Vec<String>,
source: String,
from_packages: Vec<String>,
) -> ActiveEnvironmentRecord {
ActiveEnvironmentRecord {
name: leak_string(name),
allowed_mode,
argspec: ParsedArgSpec {
args: leak_arg_specs(args),
source: leak_string(source),
},
body_mode,
tags: leak_tags(tags),
from_packages: leak_string_array(from_packages),
}
}
fn environment_item_into_meta(
item: EnvironmentItem,
from_packages: Vec<String>,
) -> Result<ActiveEnvironmentRecord, ArgSpecParseError> {
let context = format!("environment {}", item.name);
let args = parse_arg_specs(item.spec.as_str(), context.as_str())?;
Ok(make_env_meta(
item.name,
item.allowed_mode,
args,
item.body_mode,
item.tags,
item.spec,
from_packages,
))
}
#[cfg(test)]
fn command_spec_into_meta(spec: CommandSpec, from_packages: Vec<String>) -> ActiveCommandRecord {
make_command_meta(
spec.name,
spec.kind,
spec.allowed_mode,
spec.argspec.args,
spec.tags,
spec.argspec.source,
from_packages,
)
}
fn builtin_command_into_meta(
record: &'static BuiltinCommandRecord,
from_packages: Vec<String>,
) -> ActiveCommandRecord {
ActiveCommandRecord {
name: record.name,
kind: record.kind,
allowed_mode: record.allowed_mode,
argspec: record.argspec,
tags: record.tags,
from_packages: leak_string_array(from_packages),
}
}
#[cfg(test)]
fn environment_spec_into_meta(
spec: EnvironmentSpec,
from_packages: Vec<String>,
) -> ActiveEnvironmentRecord {
make_env_meta(
spec.name,
spec.allowed_mode,
spec.argspec.args,
spec.body_mode,
spec.tags,
spec.argspec.source,
from_packages,
)
}
fn builtin_environment_into_meta(
record: &'static BuiltinEnvironmentRecord,
from_packages: Vec<String>,
) -> ActiveEnvironmentRecord {
ActiveEnvironmentRecord {
name: record.name,
allowed_mode: record.allowed_mode,
argspec: record.argspec,
body_mode: record.body_mode,
tags: record.tags,
from_packages: leak_string_array(from_packages),
}
}
fn leak_string(value: impl Into<String>) -> &'static str {
Box::leak(value.into().into_boxed_str())
}
fn leak_arg_specs(args: Vec<ArgSpec>) -> &'static [ArgSpec] {
Box::leak(args.into_boxed_slice())
}
fn leak_tags(tags: Vec<String>) -> &'static [&'static str] {
let tags: Vec<&'static str> = tags
.into_iter()
.map(|tag| Box::leak(tag.into_boxed_str()) as &'static str)
.collect();
Box::leak(tags.into_boxed_slice())
}
fn leak_string_array(values: Vec<String>) -> &'static [&'static str] {
let leaked: Vec<&'static str> = values.into_iter().map(leak_string).collect();
Box::leak(leaked.into_boxed_slice())
}
fn dedup_names_in_request_order<'a>(requested: &[&'a str]) -> Vec<&'a str> {
let mut unique = Vec::new();
for &name in requested {
if !unique.contains(&name) {
unique.push(name);
}
}
unique
}
fn managed_package_names() -> impl Iterator<Item = &'static str> {
texform_knowledge::builtin::MANAGED_PACKAGE_IMPORT_ORDER
.iter()
.map(|package| package.as_str())
}
fn is_managed_package(name: &str) -> bool {
PackageName::from_str(name).is_some()
}
fn canonical_package_import_order<'a>(requested: &[&'a str]) -> Vec<&'a str> {
let unique = dedup_names_in_request_order(requested);
let mut normalized = Vec::new();
for managed in managed_package_names() {
if let Some(&name) = unique.iter().find(|&&candidate| candidate == managed) {
normalized.push(name);
}
}
for &name in &unique {
if !is_managed_package(name) {
normalized.push(name);
}
}
normalized
}
fn from_packages_are_managed(packages: &[&str]) -> bool {
!packages.is_empty() && packages.iter().all(|package| is_managed_package(package))
}
fn is_physics_denylisted_command(name: &str) -> bool {
PHYSICS_COMMAND_MERGE_DENYLIST.contains(&name)
}
fn merge_tags(existing: &[&str], incoming: &[&str]) -> Vec<String> {
let mut merged = Vec::new();
for &tag in existing.iter().chain(incoming.iter()) {
if !merged.iter().any(|existing_tag| existing_tag == tag) {
merged.push(tag.to_string());
}
}
merged.sort();
merged
}
fn merge_from_packages(existing: &[&str], incoming: &[&str]) -> Vec<String> {
let combined: Vec<&str> = existing
.iter()
.copied()
.chain(incoming.iter().copied())
.collect();
canonical_package_import_order(combined.as_slice())
.into_iter()
.map(ToString::to_string)
.collect()
}
fn should_merge_command(existing: &ActiveCommandRecord, incoming: &ActiveCommandRecord) -> bool {
existing.name == incoming.name
&& existing.kind == incoming.kind
&& existing.argspec.source == incoming.argspec.source
&& from_packages_are_managed(existing.from_packages)
&& from_packages_are_managed(incoming.from_packages)
&& !(is_physics_denylisted_command(existing.name)
&& (existing.from_packages.contains(&"physics")
|| incoming.from_packages.contains(&"physics")))
}
fn should_merge_environment(
existing: &ActiveEnvironmentRecord,
incoming: &ActiveEnvironmentRecord,
) -> bool {
existing.name == incoming.name
&& existing.argspec.source == incoming.argspec.source
&& existing.body_mode == incoming.body_mode
&& from_packages_are_managed(existing.from_packages)
&& from_packages_are_managed(incoming.from_packages)
}
fn merge_command_meta(
existing: &ActiveCommandRecord,
incoming: &ActiveCommandRecord,
) -> ActiveCommandRecord {
debug_assert!(should_merge_command(existing, incoming));
debug_assert_eq!(existing.argspec.args, incoming.argspec.args);
make_command_meta(
existing.name.to_string(),
existing.kind,
existing.allowed_mode.union(incoming.allowed_mode),
existing.argspec.args.to_vec(),
merge_tags(existing.tags, incoming.tags),
existing.argspec.source.to_string(),
merge_from_packages(existing.from_packages, incoming.from_packages),
)
}
fn merge_environment_meta(
existing: &ActiveEnvironmentRecord,
incoming: &ActiveEnvironmentRecord,
) -> ActiveEnvironmentRecord {
debug_assert!(should_merge_environment(existing, incoming));
debug_assert_eq!(existing.argspec.args, incoming.argspec.args);
make_env_meta(
existing.name.to_string(),
existing.allowed_mode.union(incoming.allowed_mode),
existing.argspec.args.to_vec(),
existing.body_mode,
merge_tags(existing.tags, incoming.tags),
existing.argspec.source.to_string(),
merge_from_packages(existing.from_packages, incoming.from_packages),
)
}
pub fn lookup_command_node_name(node: &Node) -> Option<&str> {
match node {
Node::Command { name, .. } | Node::Infix { name, .. } | Node::Declarative { name, .. } => {
Some(name.as_str())
}
_ => None,
}
}
pub fn lookup_environment_node_name(node: &Node) -> Option<&str> {
match node {
Node::Environment { name, .. } => Some(name.as_str()),
_ => None,
}
}
fn import_package_names(
kb: &mut KnowledgeBase,
requested: &[&str],
) -> Result<(), PackageLoadError> {
for &name in requested {
let pkg = texform_knowledge::builtin::lookup_package(name).ok_or_else(|| {
PackageLoadError::UnknownPackage {
name: name.to_string(),
}
})?;
kb.import_builtin_package(pkg);
}
Ok(())
}
fn import_package_names_for_mode(
kb: &mut KnowledgeBase,
requested: &[&str],
target_mode: ContentMode,
) -> Result<(), PackageLoadError> {
for &name in requested {
let pkg = texform_knowledge::builtin::lookup_package(name).ok_or_else(|| {
PackageLoadError::UnknownPackage {
name: name.to_string(),
}
})?;
kb.import_builtin_package_for_mode(pkg, target_mode);
}
Ok(())
}
#[cfg(test)]
pub(crate) fn try_build_kb_from_exact_packages(
requested: &[&str],
) -> Result<KnowledgeBase, PackageLoadError> {
let mut kb = KnowledgeBase::new();
import_package_names(&mut kb, requested)?;
Ok(kb)
}
#[cfg(test)]
fn build_default_kb(packages: Option<&[&str]>) -> KnowledgeBase {
match packages {
Some(list) => KnowledgeBase::build_from_packages(list),
None => {
let package_names = texform_knowledge::builtin::all_package_names();
KnowledgeBase::build_from_packages(package_names.as_slice())
}
}
}
#[cfg(test)]
#[path = "knowledge/tests.rs"]
mod tests;