From e9469f6e7c4ff04cdc939030c5af4c1516a0271b Mon Sep 17 00:00:00 2001 From: Collin Williams <96917990+bluedragon1221@users.noreply.github.com> Date: Wed, 31 Dec 2025 07:44:54 -0600 Subject: progress --- .gitignore | 1 + Cargo.lock | 25 ++++++ Cargo.toml | 7 ++ base.rules | 5 ++ src/def_rules.rs | 24 ++++++ src/lib.rs | 3 + src/main.rs | 29 +++++++ src/match_rules.rs | 103 +++++++++++++++++++++++ src/sexpr.rs | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test_rule | 1 + 10 files changed, 440 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 base.rules create mode 100644 src/def_rules.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/match_rules.rs create mode 100644 src/sexpr.rs create mode 100644 test_rule diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4e6784c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "difx" +version = "0.1.0" +dependencies = [ + "nom", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a566ccb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "difx" +version = "0.1.0" +edition = "2024" + +[dependencies] +nom = "8.0.0" diff --git a/base.rules b/base.rules new file mode 100644 index 0000000..aa14298 --- /dev/null +++ b/base.rules @@ -0,0 +1,5 @@ +(def-rule (+ @a) @a) +(def-rule (* @a) @a) +(def-rule (+ @a @a .) (+ (* 2 @a) .)) +(def-rule (+ @a (* $i @a) .) (+ (* (+ $i 1) @a) .)) +(def-rule (+ (* $i @a) (* $j @a) .) (+ (* (+ $i $j) @a) .)) diff --git a/src/def_rules.rs b/src/def_rules.rs new file mode 100644 index 0000000..a2ba36e --- /dev/null +++ b/src/def_rules.rs @@ -0,0 +1,24 @@ +use crate::sexpr::{self, Atom, Expr}; + +#[derive(Debug, Clone)] +pub struct Rule { + pub lhs: Expr, + pub rhs: Expr +} + +impl TryFrom for Rule { + type Error = (); + fn try_from(value: Expr) -> Result { + if let Expr::Application(v) = value && v[0] == Expr::Atom(Atom::Builtin(String::from("def-rule"))) && v.len() == 3 { + Ok(Rule { + lhs: v[1].clone(), + rhs: v[2].clone() + }) + } else {Err(())} + } +} + +pub fn parse_def_rules(input: String) -> Vec { + let exprs = sexpr::Parser::new(&input).parse_all(); + exprs.iter().filter_map(|e| e.clone().try_into().ok()).collect() +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1bb3e41 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod sexpr; +pub mod def_rules; +pub mod match_rules; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..911948d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,29 @@ +use difx::sexpr::Parser; +use difx::match_rules::{self, Bindings}; + +fn main() { + let input = include_str!("../test_rule"); + let rules = difx::def_rules::parse_def_rules(input.to_string()); + // println!("{:#?}", rules); + + let pat = rules[0].clone().lhs; + println!("{:?}", pat); + + loop { + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .unwrap(); + + let mut parser = Parser::new(&input); + let parsed = parser.parse_one(); + + let mut bindings = Bindings::default(); + + if match_rules::matches(&pat, &parsed, &mut bindings) { + println!("{:?}", &bindings); + println!("MATCH"); + } + } +} + diff --git a/src/match_rules.rs b/src/match_rules.rs new file mode 100644 index 0000000..938ee08 --- /dev/null +++ b/src/match_rules.rs @@ -0,0 +1,103 @@ +use std::collections::HashMap; + +use crate::sexpr::{Atom, Expr, Variable, VariableType}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum BindValue { + Integer(i64), + Expr(Expr), + Ellipsis(Vec) +} + +#[derive(Default)] +pub struct Bindings<'b>(HashMap<&'b str, BindValue>); + +impl<'b> Bindings<'b> { + pub fn check_or_insert(&mut self, index: &'b String, bind_value: BindValue) -> bool { + if let Some(existing) = self.0.get(index.as_str()) { + existing == &bind_value + } else { + self.0.insert(index, bind_value); + true + } + } +} + +impl std::fmt::Debug for Bindings<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +pub fn args_match<'b>(p_args: &'b [Expr], args: &'b [Expr], bindings: &mut Bindings<'b>) -> bool { + const ELLIPSIS: Expr = Expr::Atom(Atom::Ellipsis); + + if p_args.contains(&ELLIPSIS) { + let mut sorted_p: Vec<&Expr> = p_args.iter() + .filter(|p| **p != ELLIPSIS) + .collect(); + + let mut sorted_a: Vec<&Expr> = args.iter().collect(); + + if sorted_p.len() != sorted_a.len() { + return false; + } + + sorted_p.sort(); + sorted_a.sort(); + + std::iter::zip(sorted_p, sorted_a).all(|(p, a)| { + matches(p, a, bindings) + }) + } else { + if p_args.len() != args.len() { + return false; + } + std::iter::zip(p_args.iter(), args.iter()).all(|(a, b)| { + matches(a, b, bindings) + }) + } +} + +pub fn matches<'b>(p: &'b Expr, expr: &'b Expr, bindings: &mut Bindings<'b>) -> bool { + match expr { + Expr::Atom(Atom::Int(i)) => { + match p { + Expr::Atom(Atom::Int(pi)) => i == pi, + Expr::Atom(Atom::Variable(Variable { r#type: e @ (VariableType::Integer | VariableType::Expr), index })) => { + bindings.check_or_insert(index, match e { + VariableType::Integer => BindValue::Integer(*i), + VariableType::Expr => BindValue::Expr(Expr::Atom(Atom::Int(*i))) + }) + } + _ => false + } + } + Expr::Atom(Atom::Builtin(s)) => { + match p { + Expr::Atom(Atom::Builtin(ps)) => s == ps, + Expr::Atom(Atom::Variable(Variable { r#type: VariableType::Expr, index })) => { + bindings.check_or_insert(index, BindValue::Expr(Expr::Atom(Atom::Builtin(s.to_string())))) + } + _ => false + } + } + Expr::Application(exprs) => { + if let Expr::Atom(Atom::Builtin(f)) = &exprs[0] && + let Expr::Application(p_exprs) = p && + let Expr::Atom(Atom::Builtin(pf)) = &p_exprs[0] { + f == pf && args_match(&p_exprs[1..], &exprs[1..], bindings) + } else if exprs.len() == 1 { + matches(p, &exprs[0], bindings) + } else if let Expr::Atom(Atom::Variable(Variable { r#type: VariableType::Expr, index })) = p { + bindings.check_or_insert(index, BindValue::Expr(expr.clone())) + } else { + false + } + }, + Expr::Atom(Atom::Variable(_)) | Expr::Atom(Atom::Ellipsis) => { + // These should only appear in patterns, not in expressions being matched + false + } + } +} diff --git a/src/sexpr.rs b/src/sexpr.rs new file mode 100644 index 0000000..557bc5d --- /dev/null +++ b/src/sexpr.rs @@ -0,0 +1,242 @@ +use std::cmp::Ordering; +use std::iter::Peekable; +use std::str::{Chars, FromStr}; + +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Variable { + pub r#type: VariableType, + pub index: String +} + +impl FromStr for Variable { + type Err = (); + fn from_str(s: &str) -> Result { + match s.chars().nth(0) { + Some('$') => Ok(Variable { + r#type: VariableType::Integer, + index: s[1..].to_string() + }), + Some('@') => Ok(Variable { + r#type: VariableType::Expr, + index: s[1..].to_string() + }), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum VariableType { + Integer, + Expr +} + +#[derive(Debug, PartialEq, Clone, Eq, Hash)] +pub enum Atom { + Int(i64), + Builtin(String), + Variable(Variable), + Ellipsis, +} + +impl PartialOrd for Atom { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Atom { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (Atom::Int(i1), Atom::Int(i2)) => i1.cmp(i2), + (Atom::Int(_), _) => Ordering::Less, + (_, Atom::Int(_)) => Ordering::Greater, + + (Atom::Builtin(b1), Atom::Builtin(b2)) => b1.cmp(b2), + (Atom::Builtin(_), Atom::Variable(_)) => Ordering::Less, + (Atom::Builtin(_), Atom::Ellipsis) => Ordering::Less, + + (Atom::Variable(v1), Atom::Variable(v2)) => { + v1.index.cmp(&v2.index) + } + (Atom::Variable(_), Atom::Builtin(_)) => Ordering::Greater, + (Atom::Variable(_), Atom::Ellipsis) => Ordering::Less, + + (Atom::Ellipsis, Atom::Ellipsis) => Ordering::Equal, + (Atom::Ellipsis, _) => Ordering::Greater, + } + } +} + +impl std::fmt::Display for Atom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Atom::Int(i) => write!(f, "{}", i), + Atom::Builtin(b) => write!(f, "{}", b), + Atom::Variable(v) => write!(f, "{}{}", + match v.r#type { + VariableType::Integer => "$", + VariableType::Expr => "@", + }, + v.index + ), + Atom::Ellipsis => write!(f, "."), + } + } +} + +#[derive(Debug, PartialEq, Clone, Eq, Hash)] +pub enum Expr { + Atom(Atom), + Application(Vec), +} + +impl PartialOrd for Expr { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Expr { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (Expr::Atom(_), Expr::Application(_)) => Ordering::Less, + (Expr::Application(_), Expr::Atom(_)) => Ordering::Greater, + (Expr::Atom(a1), Expr::Atom(a2)) => a1.cmp(a2), + (Expr::Application(v1), Expr::Application(v2)) => { + v1.len().cmp(&v2.len()) + .then_with(|| v1.cmp(v2)) + } + } + } +} + +impl std::fmt::Display for Expr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Expr::Atom(a) => write!(f, "{}", a), + Expr::Application(args) => { + let pieces: Vec = args.into_iter().map(ToString::to_string).collect(); + write!(f, "({})", pieces.join(" ")) + } + } + } +} + +#[derive(Debug, PartialEq, Eq)] +enum ParseResult { + Expr(Expr), + Eof, +} + +pub struct Parser<'a> { + input: Peekable>, +} + +impl<'a> Parser<'a> { + pub fn new(input: &'a str) -> Self { + Parser { + input: input.chars().peekable(), + } + } + + pub fn parse_all(&mut self) -> Vec { + let mut expressions = Vec::new(); + loop { + match self.parse_one_optional() { + ParseResult::Expr(expr) => expressions.push(expr), + ParseResult::Eof => break, + } + } + expressions + } + + fn parse_one_optional(&mut self) -> ParseResult { + self.skip_whitespace(); + + match self.input.peek() { + Some('(') => ParseResult::Expr(self.parse_list()), + Some(_) => ParseResult::Expr(self.parse_atom()), + None => ParseResult::Eof, + } + } + + pub fn parse_one(&mut self) -> Expr { + match self.parse_one_optional() { + ParseResult::Expr(expr) => expr, + ParseResult::Eof => panic!("Unexpected end of input during parse!"), + } + } + + fn skip_whitespace(&mut self) { + while let Some(&c) = self.input.peek() { + if c.is_whitespace() { + self.input.next(); + } else { + break; + } + } + } + + fn parse_list(&mut self) -> Expr { + if self.input.next() != Some('(') { + panic!("Parser error: Expected '('"); + } + + let mut expressions = Vec::new(); + + loop { + self.skip_whitespace(); + + match self.input.peek() { + Some(')') => { + self.input.next(); + break; + } + None => panic!("Parser error: Unexpected EOF, unclosed list"), + Some(_) => { + match self.parse_one_optional() { + ParseResult::Expr(expr) => expressions.push(expr), + ParseResult::Eof => panic!("Parser error: Unexpected EOF after space inside list"), + } + } + } + } + + Expr::Application(expressions) + } + + fn parse_atom(&mut self) -> Expr { + let mut buffer = String::new(); + + while let Some(&c) = self.input.peek() { + if c.is_whitespace() || c == '(' || c == ')' { + break; + } + buffer.push(c); + self.input.next(); + } + + if buffer.is_empty() { + panic!("Parser error: Tried to parse an atom, but it was empty."); + } + + // Check for ellipsis + if buffer == "." { + return Expr::Atom(Atom::Ellipsis); + } + + // Check for variable + if let Ok(v) = buffer.parse::() { + return Expr::Atom(Atom::Variable(v)); + } + + // Check for integer + if let Ok(i) = buffer.parse::() { + return Expr::Atom(Atom::Int(i)); + } + + // Default to builtin + Expr::Atom(Atom::Builtin(buffer)) + } +} diff --git a/test_rule b/test_rule new file mode 100644 index 0000000..5658665 --- /dev/null +++ b/test_rule @@ -0,0 +1 @@ +(def-rule (+ 3 (* $i @a) .) _) -- cgit v1.3.1