use std::collections::BTreeMap;
use std::fmt::Write as _;
use std::sync::OnceLock;
use crate::coding::catalog::ProgramLanguage;
use crate::seed::parser::{parse_lino, LinoNode};
use crate::seed::CODING_IDIOMS_LINO;
use super::{ListValue, Operation, ParsedListItem};
const MAX_EXPANSION_DEPTH: usize = 8;
const MAX_INHERITANCE_DEPTH: usize = 4;
fn idiom_catalog() -> Option<&'static LinoNode> {
static TREE: OnceLock<LinoNode> = OnceLock::new();
TREE.get_or_init(|| parse_lino(CODING_IDIOMS_LINO))
.children
.first()
}
fn language_chain(catalog: &'static LinoNode, slug: &str) -> Vec<&'static LinoNode> {
let mut chain = Vec::new();
let mut current = slug.to_owned();
while chain.len() < MAX_INHERITANCE_DEPTH {
let Some(node) = catalog
.children
.iter()
.find(|child| child.name == "language" && child.id == current)
else {
break;
};
chain.push(node);
let parent = node.find_child_value("extends");
if parent.is_empty() {
break;
}
parent.clone_into(&mut current);
}
chain
}
fn default_name(key: &str) -> String {
idiom_catalog()
.and_then(|catalog| {
catalog
.children
.iter()
.find(|child| child.name == "defaults")
})
.and_then(|defaults| {
defaults
.children
.iter()
.find(|child| child.name == key)
.map(|child| child.id.clone())
})
.unwrap_or_default()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ValueType {
Integer,
Float,
Text,
}
impl ValueType {
fn from_items(items: &[ParsedListItem], is_float: bool) -> Self {
if items
.iter()
.any(|item| matches!(item.value, ListValue::Text))
{
Self::Text
} else if is_float {
Self::Float
} else {
Self::Integer
}
}
const fn links_label(self) -> &'static str {
match self {
Self::Integer => "integer",
Self::Float => "float",
Self::Text => "string",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ProgramStatement {
LiteralList {
name: String,
mutable: bool,
},
TransformList {
semantic_node: &'static str,
source: String,
target: String,
direction: String,
},
ReduceList {
source: String,
target: String,
reducer: &'static str,
},
PrintJoined {
source: String,
separator: &'static str,
},
PrintScalar {
source: String,
},
}
impl ProgramStatement {
fn links_line(&self, out: &mut String) {
match self {
Self::LiteralList { name, mutable } => {
let _ = writeln!(
out,
" semantic_node literal_list name={name} mutable={mutable}"
);
}
Self::TransformList {
semantic_node,
source,
target,
direction,
} => {
let _ = writeln!(
out,
" semantic_node {semantic_node} source={source} target={target} direction={direction}"
);
}
Self::ReduceList {
source,
target,
reducer,
} => {
let _ = writeln!(
out,
" semantic_node reduce_list source={source} target={target} reducer={reducer}"
);
}
Self::PrintJoined { source, separator } => {
let _ = writeln!(
out,
" semantic_node print_joined source={source} separator={separator:?}"
);
}
Self::PrintScalar { source } => {
let _ = writeln!(out, " semantic_node print_scalar source={source}");
}
}
}
}
#[derive(Clone)]
pub struct NumericProgram {
language: &'static ProgramLanguage,
value_type: ValueType,
literals: Vec<String>,
display_values: Vec<String>,
operation: Operation,
statements: Vec<ProgramStatement>,
}
impl NumericProgram {
#[must_use]
pub fn render(&self) -> Option<String> {
let catalog = idiom_catalog()?;
let chain = language_chain(catalog, self.language.slug);
if chain.is_empty() {
return None;
}
let composer = Composer::new(self, chain);
let family = super::family_for(self.operation.canonical());
let scaffold = composer.scaffold(family)?;
composer.expand(scaffold, 0)
}
#[must_use]
pub fn links_notation(&self) -> String {
let mut out = String::from("program_syntax_tree\n");
let _ = writeln!(out, " language {}", self.language.slug);
let _ = writeln!(out, " value_type {}", self.value_type.links_label());
let _ = writeln!(out, " operation {}", self.operation.canonical());
let _ = writeln!(out, " literal_values {}", self.display_values.join("|"));
for statement in &self.statements {
statement.links_line(&mut out);
}
out.trim_end().to_owned()
}
fn literal(&self) -> String {
self.literals.join(", ")
}
}
struct Composer<'p> {
program: &'p NumericProgram,
chain: Vec<&'static LinoNode>,
bindings: BTreeMap<&'static str, String>,
}
impl<'p> Composer<'p> {
fn new(program: &'p NumericProgram, chain: Vec<&'static LinoNode>) -> Self {
let mut composer = Self {
program,
chain,
bindings: BTreeMap::new(),
};
for key in ["list", "transformed", "reduced"] {
if let Some(name) = composer.resolved_name(key) {
composer.bindings.insert(key, name);
}
}
if let Some(type_name) = composer.resolved_type() {
composer.bindings.insert("type", type_name);
}
composer.bindings.insert("literal", program.literal());
composer
.bindings
.insert("count", program.literals.len().to_string());
composer.bindings.insert("tab", "\t".to_owned());
composer
}
fn resolved_name(&self, key: &str) -> Option<String> {
for language in &self.chain {
if let Some(names) = language.children.iter().find(|child| child.name == "names") {
if let Some(entry) = names.children.iter().find(|child| child.name == key) {
return Some(entry.id.clone());
}
}
}
let name = default_name(key);
if name.is_empty() {
None
} else {
Some(name)
}
}
fn resolved_type(&self) -> Option<String> {
let class = self.program.value_type.links_label();
for language in &self.chain {
if let Some(types) = language.children.iter().find(|child| child.name == "types") {
if let Some(entry) = types.children.iter().find(|child| child.name == class) {
return Some(entry.id.clone());
}
}
}
None
}
fn scaffold(&self, family: &str) -> Option<&'static str> {
for language in &self.chain {
if let Some(scaffold) = language
.children
.iter()
.find(|child| child.name == "scaffold" && child.id == family)
{
return Some(scaffold.find_child_value("code"));
}
}
None
}
fn idiom(&self, slot: &str) -> Option<&'static LinoNode> {
for language in &self.chain {
if let Some(idiom) = language
.children
.iter()
.find(|child| child.name == "idiom" && child.id == slot)
{
return Some(idiom);
}
}
None
}
fn select_case(&self, idiom: &'static LinoNode) -> Option<&'static str> {
let operation = self.program.operation.canonical();
let class = self.program.value_type.links_label();
let mut best: Option<(&'static str, u32)> = None;
for case in idiom.children.iter().filter(|child| child.name == "case") {
let mut for_tokens = case.find_child_value("for").split_whitespace();
let operation_exact = for_tokens.clone().any(|token| token == operation);
if !operation_exact && !for_tokens.any(|token| token == "any") {
continue;
}
let on = case.children.iter().find(|child| child.name == "on");
if let Some(on) = on {
if !on.id.split_whitespace().any(|token| token == class) {
continue;
}
}
let score = u32::from(operation_exact) * 2 + u32::from(on.is_some());
if best.map_or(true, |(_, current)| score > current) {
best = Some((case.find_child_value("code"), score));
}
}
best.map(|(code, _)| code)
}
fn expand(&self, template: &str, depth: usize) -> Option<String> {
if depth > MAX_EXPANSION_DEPTH {
return None;
}
let chars: Vec<char> = template.chars().collect();
let mut out = String::new();
let mut index = 0;
while index < chars.len() {
if chars[index] != '{' {
out.push(chars[index]);
index += 1;
continue;
}
let mut end = index + 1;
while end < chars.len() && is_slot_char(chars[end]) {
end += 1;
}
if end >= chars.len() || chars[end] != '}' || end == index + 1 {
out.push('{');
index += 1;
continue;
}
let name: String = chars[index + 1..end].iter().collect();
if let Some(value) = self.bindings.get(name.as_str()) {
out.push_str(value);
} else if let Some(idiom) = self.idiom(&name) {
let code = self.select_case(idiom)?;
out.push_str(&self.expand(code, depth + 1)?);
} else {
out.push('{');
out.push_str(&name);
out.push('}');
}
index = end + 1;
}
Some(out)
}
}
const fn is_slot_char(ch: char) -> bool {
ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_'
}
fn mutates_list_in_place(slug: &str) -> bool {
let Some(catalog) = idiom_catalog() else {
return false;
};
for language in language_chain(catalog, slug) {
if let Some(node) = language
.children
.iter()
.find(|child| child.name == "mutable_list")
{
return node.id == "true";
}
}
false
}
#[must_use]
pub fn build(
language: &'static ProgramLanguage,
items: &[ParsedListItem],
operation: Operation,
is_float: bool,
) -> NumericProgram {
let value_type = ValueType::from_items(items, is_float);
let literals = item_literals(items, value_type);
let display_values = items.iter().map(|item| item.text.clone()).collect();
let canonical = operation.canonical();
let list = default_name("list");
let mut statements = vec![ProgramStatement::LiteralList {
name: list.clone(),
mutable: mutates_list_in_place(language.slug),
}];
match operation {
Operation::Transform(_) => {
let target = default_name("transformed");
statements.push(ProgramStatement::TransformList {
semantic_node: if canonical == "reverse" {
"reverse_list"
} else {
"sort_list"
},
source: list,
target: target.clone(),
direction: super::direction_for(canonical),
});
statements.push(ProgramStatement::PrintJoined {
source: target,
separator: ", ",
});
}
Operation::Reduce(_) => {
let target = default_name("reduced");
statements.push(ProgramStatement::ReduceList {
source: list,
target: target.clone(),
reducer: canonical,
});
statements.push(ProgramStatement::PrintScalar { source: target });
}
}
NumericProgram {
language,
value_type,
literals,
display_values,
operation,
statements,
}
}
fn item_literals(items: &[ParsedListItem], value_type: ValueType) -> Vec<String> {
items
.iter()
.map(|item| match item.value {
ListValue::Text => quoted_string_literal(&item.text),
ListValue::Number(_) => {
if value_type == ValueType::Float && !item.text.contains('.') {
format!("{}.0", item.text)
} else {
item.text.clone()
}
}
})
.collect()
}
fn quoted_string_literal(value: &str) -> String {
let mut out = String::from("\"");
for ch in value.chars() {
match ch {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
_ => out.push(ch),
}
}
out.push('"');
out
}