use crate::expr::Expr;
use crate::gen::Gen;
use crate::governance::{Governance, Relation};
use crate::rat::Rat;
use crate::trit::{Trit, N, P, Z};
use crate::word::Word;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct TypeDef {
pub name: String,
pub sig: Option<Trit>,
}
#[derive(Clone, Default)]
pub struct TypeRegistry {
types: HashMap<String, TypeDef>,
}
impl TypeRegistry {
pub fn new() -> Self {
TypeRegistry::default()
}
pub fn with_standard(mut self) -> Self {
self.register("imag", Some(N));
self.register("dual", Some(Z));
self.register("hyp", Some(P));
self.register("any", None);
self
}
pub fn register(&mut self, name: &str, sig: Option<Trit>) {
self.types.insert(
name.to_string(),
TypeDef {
name: name.to_string(),
sig,
},
);
}
pub fn get(&self, name: &str) -> Option<&TypeDef> {
self.types.get(name)
}
pub fn satisfies(&self, type_name: &str, g: Gen) -> bool {
match self.get(type_name) {
None => false,
Some(td) => match td.sig {
None => true, Some(required) => g.sig == required,
},
}
}
}
pub type BinaryBridge = fn(Gen, Gen) -> bool;
#[derive(Clone, Default)]
pub struct BridgeRegistry {
binary: HashMap<String, BinaryBridge>,
}
impl BridgeRegistry {
pub fn new() -> Self {
BridgeRegistry::default()
}
pub fn with_standard(mut self) -> Self {
self.register("ordered", |a, b| a < b);
self
}
pub fn register(&mut self, name: &str, f: BinaryBridge) {
self.binary.insert(name.to_string(), f);
}
pub fn call(&self, name: &str, a: Gen, b: Gen) -> Option<bool> {
self.binary.get(name).map(|f| f(a, b))
}
}
#[derive(Debug, Clone)]
pub struct LoadError {
pub message: String,
pub line: usize,
}
impl std::fmt::Display for LoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "line {}: {}", self.line, self.message)
}
}
fn err(msg: impl Into<String>, line: usize) -> LoadError {
LoadError {
message: msg.into(),
line,
}
}
pub fn load(
source: &str,
base: Governance,
types: &TypeRegistry,
bridges: &BridgeRegistry,
gens: &[Gen],
) -> Result<Governance, LoadError> {
let mut gov = base;
let mut local_types = types.clone();
for (i, raw_line) in source.lines().enumerate() {
let line_num = i + 1;
let line = raw_line.trim();
if line.is_empty() || line.starts_with("//") {
continue;
}
if line.starts_with("type ") {
let td = parse_type_def(line, line_num)?;
local_types.register(&td.name.clone(), td.sig);
} else if line.starts_with("for ") {
let relations = parse_parametric(line, line_num, &local_types, bridges, gens)?;
for rel in relations {
gov = gov.with_relation(rel);
}
} else if line.contains('=') {
let rel = parse_concrete(line, line_num)?;
gov = gov.with_relation(rel);
} else {
return Err(err(format!("unrecognized line: '{line}'"), line_num));
}
}
Ok(gov)
}
pub fn load_standard(
source: &str,
base: Governance,
gens: &[Gen],
) -> Result<Governance, LoadError> {
let types = TypeRegistry::new().with_standard();
let bridges = BridgeRegistry::new().with_standard();
load(source, base, &types, &bridges, gens)
}
fn parse_type_def(line: &str, ln: usize) -> Result<TypeDef, LoadError> {
let rest = line.strip_prefix("type ").unwrap().trim();
let eq = rest
.find(" = ")
.ok_or_else(|| err("type requires ' = '", ln))?;
let name = rest[..eq].trim().to_string();
let body = rest[eq + 3..].trim();
let inner = body
.strip_prefix('{')
.and_then(|s| s.strip_suffix('}'))
.ok_or_else(|| err("type body must be { ... }", ln))?
.trim();
if inner.is_empty() {
return Ok(TypeDef { name, sig: None });
}
let eq_pos = inner
.rfind('=')
.ok_or_else(|| err("type body needs '='", ln))?;
let scalar = inner[eq_pos + 1..].trim();
let sig = match scalar {
"-1" => Some(N),
"0" => Some(Z),
"1" => Some(P),
_ => {
return Err(err(
format!("type scalar must be -1, 0, or 1; got '{scalar}'"),
ln,
))
}
};
Ok(TypeDef { name, sig })
}
fn parse_concrete(line: &str, ln: usize) -> Result<Relation, LoadError> {
let parts: Vec<&str> = line.splitn(2, '=').collect();
if parts.len() != 2 {
return Err(err("concrete relation requires '='", ln));
}
let pat_str = parts[0].trim();
let repl_str = parts[1].trim();
let pat_expr = parse_expr_concrete(pat_str, ln)?;
let mut terms: Vec<_> = pat_expr.terms().collect();
if terms.len() != 1 {
return Err(err("pattern must be a single product", ln));
}
let (word, coeff) = terms.remove(0);
if !coeff.abs().is_one() {
return Err(err("pattern coefficient must be ±1", ln));
}
let mut replacement = parse_expr_concrete(repl_str, ln)?;
if *coeff == Rat::neg_one() {
replacement = replacement.neg();
}
Ok(Relation::new(word.clone(), replacement))
}
fn parse_expr_concrete(s: &str, ln: usize) -> Result<Expr, LoadError> {
let s = s.trim();
if let Some(rest) = s.strip_prefix('-') {
let inner = parse_expr_concrete(rest.trim(), ln)?;
return Ok(inner.neg());
}
if let Ok(n) = s.parse::<i64>() {
return Ok(Expr::int(n));
}
let parts: Vec<&str> = s.split('*').collect();
let mut gens = Vec::new();
let mut scalar = Rat::one();
for part in &parts {
let p = part.trim();
if let Some(stripped) = p.strip_prefix('-') {
scalar = scalar.neg();
let rest = stripped.trim();
if let Ok(g) = parse_gen_name(rest, ln) {
gens.push(g);
} else if let Ok(n) = rest.parse::<i64>() {
scalar = scalar * Rat::integer(n);
} else {
return Err(err(format!("unknown token: '{rest}'"), ln));
}
} else if let Ok(g) = parse_gen_name(p, ln) {
gens.push(g);
} else if let Ok(n) = p.parse::<i64>() {
scalar = scalar * Rat::integer(n);
} else {
return Err(err(format!("unknown token: '{p}'"), ln));
}
}
if gens.is_empty() {
Ok(Expr::scalar(scalar))
} else {
Ok(Expr::term(scalar, Word::from_gens(&gens)))
}
}
fn parse_gen_name(s: &str, ln: usize) -> Result<Gen, LoadError> {
if let Some(num_str) = s.strip_prefix('e') {
if let Ok(n) = num_str.parse::<u32>() {
if n == 0 {
return Err(err("generator index must be >= 1", ln));
}
return Ok(Gen::imaginary(n - 1));
}
}
Err(err(
format!("'{}' is not a generator name (expected eN)", s),
ln,
))
}
fn parse_parametric(
line: &str,
ln: usize,
types: &TypeRegistry,
bridges: &BridgeRegistry,
gens: &[Gen],
) -> Result<Vec<Relation>, LoadError> {
let rest = line.strip_prefix("for ").unwrap().trim();
let arrow = rest
.find("=>")
.ok_or_else(|| err("parametric requires '=>'", ln))?;
let head = rest[..arrow].trim();
let body = rest[arrow + 2..].trim();
let (bindings, where_pred) = parse_head(head, ln)?;
let eq = body
.find('=')
.ok_or_else(|| err("parametric body requires '='", ln))?;
let pat_str = body[..eq].trim();
let repl_str = body[eq + 1..].trim();
let vars: Vec<char> = bindings.iter().map(|(v, _)| *v).collect();
let pattern_vars = parse_var_pattern(pat_str, &vars, ln)?;
let replacement = parse_var_replacement(repl_str, &vars, ln)?;
let relations = instantiate(
&bindings,
where_pred.as_deref(),
&pattern_vars,
&replacement,
types,
bridges,
gens,
ln,
)?;
Ok(relations)
}
#[allow(clippy::type_complexity)]
fn parse_head(head: &str, ln: usize) -> Result<(Vec<(char, String)>, Option<String>), LoadError> {
let (bindings_str, where_str) = if let Some(pos) = head.find(" where ") {
(&head[..pos], Some(head[pos + 7..].trim().to_string()))
} else {
(head, None)
};
let mut bindings = Vec::new();
for part in bindings_str.split(',') {
let part = part.trim();
let colon = part
.find(':')
.ok_or_else(|| err(format!("binding '{part}' needs ': TypeName'"), ln))?;
let var_str = part[..colon].trim();
let type_str = part[colon + 1..].trim();
if var_str.len() != 1 || !var_str.chars().next().unwrap().is_lowercase() {
return Err(err(
format!("variable must be single lowercase letter, got '{var_str}'"),
ln,
));
}
bindings.push((var_str.chars().next().unwrap(), type_str.to_string()));
}
if bindings.is_empty() || bindings.len() > 2 {
return Err(err("parametric supports 1 or 2 variables", ln));
}
Ok((bindings, where_str))
}
fn parse_var_pattern(s: &str, vars: &[char], ln: usize) -> Result<Vec<char>, LoadError> {
s.split('*')
.map(|p| {
let p = p.trim();
if p.len() == 1 && vars.contains(&p.chars().next().unwrap()) {
Ok(p.chars().next().unwrap())
} else {
Err(err(format!("pattern token '{p}' is not a variable"), ln))
}
})
.collect()
}
#[derive(Debug, Clone)]
enum VarReplacement {
Scalar(Rat),
Word(Vec<char>),
NegWord(Vec<char>),
}
fn parse_var_replacement(s: &str, vars: &[char], ln: usize) -> Result<VarReplacement, LoadError> {
match s {
"-1" => return Ok(VarReplacement::Scalar(Rat::neg_one())),
"0" => return Ok(VarReplacement::Scalar(Rat::zero())),
"1" => return Ok(VarReplacement::Scalar(Rat::one())),
_ => {}
}
if let Some(rest) = s.strip_prefix('-') {
let vars_seq = parse_var_pattern(rest.trim(), vars, ln)?;
return Ok(VarReplacement::NegWord(vars_seq));
}
let vars_seq = parse_var_pattern(s, vars, ln)?;
Ok(VarReplacement::Word(vars_seq))
}
#[allow(clippy::too_many_arguments)]
fn instantiate(
bindings: &[(char, String)],
where_pred: Option<&str>,
pattern_vars: &[char],
replacement: &VarReplacement,
types: &TypeRegistry,
bridges: &BridgeRegistry,
gens: &[Gen],
ln: usize,
) -> Result<Vec<Relation>, LoadError> {
let mut relations = Vec::new();
match bindings.len() {
1 => {
let (var, type_name) = &bindings[0];
for &g in gens {
if !types.satisfies(type_name, g) {
continue;
}
let mut assignment = HashMap::new();
assignment.insert(*var, g);
if let Some(rel) = build_relation(pattern_vars, replacement, &assignment) {
relations.push(rel);
}
}
}
2 => {
let (va, ta) = &bindings[0];
let (vb, tb) = &bindings[1];
for &ga in gens {
if !types.satisfies(ta, ga) {
continue;
}
for &gb in gens {
if !types.satisfies(tb, gb) {
continue;
}
if let Some(pred) = where_pred {
let a_val = if *va == bindings[0].0 { ga } else { gb };
let b_val = if *vb == bindings[1].0 { gb } else { ga };
match bridges.call(
pred.split('(').next().unwrap_or(pred).trim(),
a_val,
b_val,
) {
Some(false) | None => continue,
Some(true) => {}
}
}
let mut assignment = HashMap::new();
assignment.insert(*va, ga);
assignment.insert(*vb, gb);
if let Some(rel) = build_relation(pattern_vars, replacement, &assignment) {
relations.push(rel);
}
}
}
}
_ => return Err(err("parametric supports 1 or 2 variables", ln)),
}
Ok(relations)
}
fn build_relation(
pattern_vars: &[char],
replacement: &VarReplacement,
assignment: &HashMap<char, Gen>,
) -> Option<Relation> {
let source_gens: Vec<Gen> = pattern_vars
.iter()
.map(|&c| assignment.get(&c).copied())
.collect::<Option<Vec<_>>>()?;
let source = Word::from_gens(&source_gens);
let target = match replacement {
VarReplacement::Scalar(r) => Expr::scalar(*r),
VarReplacement::Word(vars) => {
let gens: Vec<Gen> = vars
.iter()
.map(|&c| assignment.get(&c).copied())
.collect::<Option<Vec<_>>>()?;
Expr::term(Rat::one(), Word::from_gens(&gens))
}
VarReplacement::NegWord(vars) => {
let gens: Vec<Gen> = vars
.iter()
.map(|&c| assignment.get(&c).copied())
.collect::<Option<Vec<_>>>()?;
Expr::term(Rat::neg_one(), Word::from_gens(&gens))
}
};
Some(Relation::new(source, target))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gen::Gen;
fn gens_cl200() -> Vec<Gen> {
vec![Gen::imaginary(0), Gen::imaginary(1)]
}
fn gens_mixed() -> Vec<Gen> {
vec![Gen::imaginary(0), Gen::degenerate(0), Gen::hyperbolic(0)]
}
#[test]
fn load_empty() {
let gov = load_standard("", Governance::free(), &[]).unwrap();
assert_eq!(gov.num_relations(), 0);
}
#[test]
fn load_comments_and_blanks() {
let gov = load_standard("// comment\n\n// another\n", Governance::free(), &[]).unwrap();
assert_eq!(gov.num_relations(), 0);
}
#[test]
fn load_type_def_imag() {
let source = "type imag = { x * x = -1 }";
let types = TypeRegistry::new();
let bridges = BridgeRegistry::new();
load(source, Governance::free(), &types, &bridges, &[]).unwrap();
}
#[test]
fn load_type_def_marker() {
let source = "type step = { }";
let types = TypeRegistry::new();
let bridges = BridgeRegistry::new();
load(source, Governance::free(), &types, &bridges, &[]).unwrap();
}
#[test]
fn load_parametric_imag_square() {
let source = "\
type imag = { x * x = -1 }
for x : imag => x * x = -1
";
let gens = gens_cl200(); let types = TypeRegistry::new();
let bridges = BridgeRegistry::new();
let gov = load(source, Governance::free(), &types, &bridges, &gens).unwrap();
assert_eq!(gov.num_relations(), 2);
let e1 = Expr::gen(Gen::imaginary(0));
assert_eq!(gov.mul(&e1, &e1), Expr::int(-1));
}
#[test]
fn load_parametric_anticommute_directional() {
let source = "\
type any = { }
for a : any, b : any where ordered(a, b) => b * a = -a * b
";
let gens = gens_cl200();
let gov = load_standard(source, Governance::free(), &gens).unwrap();
let e2e1 = Expr::term(
Rat::one(),
Word::from_gens(&[Gen::imaginary(1), Gen::imaginary(0)]),
);
let result = gov.canonicalize(&e2e1);
assert_eq!(
result.coeff(&Word::from_gens(&[Gen::imaginary(0), Gen::imaginary(1)])),
Rat::neg_one()
);
}
#[test]
fn load_trit_neb_file() {
let source = include_str!("neb/trit.neb");
let gens = gens_cl200();
let gov = load_standard(source, Governance::free(), &gens).unwrap();
let e1 = Expr::gen(Gen::imaginary(0));
let e2 = Expr::gen(Gen::imaginary(1));
assert_eq!(gov.mul(&e1, &e1), Expr::int(-1));
assert_eq!(gov.mul(&e2, &e2), Expr::int(-1));
let e2e1 = Expr::term(
Rat::one(),
Word::from_gens(&[Gen::imaginary(1), Gen::imaginary(0)]),
);
let canon = gov.canonicalize(&e2e1);
assert_eq!(
canon.coeff(&Word::from_gens(&[Gen::imaginary(0), Gen::imaginary(1)])),
Rat::neg_one()
);
}
#[test]
fn load_mixed_concrete_and_parametric() {
let source = "\
type imag = { x * x = -1 }
for x : imag => x * x = -1
e2*e1 = -e1*e2
";
let gens = gens_cl200();
let types = TypeRegistry::new();
let bridges = BridgeRegistry::new();
let gov = load(source, Governance::free(), &types, &bridges, &gens).unwrap();
let e1 = Expr::gen(Gen::imaginary(0));
assert_eq!(gov.mul(&e1, &e1), Expr::int(-1));
}
#[test]
fn load_mixed_types() {
let source = "\
type imag = { x * x = -1 }
type dual = { x * x = 0 }
type hyp = { x * x = 1 }
for x : imag => x * x = -1
for x : dual => x * x = 0
for x : hyp => x * x = 1
";
let gens = gens_mixed();
let types = TypeRegistry::new();
let bridges = BridgeRegistry::new();
let gov = load(source, Governance::free(), &types, &bridges, &gens).unwrap();
assert_eq!(
gov.mul(&Expr::gen(Gen::imaginary(0)), &Expr::gen(Gen::imaginary(0))),
Expr::int(-1)
);
assert!(gov
.mul(
&Expr::gen(Gen::degenerate(0)),
&Expr::gen(Gen::degenerate(0))
)
.is_zero());
assert_eq!(
gov.mul(
&Expr::gen(Gen::hyperbolic(0)),
&Expr::gen(Gen::hyperbolic(0))
),
Expr::int(1)
);
}
#[test]
fn load_parametric_matches_cl_constructor() {
let source = include_str!("neb/trit.neb");
let gens = gens_cl200();
let neb_gov = load_standard(source, Governance::free(), &gens).unwrap();
let built_gov = Governance::cl(2, 0, 0);
let e1 = Expr::gen(Gen::imaginary(0));
let e2 = Expr::gen(Gen::imaginary(1));
assert_eq!(neb_gov.mul(&e1, &e1), built_gov.mul(&e1, &e1));
assert_eq!(neb_gov.mul(&e2, &e2), built_gov.mul(&e2, &e2));
let e2e1 = Expr::term(
Rat::one(),
Word::from_gens(&[Gen::imaginary(1), Gen::imaginary(0)]),
);
assert_eq!(neb_gov.canonicalize(&e2e1), built_gov.canonicalize(&e2e1));
}
}