use crate::logic::typing::ContextTransition;
use super::Type;
use regex::Regex;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum TypeOperation {
Equality,
Inclusion,
}
pub type Term = String;
pub type TypeAscription = (Term, Type);
#[derive(Debug, Clone)]
pub struct TypeSetting {
pub name: String,
pub extensions: Vec<TypeAscription>,
pub no_propagate: bool,
}
#[derive(Debug, Clone)]
pub enum TypingJudgment {
Ascription(TypeAscription),
Membership(String, String),
Operation {
left: Type,
op: TypeOperation,
right: Type,
},
}
#[derive(Debug, Clone)]
pub struct Premise {
pub setting: Option<TypeSetting>,
pub judgment: Option<TypingJudgment>,
}
pub enum PremiseStatus {
Satisfied,
Unknown,
Contradiction,
}
#[derive(Debug, Clone, Default)]
pub struct ConclusionContext {
pub input: String,
pub output: Option<TypeSetting>,
}
impl ConclusionContext {
pub fn is_empty(&self) -> bool {
self.input.is_empty() && self.output.is_none()
}
}
#[derive(Debug, Clone)]
pub enum ConclusionKind {
Type(Type),
ContextLookup(String, String),
}
#[derive(Debug, Clone)]
pub struct Conclusion {
pub context: ConclusionContext,
pub kind: ConclusionKind,
}
#[derive(Debug, Clone)]
pub struct TypingRule {
pub name: String,
pub premises: Vec<Premise>,
pub conclusion: Conclusion,
}
pub enum RuleResult {
Success((Type, Option<ContextTransition>)),
Partial(Type),
Contradiction,
}
impl TypingRule {
pub fn used_bindings(&self) -> std::collections::HashSet<&str> {
let mut bindings = std::collections::HashSet::new();
fn collect_type_refs<'a>(ty: &'a Type, out: &mut std::collections::HashSet<&'a str>) {
match ty {
Type::Meta(name) => {
out.insert(name.as_str());
}
Type::Arrow(a, b) => {
collect_type_refs(a, out);
collect_type_refs(b, out);
}
Type::Array(inner) => {
collect_type_refs(inner, out);
}
Type::Union(items) => {
for item in items {
collect_type_refs(item, out);
}
}
_ => {}
}
}
for premise in &self.premises {
if let Some(setting) = &premise.setting {
for (var, ty) in &setting.extensions {
bindings.insert(var.as_str());
collect_type_refs(ty, &mut bindings);
}
}
if let Some(judgment) = &premise.judgment {
match judgment {
TypingJudgment::Ascription((term, t)) => {
collect_type_refs(t, &mut bindings);
bindings.insert(term.as_str());
}
TypingJudgment::Membership(var, _) => {
bindings.insert(var.as_str());
}
TypingJudgment::Operation { left, right, .. } => {
collect_type_refs(left, &mut bindings);
collect_type_refs(right, &mut bindings);
}
}
}
}
if let Some(output) = &self.conclusion.context.output {
for (var, ty) in &output.extensions {
bindings.insert(var.as_str());
collect_type_refs(ty, &mut bindings);
}
}
match &self.conclusion.kind {
ConclusionKind::Type(ty) => collect_type_refs(ty, &mut bindings),
ConclusionKind::ContextLookup(_, var) => {
bindings.insert(var.as_str());
}
}
bindings
}
pub fn pretty(&self, indent: usize) -> String {
let indent_str = " ".repeat(indent);
let mut out = String::new();
let conclusion_str = format!("{}", self.conclusion);
if self.premises.is_empty() {
out.push_str(&format!(
"{}{} [{}]",
indent_str, conclusion_str, self.name
));
return out;
}
let premise_lines: Vec<String> = self
.premises
.iter()
.map(|p| format!("{}{}", indent_str, p))
.collect();
let max_width = premise_lines
.iter()
.map(|l| l.trim_start().len())
.chain([conclusion_str.len()])
.max()
.unwrap_or(0);
let bar = format!("{}{}", indent_str, "─".repeat(max_width.max(4)));
out.push_str(&premise_lines.join("\n"));
out.push('\n');
out.push_str(&format!("{} [{}]", bar, self.name));
out.push('\n');
out.push_str(&format!("{}{}", indent_str, conclusion_str));
out
}
}
pub struct RuleParser;
impl RuleParser {
pub fn parse(
premises_str: &str,
conclusion_str: &str,
name: &str,
) -> Result<TypingRule, String> {
let premises = premises_str
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.filter_map(|p| match Self::parse_premise(p) {
Ok(Some(pr)) => Some(Ok(pr)),
Ok(None) => None,
Err(e) => Some(Err(e)),
})
.collect::<Result<Vec<_>, _>>()?;
let conclusion = Self::parse_conclusion(conclusion_str)?;
Ok(TypingRule {
name: name.to_string(),
premises,
conclusion,
})
}
pub fn parse_conclusion(s: &str) -> Result<Conclusion, String> {
let s = s.trim();
if let Some((lhs, rhs)) = s.split_once('⊢') {
let ty = Type::parse(rhs.trim())?;
let ctx = Self::parse_conclusion_context(lhs.trim())?;
return Ok(Conclusion {
context: ctx,
kind: ConclusionKind::Type(ty),
});
}
if let Some((ctx, var)) = Self::try_parse_context_lookup(s) {
return Ok(Conclusion {
context: ConclusionContext::default(),
kind: ConclusionKind::ContextLookup(ctx, var),
});
}
let ty = Type::parse(s)?;
Ok(Conclusion {
context: ConclusionContext::default(),
kind: ConclusionKind::Type(ty),
})
}
pub fn parse_premise(s: &str) -> Result<Option<Premise>, String> {
let s = s.trim();
if s.is_empty() {
return Ok(None);
}
if let Some((var, ctx)) = s.split_once('∈') {
let var = var.trim().to_string();
let ctx = ctx.trim().to_string();
if var.is_empty() || ctx.is_empty() {
return Err(format!("Invalid membership premise: '{}'", s));
}
return Ok(Some(Premise {
setting: None,
judgment: Some(TypingJudgment::Membership(var, ctx)),
}));
}
if let Some((setting_part, ascr_part)) = s.split_once('⊢') {
let setting = Self::parse_setting(setting_part.trim())?;
let ascription = Self::parse_ascription(ascr_part.trim())?;
return Ok(Some(Premise {
setting: Some(setting),
judgment: Some(TypingJudgment::Ascription(ascription)),
}));
}
else if let Some((left, op, right)) = Self::try_parse_operation(s) {
return Ok(Some(Premise {
setting: None,
judgment: Some(TypingJudgment::Operation { left, op, right }),
}));
}
let setting = Self::parse_setting(s)?;
Ok(Some(Premise {
setting: Some(setting),
judgment: None,
}))
}
pub fn parse_setting(s: &str) -> Result<TypeSetting, String> {
let s = s.trim();
if s.starts_with('[') && s.ends_with(']') && s.len() >= 2 {
let inner = s[1..s.len() - 1].trim();
let mut setting = Self::parse_setting(inner)?;
setting.no_propagate = true;
return Ok(setting);
}
if !s.contains('[') {
return Ok(TypeSetting {
name: s.to_string(),
extensions: Vec::new(),
no_propagate: false,
});
}
let name_re = Regex::new(r"^\s*([^\[\s]+)\s*\[").map_err(|e| e.to_string())?;
let name = name_re
.captures(s)
.and_then(|c| c.get(1))
.map(|m| m.as_str().to_string())
.ok_or_else(|| "Invalid setting: expected name before '['".to_string())?;
let ext_re = Regex::new(r"\[([^:\]]+):([^\]]+)\]").map_err(|e| e.to_string())?;
let mut extensions = Vec::new();
for cap in ext_re.captures_iter(s) {
let var = cap[1].trim().to_string();
let ty = Type::parse(cap[2].trim())?;
extensions.push((var, ty));
}
Ok(TypeSetting {
name,
extensions,
no_propagate: false,
})
}
fn parse_ascription(s: &str) -> Result<TypeAscription, String> {
let parts: Vec<&str> = s.splitn(2, ':').map(str::trim).collect();
if parts.len() != 2 {
return Err(format!(
"Invalid ascription: expected 'term : type', got '{}'",
s
));
}
let term = parts[0].to_string();
let ty = Type::parse(parts[1])?;
Ok((term, ty))
}
fn try_parse_operation(s: &str) -> Option<(Type, TypeOperation, Type)> {
if let Some((l, r)) = s.split_once("=")
&& let (Ok(left), Ok(right)) = (Type::parse(l.trim()), Type::parse(r.trim()))
{
return Some((left, TypeOperation::Equality, right));
}
for sep in ["⊆ ", "<="] {
if let Some((l, r)) = s.split_once(sep)
&& let (Ok(left), Ok(right)) = (Type::parse(l.trim()), Type::parse(r.trim()))
{
return Some((left, TypeOperation::Inclusion, right));
}
}
None
}
fn try_parse_context_lookup(s: &str) -> Option<(String, String)> {
let paren_start = s.find('(')?;
let paren_end = s.find(')')?;
if paren_end > paren_start && paren_end == s.len() - 1 {
let ctx = s[..paren_start].trim();
let var = s[paren_start + 1..paren_end].trim();
if !ctx.is_empty() && !var.is_empty() {
return Some((ctx.to_string(), var.to_string()));
}
}
None
}
fn parse_conclusion_context(s: &str) -> Result<ConclusionContext, String> {
if s.is_empty() {
return Ok(ConclusionContext::default());
}
for arrow in ["→", "->"] {
if let Some((l, r)) = s.split_once(arrow) {
let input = if l.trim().is_empty() {
String::new()
} else {
Self::parse_setting(l.trim())?.name
};
let output = if r.trim().is_empty() {
None
} else {
Some(Self::parse_setting(r.trim())?)
};
return Ok(ConclusionContext { input, output });
}
}
let input = Self::parse_setting(s)?.name;
Ok(ConclusionContext {
input,
output: None,
})
}
}
impl TypingRule {
pub fn new(premises: String, conclusion: String, name: String) -> Result<Self, String> {
RuleParser::parse(&premises, &conclusion, &name)
}
pub fn parse_conclusion(s: &str) -> Result<Conclusion, String> {
RuleParser::parse_conclusion(s)
}
}
impl Conclusion {
pub fn try_from_str(s: &str) -> Result<Self, String> {
RuleParser::parse_conclusion(s)
}
pub fn try_from_string(s: String) -> Result<Self, String> {
Self::try_from_str(&s)
}
}
impl fmt::Display for TypeOperation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeOperation::Equality => write!(f, "="),
TypeOperation::Inclusion => write!(f, "⊆"),
}
}
}
impl fmt::Display for TypeSetting {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let base = if self.extensions.is_empty() {
self.name.clone()
} else {
let exts: Vec<String> = self
.extensions
.iter()
.map(|(t, ty)| format!("{}:{}", t, ty))
.collect();
format!("{}[{}]", self.name, exts.join(", "))
};
if self.no_propagate {
write!(f, "[{}]", base)
} else {
write!(f, "{}", base)
}
}
}
impl fmt::Display for TypingJudgment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypingJudgment::Ascription((term, ty)) => write!(f, "{} : {}", term, ty),
TypingJudgment::Membership(var, ctx) => write!(f, "{} ∈ {}", var, ctx),
TypingJudgment::Operation { left, op, right } => {
write!(f, "{} {} {}", left, op, right)
}
}
}
}
impl fmt::Display for Premise {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.setting, &self.judgment) {
(Some(s), Some(j)) => write!(f, "{} ⊢ {}", s, j),
(Some(s), None) => write!(f, "{}", s),
(None, Some(j)) => write!(f, "{}", j),
(None, None) => Ok(()),
}
}
}
impl fmt::Display for Conclusion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ConclusionKind::Type(ty) => {
if self.context.is_empty() {
write!(f, "{}", ty)
} else {
let input = &self.context.input;
match (input.is_empty(), &self.context.output) {
(false, Some(o)) => write!(f, "{} → {} ⊢ {}", input, o, ty),
(false, None) => write!(f, "{} ⊢ {}", input, ty),
(true, Some(o)) => write!(f, "{} → {} ⊢ {}", o.name, o, ty),
(true, None) => write!(f, "{}", ty),
}
}
}
ConclusionKind::ContextLookup(_ctx, var) => write!(f, "lookup({})", var),
}
}
}
impl fmt::Display for TypingRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.premises.is_empty() {
write!(f, "[{}] {}", self.name, self.conclusion)
} else {
let premises: Vec<String> = self.premises.iter().map(|p| p.to_string()).collect();
write!(
f,
"[{}] {} ⇒ {}",
self.name,
premises.join(", "),
self.conclusion
)
}
}
}