use super::{Pattern, Template, MacroTransformer};
use crate::ast::Expr;
use crate::diagnostics::{Error, Result, Spanned};
use crate::eval::Environment;
use std::collections::HashSet;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub struct SyntaxRulesTransformer {
pub literals: Vec<String>,
pub rules: Vec<SyntaxRule>,
pub name: Option<String>,
pub definition_env: Rc<Environment>,
pub custom_ellipsis: Option<String>,
pub srfi_149_mode: bool,
}
#[derive(Debug, Clone)]
pub struct SyntaxRule {
pub pattern: Pattern,
pub template: Template,
}
pub fn parse_syntax_rules(
expr: &Spanned<Expr>,
definition_env: Rc<Environment>,
) -> Result<SyntaxRulesTransformer> {
match &expr.inner {
Expr::Application { operator, operands } => {
if let Expr::Identifier(name) = &operator.inner {
if name != "syntax-rules" {
return Err(Box::new(Error::macro_error(
format!("Expected syntax-rules, got {name}"),
operator.span,
)));
}
} else {
return Err(Box::new(Error::macro_error(
"syntax-rules must be called as a function".to_string(),
operator.span,
)));
}
if operands.len() < 2 {
return Err(Box::new(Error::macro_error(
"syntax-rules requires at least literals list and one rule".to_string(),
expr.span,
)));
}
let (custom_ellipsis, literals_index) = if operands.len() >= 3 {
if let Some(ellipsis) = parse_custom_ellipsis(&operands[0])? {
(Some(ellipsis), 1) } else {
(None, 0) }
} else {
(None, 0) };
let literals = parse_literals_list(&operands[literals_index])?;
let mut rules = Vec::new();
let ellipsis_token = custom_ellipsis.as_deref().unwrap_or("...");
let srfi_149_mode = true; for rule_expr in &operands[literals_index + 1..] {
let rule = parse_syntax_rule_with_mode(rule_expr, &literals, ellipsis_token, srfi_149_mode)?;
rules.push(rule);
}
if rules.is_empty() {
return Err(Box::new(Error::macro_error(
"syntax-rules must have at least one rule".to_string(),
expr.span,
)));
}
Ok(SyntaxRulesTransformer {
literals,
rules,
name: None,
definition_env,
custom_ellipsis,
srfi_149_mode: true, })
}
_ => Err(Box::new(Error::macro_error(
"syntax-rules must be a function application".to_string(),
expr.span,
))),
}
}
fn parse_custom_ellipsis(expr: &Spanned<Expr>) -> Result<Option<String>> {
match &expr.inner {
Expr::Identifier(name) => {
Ok(Some(name.clone()))
}
_ => Ok(None), }
}
fn parse_literals_list(expr: &Spanned<Expr>) -> Result<Vec<String>> {
match &expr.inner {
Expr::List(elements) if elements.is_empty() => Ok(Vec::new()),
Expr::List(elements) => {
let mut literals = Vec::new();
for element in elements {
match &element.inner {
Expr::Identifier(name) => literals.push(name.clone()),
_ => return Err(Box::new(Error::macro_error(
"Literals must be identifiers".to_string(),
element.span,
))),
}
}
Ok(literals)
}
Expr::Application { operands, .. } => {
let mut literals = Vec::new();
for operand in operands {
match &operand.inner {
Expr::Identifier(name) => literals.push(name.clone()),
_ => return Err(Box::new(Error::macro_error(
"Literals must be identifiers".to_string(),
operand.span,
))),
}
}
Ok(literals)
}
_ => Err(Box::new(Error::macro_error(
"Expected list of literal identifiers".to_string(),
expr.span,
))),
}
}
fn parse_syntax_rule(
expr: &Spanned<Expr>,
literals: &[String],
ellipsis_token: &str,
) -> Result<SyntaxRule> {
parse_syntax_rule_with_mode(expr, literals, ellipsis_token, true)
}
fn parse_syntax_rule_with_mode(
expr: &Spanned<Expr>,
literals: &[String],
ellipsis_token: &str,
srfi_149_mode: bool,
) -> Result<SyntaxRule> {
match &expr.inner {
Expr::List(elements) if elements.len() == 2 => {
let pattern = parse_pattern(&elements[0], literals, ellipsis_token)?;
let mut template = parse_template(&elements[1], ellipsis_token)?;
if srfi_149_mode {
let pattern_depth = pattern.ellipsis_depth();
if template.needs_extra_ellipses(pattern_depth) {
template = template.with_extra_ellipses(pattern_depth);
}
let pattern_var_depths = pattern.variable_depths();
template.resolve_ambiguities(&pattern_var_depths);
}
Ok(SyntaxRule { pattern, template })
}
Expr::Application { operands, .. } if operands.len() == 2 => {
let pattern = parse_pattern(&operands[0], literals, ellipsis_token)?;
let mut template = parse_template(&operands[1], ellipsis_token)?;
if srfi_149_mode {
let pattern_depth = pattern.ellipsis_depth();
if template.needs_extra_ellipses(pattern_depth) {
template = template.with_extra_ellipses(pattern_depth);
}
let pattern_var_depths = pattern.variable_depths();
template.resolve_ambiguities(&pattern_var_depths);
}
Ok(SyntaxRule { pattern, template })
}
_ => Err(Box::new(Error::macro_error(
"Syntax rule must be (pattern template)".to_string(),
expr.span,
))),
}
}
fn parse_pattern(expr: &Spanned<Expr>, literals: &[String], ellipsis_token: &str) -> Result<Pattern> {
match &expr.inner {
Expr::Identifier(name) => {
if literals.contains(name) {
Ok(Pattern::Identifier(name.clone()))
} else {
Ok(Pattern::Variable(name.clone()))
}
}
Expr::Literal(lit) => Ok(Pattern::Literal(lit.clone())),
Expr::Keyword(kw) => Ok(Pattern::Keyword(kw.clone())),
Expr::List(elements) => parse_list_pattern(elements, literals, ellipsis_token),
Expr::Application { operator, operands } => {
let mut all_elements = vec![(**operator).clone()];
all_elements.extend(operands.iter().cloned());
parse_list_pattern(&all_elements, literals, ellipsis_token)
}
Expr::Pair { car, cdr } => {
let car_pattern = parse_pattern(car, literals, ellipsis_token)?;
let cdr_pattern = parse_pattern(cdr, literals, ellipsis_token)?;
Ok(Pattern::Pair {
car: Box::new(car_pattern),
cdr: Box::new(cdr_pattern),
})
}
_ => Err(Box::new(Error::macro_error(
format!("Unsupported pattern type: {:?}", expr.inner),
expr.span,
))),
}
}
fn parse_list_pattern(
elements: &[Spanned<Expr>],
literals: &[String],
ellipsis_token: &str,
) -> Result<Pattern> {
if elements.is_empty() {
return Ok(Pattern::Nil);
}
let mut patterns = Vec::new();
let mut i = 0;
while i < elements.len() {
if i + 1 < elements.len() {
if let Expr::Identifier(name) = &elements[i + 1].inner {
if name == ellipsis_token {
let ellipsis_pattern = parse_pattern(&elements[i], literals, ellipsis_token)?;
let mut rest_patterns = Vec::new();
for rest_elem in &elements[i + 2..] {
rest_patterns.push(parse_pattern(rest_elem, literals, ellipsis_token)?);
}
let rest = if rest_patterns.is_empty() {
None
} else if rest_patterns.len() == 1 {
Some(Box::new(rest_patterns.into_iter().next().unwrap()))
} else {
Some(Box::new(Pattern::List(rest_patterns)))
};
return Ok(Pattern::Ellipsis {
patterns,
ellipsis_pattern: Box::new(ellipsis_pattern),
rest,
});
}
}
}
patterns.push(parse_pattern(&elements[i], literals, ellipsis_token)?);
i += 1;
}
Ok(Pattern::List(patterns))
}
fn parse_template(expr: &Spanned<Expr>, ellipsis_token: &str) -> Result<Template> {
match &expr.inner {
Expr::Identifier(name) => Ok(Template::Variable(name.clone())),
Expr::Literal(lit) => Ok(Template::Literal(lit.clone())),
Expr::Keyword(kw) => Ok(Template::Keyword(kw.clone())),
Expr::List(elements) => parse_list_template(elements, ellipsis_token),
Expr::Application { operator, operands } => {
let mut all_elements = vec![(**operator).clone()];
all_elements.extend(operands.iter().cloned());
parse_list_template(&all_elements, ellipsis_token)
}
Expr::Pair { car, cdr } => {
let car_template = parse_template(car, ellipsis_token)?;
let cdr_template = parse_template(cdr, ellipsis_token)?;
Ok(Template::Pair {
car: Box::new(car_template),
cdr: Box::new(cdr_template),
})
}
_ => Err(Box::new(Error::macro_error(
format!("Unsupported template type: {:?}", expr.inner),
expr.span,
))),
}
}
fn parse_list_template(elements: &[Spanned<Expr>], ellipsis_token: &str) -> Result<Template> {
if elements.is_empty() {
return Ok(Template::Nil);
}
let mut templates = Vec::new();
let mut i = 0;
while i < elements.len() {
if i + 1 < elements.len() {
if let Expr::Identifier(name) = &elements[i + 1].inner {
if name == ellipsis_token {
let ellipsis_template = parse_template(&elements[i], ellipsis_token)?;
let mut depth = 1;
let mut next_pos = i + 2;
while next_pos < elements.len() {
if let Expr::Identifier(next_name) = &elements[next_pos].inner {
if next_name == ellipsis_token {
depth += 1;
next_pos += 1;
} else {
break;
}
} else {
break;
}
}
let mut rest_templates = Vec::new();
for rest_elem in &elements[next_pos..] {
rest_templates.push(parse_template(rest_elem, ellipsis_token)?);
}
let rest = if rest_templates.is_empty() {
None
} else if rest_templates.len() == 1 {
Some(rest_templates.into_iter().next().unwrap())
} else {
Some(Template::List(rest_templates))
};
return if depth == 1 {
Ok(Template::Ellipsis {
templates,
ellipsis_template: Box::new(ellipsis_template),
rest: rest.map(Box::new),
})
} else {
Ok(Template::NestedEllipsis {
templates,
nested_template: Box::new(ellipsis_template),
depth,
rest: rest.map(Box::new),
})
};
}
}
}
templates.push(parse_template(&elements[i], ellipsis_token)?);
i += 1;
}
Ok(Template::List(templates))
}
pub fn syntax_rules_to_macro_transformer(
syntax_rules: SyntaxRulesTransformer,
) -> MacroTransformer {
let primary_rule = syntax_rules.rules.first().cloned().unwrap_or(SyntaxRule {
pattern: Pattern::Wildcard,
template: Template::Nil,
});
MacroTransformer {
pattern: primary_rule.pattern,
template: primary_rule.template,
definition_env: syntax_rules.definition_env,
name: syntax_rules.name,
source: None,
}
}
pub fn expand_syntax_rules(
transformer: &SyntaxRulesTransformer,
input: &Spanned<Expr>,
) -> Result<Spanned<Expr>> {
for rule in &transformer.rules {
if let Ok(bindings) = rule.pattern.match_expr(input) {
let expanded = rule.template.expand(&bindings, input.span)?;
return Ok(expanded);
}
}
Err(Box::new(Error::macro_error(
"No pattern matched in syntax-rules".to_string(),
input.span,
)))
}
pub fn validate_pattern(pattern: &Pattern, literals: &[String]) -> Result<()> {
let literal_set: HashSet<_> = literals.iter().collect();
validate_pattern_inner(pattern, &literal_set, &mut HashSet::new())
}
fn validate_pattern_inner(
pattern: &Pattern,
literals: &HashSet<&String>,
bound_vars: &mut HashSet<String>,
) -> Result<()> {
match pattern {
Pattern::Variable(name) => {
if literals.contains(&name) {
return Err(Box::new(Error::macro_error(
format!("Variable {name} conflicts with literal"),
crate::diagnostics::Span::new(0, 0),
)));
}
if bound_vars.contains(name) {
return Err(Box::new(Error::macro_error(
format!("Variable {name} bound multiple times"),
crate::diagnostics::Span::new(0, 0),
)));
}
bound_vars.insert(name.clone());
Ok(())
}
Pattern::List(patterns) => {
for pat in patterns {
validate_pattern_inner(pat, literals, bound_vars)?;
}
Ok(())
}
Pattern::Ellipsis { patterns, ellipsis_pattern, rest } => {
for pat in patterns {
validate_pattern_inner(pat, literals, bound_vars)?;
}
let mut ellipsis_vars = HashSet::new();
validate_pattern_inner(ellipsis_pattern, literals, &mut ellipsis_vars)?;
for var in &ellipsis_vars {
if bound_vars.contains(var) {
return Err(Box::new(Error::macro_error(
format!("Ellipsis variable {var} conflicts with outer variable"),
crate::diagnostics::Span::new(0, 0),
)));
}
}
if let Some(rest_pat) = rest {
validate_pattern_inner(rest_pat, literals, bound_vars)?;
}
Ok(())
}
Pattern::Pair { car, cdr } => {
validate_pattern_inner(car, literals, bound_vars)?;
validate_pattern_inner(cdr, literals, bound_vars)
}
Pattern::Or(alternatives) => {
let mut first_vars: Option<HashSet<String>> = None;
for alt in alternatives {
let mut alt_vars = HashSet::new();
validate_pattern_inner(alt, literals, &mut alt_vars)?;
if let Some(ref expected_vars) = first_vars {
if alt_vars != *expected_vars {
return Err(Box::new(Error::macro_error(
"Alternative patterns must bind same variables".to_string(),
crate::diagnostics::Span::new(0, 0),
)));
}
} else {
first_vars = Some(alt_vars.clone());
}
bound_vars.extend(alt_vars);
}
Ok(())
}
Pattern::And(conjuncts) => {
for conj in conjuncts {
validate_pattern_inner(conj, literals, bound_vars)?;
}
Ok(())
}
Pattern::Not(sub_pattern) => {
let mut dummy_vars = HashSet::new();
validate_pattern_inner(sub_pattern, literals, &mut dummy_vars)?;
if !dummy_vars.is_empty() {
return Err(Box::new(Error::macro_error(
"Negative patterns cannot bind variables".to_string(),
crate::diagnostics::Span::new(0, 0),
)));
}
Ok(())
}
_ => Ok(()), }
}
pub fn validate_template(
template: &Template,
pattern_vars: &HashSet<String>,
ellipsis_vars: &HashSet<String>,
) -> Result<()> {
match template {
Template::Variable(name) => {
if !pattern_vars.contains(name) && !ellipsis_vars.contains(name) {
return Err(Box::new(Error::macro_error(
format!("Template variable {name} not bound by pattern"),
crate::diagnostics::Span::new(0, 0),
)));
}
Ok(())
}
Template::List(templates) => {
for tmpl in templates {
validate_template(tmpl, pattern_vars, ellipsis_vars)?;
}
Ok(())
}
Template::Ellipsis { templates, ellipsis_template, rest } => {
for tmpl in templates {
validate_template(tmpl, pattern_vars, ellipsis_vars)?;
}
validate_template(ellipsis_template, &HashSet::new(), ellipsis_vars)?;
if let Some(rest_tmpl) = rest {
validate_template(rest_tmpl, pattern_vars, ellipsis_vars)?;
}
Ok(())
}
Template::Pair { car, cdr } => {
validate_template(car, pattern_vars, ellipsis_vars)?;
validate_template(cdr, pattern_vars, ellipsis_vars)
}
Template::Conditional { condition, then_branch, else_branch } => {
validate_template(condition, pattern_vars, ellipsis_vars)?;
validate_template(then_branch, pattern_vars, ellipsis_vars)?;
if let Some(else_tmpl) = else_branch {
validate_template(else_tmpl, pattern_vars, ellipsis_vars)?;
}
Ok(())
}
Template::Transform { argument, .. } => {
validate_template(argument, pattern_vars, ellipsis_vars)
}
Template::Splice(name) => {
if !ellipsis_vars.contains(name) {
return Err(Box::new(Error::macro_error(
format!("Splice variable {name} not bound as ellipsis variable"),
crate::diagnostics::Span::new(0, 0),
)));
}
Ok(())
}
_ => Ok(()), }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::diagnostics::Span;
fn make_spanned<T>(value: T) -> Spanned<T> {
Spanned::new(value, Span::new(0, 1))
}
#[test]
fn test_parse_literals_list() {
let expr = make_spanned(Expr::List(vec![]));
let literals = parse_literals_list(&expr).unwrap();
assert!(literals.is_empty());
let expr = make_spanned(Expr::List(vec![
make_spanned(Expr::Identifier("else".to_string())),
make_spanned(Expr::Identifier("=>".to_string())),
]));
let literals = parse_literals_list(&expr).unwrap();
assert_eq!(literals, vec!["else", "=>"]);
}
#[test]
fn test_parse_simple_pattern() {
let literals = vec!["else".to_string()];
let expr = make_spanned(Expr::Identifier("x".to_string()));
let pattern = parse_pattern(&expr, &literals, "...").unwrap();
assert!(matches!(pattern, Pattern::Variable(_)));
let expr = make_spanned(Expr::Identifier("else".to_string()));
let pattern = parse_pattern(&expr, &literals, "...").unwrap();
assert!(matches!(pattern, Pattern::Identifier(_)));
let expr = make_spanned(Expr::Literal(crate::ast::Literal::Number(42.0)));
let pattern = parse_pattern(&expr, &literals, "...").unwrap();
assert!(matches!(pattern, Pattern::Literal(_)));
}
#[test]
fn test_parse_list_pattern() {
let literals = vec![];
let elements = vec![
make_spanned(Expr::Identifier("if".to_string())),
make_spanned(Expr::Identifier("test".to_string())),
make_spanned(Expr::Identifier("then".to_string())),
];
let pattern = parse_list_pattern(&elements, &literals, "...").unwrap();
match pattern {
Pattern::List(patterns) => {
assert_eq!(patterns.len(), 3);
assert!(matches!(patterns[0], Pattern::Variable(_)));
}
_ => panic!("Expected list pattern"),
}
}
#[test]
fn test_parse_ellipsis_pattern() {
let literals = vec![];
let elements = vec![
make_spanned(Expr::Identifier("x".to_string())),
make_spanned(Expr::Identifier("y".to_string())),
make_spanned(Expr::Identifier("...".to_string())),
];
let pattern = parse_list_pattern(&elements, &literals, "...").unwrap();
match pattern {
Pattern::Ellipsis { patterns, ellipsis_pattern, rest } => {
assert_eq!(patterns.len(), 1);
if let Pattern::Variable(_) = ellipsis_pattern.as_ref() {
} else {
panic!("Expected variable pattern");
}
assert!(rest.is_none());
}
_ => panic!("Expected ellipsis pattern"),
}
}
#[test]
fn test_validate_pattern() {
let literals = vec!["else".to_string()];
let pattern = Pattern::List(vec![
Pattern::Variable("x".to_string()),
Pattern::Identifier("else".to_string()),
]);
assert!(validate_pattern(&pattern, &literals).is_ok());
let pattern = Pattern::Variable("else".to_string());
assert!(validate_pattern(&pattern, &literals).is_err());
}
#[test]
fn test_validate_template() {
let mut pattern_vars = HashSet::new();
pattern_vars.insert("x".to_string());
let ellipsis_vars = HashSet::new();
let template = Template::List(vec![
Template::Identifier("if".to_string()),
Template::Variable("x".to_string()),
]);
assert!(validate_template(&template, &pattern_vars, &ellipsis_vars).is_ok());
let template = Template::Variable("y".to_string());
assert!(validate_template(&template, &pattern_vars, &ellipsis_vars).is_err());
}
}
impl SyntaxRulesTransformer {
pub fn with_srfi_149_mode(
mut self,
enable_srfi_149: bool,
) -> Self {
self.srfi_149_mode = enable_srfi_149;
self
}
pub fn is_srfi_149_enabled(&self) -> bool {
self.srfi_149_mode
}
}