conlang: Unify binding operations!

This breaks out the pattern matching/unification algorithm from cl-interpret/interpret.rs to cl-interpret/pattern.rs

TODO: pattern destructuring in const, static :^)
This commit is contained in:
2025-02-22 05:16:37 -06:00
parent 7a8da33de9
commit e3d94d8949
14 changed files with 202 additions and 171 deletions

View File

@@ -319,7 +319,7 @@ impl std::fmt::Display for ConValue {
let (name, map) = parts.as_ref();
use std::fmt::Write;
if !name.is_empty() {
write!(f, "{name}: ")?;
write!(f, "{name} ")?;
}
let mut f = f.delimit_with("{", "\n}");
for (k, v) in map.iter() {

View File

@@ -2,7 +2,7 @@
use collect_upvars::collect_upvars;
use super::{Callable, ConValue, Environment, Error, IResult, Interpret};
use super::{pattern, Callable, ConValue, Environment, Error, IResult, Interpret};
use cl_ast::{Function as FnDecl, Param, Sym};
use std::{
cell::{Ref, RefCell},
@@ -70,8 +70,10 @@ impl Callable for Function {
// TODO: completely refactor data storage
let mut frame = env.frame("fn args");
for (Param { mutability: _, name }, value) in bind.iter().zip(args) {
frame.insert(*name, Some(value.clone()));
for (Param { mutability: _, bind }, value) in bind.iter().zip(args) {
for (name, value) in pattern::substitution(bind, value.clone())? {
frame.insert(*name, Some(value));
}
}
let res = body.interpret(&mut frame);
drop(frame);

View File

@@ -67,8 +67,8 @@ impl<'a> Visit<'a> for CollectUpvars<'_> {
fn visit_function(&mut self, f: &'a cl_ast::Function) {
let Function { name: _, sign: _, bind, body } = f;
// parameters can never be upvars
for Param { mutability: _, name } in bind {
self.bind_name(name);
for Param { mutability: _, bind } in bind {
self.visit_pattern(bind);
}
if let Some(body) = body {
self.visit_expr(body);

View File

@@ -139,7 +139,7 @@ impl Interpret for Struct {
.enumerate()
.map(|(idx, _)| Param {
mutability: Mutability::Not,
name: idx.to_string().into(),
bind: Pattern::Name(idx.to_string().into()),
})
.collect(),
body: None,
@@ -300,12 +300,12 @@ impl Interpret for Let {
let Let { mutable: _, name, ty: _, init } = self;
match init.as_ref().map(|i| i.interpret(env)).transpose()? {
Some(value) => {
for (name, value) in assignment::pattern_substitution(name, value)? {
for (name, value) in pattern::substitution(name, value)? {
env.insert(*name, Some(value));
}
}
None => {
for name in assignment::pattern_variables(name) {
for name in pattern::variables(name) {
env.insert(*name, None);
}
}
@@ -319,7 +319,7 @@ impl Interpret for Match {
let Self { scrutinee, arms } = self;
let scrutinee = scrutinee.interpret(env)?;
for MatchArm(pat, expr) in arms {
if let Ok(substitution) = assignment::pattern_substitution(pat, scrutinee.clone()) {
if let Ok(substitution) = pattern::substitution(pat, scrutinee.clone()) {
let mut env = env.frame("match");
for (name, value) in substitution {
env.insert(*name, Some(value));
@@ -337,136 +337,11 @@ mod assignment {
use std::collections::HashMap;
type Namespace = HashMap<Sym, Option<ConValue>>;
/// Gets the path variables in the given Pattern
pub fn pattern_variables(pat: &Pattern) -> Vec<&Sym> {
fn patvars<'p>(set: &mut Vec<&'p Sym>, pat: &'p Pattern) {
match pat {
Pattern::Name(name) if &**name == "_" => {}
Pattern::Name(name) => set.push(name),
Pattern::Literal(_) => {}
Pattern::Ref(_, pattern) => patvars(set, pattern),
Pattern::Tuple(patterns) | Pattern::Array(patterns) => {
patterns.iter().for_each(|pat| patvars(set, pat))
}
Pattern::Struct(_path, items) => {
items.iter().for_each(|(name, pat)| match pat {
Some(pat) => patvars(set, pat),
None => set.push(name),
});
}
Pattern::TupleStruct(_path, items) => {
items.iter().for_each(|pat| patvars(set, pat));
}
}
}
let mut set = Vec::new();
patvars(&mut set, pat);
set
}
/// Appends a substitution to the provided table
pub fn append_sub<'pat>(
sub: &mut HashMap<&'pat Sym, ConValue>,
pat: &'pat Pattern,
value: ConValue,
) -> IResult<()> {
match (pat, value) {
(Pattern::Array(patterns), ConValue::Array(values))
| (Pattern::Tuple(patterns), ConValue::Tuple(values)) => {
if patterns.len() != values.len() {
Err(Error::ArgNumber { want: patterns.len(), got: values.len() })?
}
for (pat, value) in patterns.iter().zip(Vec::from(values).into_iter()) {
append_sub(sub, pat, value)?;
}
Ok(())
}
(Pattern::Tuple(patterns), ConValue::Empty) if patterns.is_empty() => Ok(()),
(Pattern::Literal(Literal::Bool(a)), ConValue::Bool(b)) => {
(*a == b).then_some(()).ok_or(Error::NotAssignable)
}
(Pattern::Literal(Literal::Char(a)), ConValue::Char(b)) => {
(*a == b).then_some(()).ok_or(Error::NotAssignable)
}
(Pattern::Literal(Literal::Float(a)), ConValue::Float(b)) => (f64::from_bits(*a) == b)
.then_some(())
.ok_or(Error::NotAssignable),
(Pattern::Literal(Literal::Int(a)), ConValue::Int(b)) => {
(b == *a as _).then_some(()).ok_or(Error::NotAssignable)
}
(Pattern::Literal(Literal::String(a)), ConValue::String(b)) => {
(*a == *b).then_some(()).ok_or(Error::NotAssignable)
}
(Pattern::Literal(_), _) => Err(Error::NotAssignable),
(Pattern::Name(name), _) if "_".eq(&**name) => Ok(()),
(Pattern::Name(name), value) => {
sub.insert(name, value);
Ok(())
}
(Pattern::Ref(_, pat), ConValue::Ref(r)) => {
append_sub(sub, pat, Rc::unwrap_or_clone(r))
}
(Pattern::Struct(path, patterns), ConValue::Struct(parts)) => {
let (name, mut values) = *parts;
if !path.ends_with(&name) {
Err(Error::TypeError)?
}
if patterns.len() != values.len() {
return Err(Error::ArgNumber { want: patterns.len(), got: values.len() });
}
for (name, pat) in patterns {
let value = values.remove(name).ok_or(Error::TypeError)?;
match pat {
Some(pat) => append_sub(sub, pat, value)?,
None => {
sub.insert(name, value);
}
}
}
Ok(())
}
(Pattern::TupleStruct(path, patterns), ConValue::TupleStruct(parts)) => {
let (name, values) = *parts;
if !path.ends_with(&name) {
Err(Error::TypeError)?
}
if patterns.len() != values.len() {
Err(Error::ArgNumber { want: patterns.len(), got: values.len() })?
}
for (pat, value) in patterns.iter().zip(Vec::from(values).into_iter()) {
append_sub(sub, pat, value)?;
}
Ok(())
}
(pat, value) => {
eprintln!("Could not match pattern `{pat}` with value `{value}`!");
Err(Error::NotAssignable)
}
}
}
/// Constructs a substitution from a pattern and a value
pub fn pattern_substitution(
pat: &Pattern,
value: ConValue,
) -> IResult<HashMap<&Sym, ConValue>> {
let mut sub = HashMap::new();
append_sub(&mut sub, pat, value)?;
Ok(sub)
}
pub(super) fn pat_assign(env: &mut Environment, pat: &Pattern, value: ConValue) -> IResult<()> {
let mut substitution = HashMap::new();
append_sub(&mut substitution, pat, value)
.map_err(|_| Error::PatFailed(pat.clone().into()))?;
for (path, value) in substitution {
env.insert(*path, Some(value));
for (name, value) in
pattern::substitution(pat, value).map_err(|_| Error::PatFailed(pat.clone().into()))?
{
*env.get_mut(*name)? = Some(value);
}
Ok(())
}
@@ -478,6 +353,7 @@ mod assignment {
match pat {
ExprKind::Member(member) => *addrof_member(env, member)? = value,
ExprKind::Index(index) => *addrof_index(env, index)? = value,
ExprKind::Path(path) => *addrof_path(env, &path.parts)? = Some(value),
_ => Err(Error::NotAssignable)?,
}
Ok(())
@@ -978,7 +854,7 @@ impl Interpret for If {
}
impl Interpret for For {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
let Self { bind: name, cond, pass, fail } = self;
let Self { bind, cond, pass, fail } = self;
let cond = cond.interpret(env)?;
// TODO: A better iterator model
let mut bounds: Box<dyn Iterator<Item = ConValue>> = match &cond {
@@ -990,9 +866,8 @@ impl Interpret for For {
};
loop {
let mut env = env.frame("loop variable");
if let Some(loop_var) = bounds.next() {
let subs = assignment::pattern_substitution(name, loop_var)?;
for (name, value) in subs {
if let Some(value) = bounds.next() {
for (name, value) in pattern::substitution(bind, value)? {
env.insert(*name, Some(value));
}
match pass.interpret(&mut env) {

View File

@@ -25,6 +25,8 @@ pub mod function;
pub mod builtin;
pub mod pattern;
pub mod env;
pub mod error;

View File

@@ -0,0 +1,130 @@
//! Unification algorithm for cl-ast patterns and ConValues
//!
//! [`variables()`] returns a flat list of symbols that are bound by a given pattern
//! [`substitution()`] unifies a ConValue with a pattern, and produces a list of bound names
use crate::{
convalue::ConValue,
error::{Error, IResult},
};
use cl_ast::{Literal, Pattern, Sym};
use std::{collections::HashMap, rc::Rc};
/// Gets the path variables in the given Pattern
pub fn variables(pat: &Pattern) -> Vec<&Sym> {
fn patvars<'p>(set: &mut Vec<&'p Sym>, pat: &'p Pattern) {
match pat {
Pattern::Name(name) if &**name == "_" => {}
Pattern::Name(name) => set.push(name),
Pattern::Literal(_) => {}
Pattern::Ref(_, pattern) => patvars(set, pattern),
Pattern::Tuple(patterns) | Pattern::Array(patterns) => {
patterns.iter().for_each(|pat| patvars(set, pat))
}
Pattern::Struct(_path, items) => {
items.iter().for_each(|(name, pat)| match pat {
Some(pat) => patvars(set, pat),
None => set.push(name),
});
}
Pattern::TupleStruct(_path, items) => {
items.iter().for_each(|pat| patvars(set, pat));
}
}
}
let mut set = Vec::new();
patvars(&mut set, pat);
set
}
/// Appends a substitution to the provided table
pub fn append_sub<'pat>(
sub: &mut HashMap<&'pat Sym, ConValue>,
pat: &'pat Pattern,
value: ConValue,
) -> IResult<()> {
match (pat, value) {
(Pattern::Array(patterns), ConValue::Array(values))
| (Pattern::Tuple(patterns), ConValue::Tuple(values)) => {
if patterns.len() != values.len() {
Err(Error::ArgNumber { want: patterns.len(), got: values.len() })?
}
for (pat, value) in patterns.iter().zip(Vec::from(values).into_iter()) {
append_sub(sub, pat, value)?;
}
Ok(())
}
(Pattern::Tuple(patterns), ConValue::Empty) if patterns.is_empty() => Ok(()),
(Pattern::Literal(Literal::Bool(a)), ConValue::Bool(b)) => {
(*a == b).then_some(()).ok_or(Error::NotAssignable)
}
(Pattern::Literal(Literal::Char(a)), ConValue::Char(b)) => {
(*a == b).then_some(()).ok_or(Error::NotAssignable)
}
(Pattern::Literal(Literal::Float(a)), ConValue::Float(b)) => (f64::from_bits(*a) == b)
.then_some(())
.ok_or(Error::NotAssignable),
(Pattern::Literal(Literal::Int(a)), ConValue::Int(b)) => {
(b == *a as _).then_some(()).ok_or(Error::NotAssignable)
}
(Pattern::Literal(Literal::String(a)), ConValue::String(b)) => {
(*a == *b).then_some(()).ok_or(Error::NotAssignable)
}
(Pattern::Literal(_), _) => Err(Error::NotAssignable),
(Pattern::Name(name), _) if "_".eq(&**name) => Ok(()),
(Pattern::Name(name), value) => {
sub.insert(name, value);
Ok(())
}
(Pattern::Ref(_, pat), ConValue::Ref(r)) => append_sub(sub, pat, Rc::unwrap_or_clone(r)),
(Pattern::Struct(path, patterns), ConValue::Struct(parts)) => {
let (name, mut values) = *parts;
if !path.ends_with(&name) {
Err(Error::TypeError)?
}
if patterns.len() != values.len() {
return Err(Error::ArgNumber { want: patterns.len(), got: values.len() });
}
for (name, pat) in patterns {
let value = values.remove(name).ok_or(Error::TypeError)?;
match pat {
Some(pat) => append_sub(sub, pat, value)?,
None => {
sub.insert(name, value);
}
}
}
Ok(())
}
(Pattern::TupleStruct(path, patterns), ConValue::TupleStruct(parts)) => {
let (name, values) = *parts;
if !path.ends_with(&name) {
Err(Error::TypeError)?
}
if patterns.len() != values.len() {
Err(Error::ArgNumber { want: patterns.len(), got: values.len() })?
}
for (pat, value) in patterns.iter().zip(Vec::from(values).into_iter()) {
append_sub(sub, pat, value)?;
}
Ok(())
}
(pat, value) => {
eprintln!("Could not match pattern `{pat}` with value `{value}`!");
Err(Error::NotAssignable)
}
}
}
/// Constructs a substitution from a pattern and a value
pub fn substitution(pat: &Pattern, value: ConValue) -> IResult<HashMap<&Sym, ConValue>> {
let mut sub = HashMap::new();
append_sub(&mut sub, pat, value)?;
Ok(sub)
}