mimispec 0.2.1

A high-density intent description language for human-AI collaboration
Documentation
use crate::ast::*;
use crate::error::ParseError;
use crate::lexer::TokenKind;
use crate::parser::{BinOp, Parser};

const EXPR_RECURSION_LIMIT: u32 = 256;

impl Parser {
    pub(super) fn parse_condition(&mut self) -> Result<Condition, ParseError> {
        if self.check(&TokenKind::Ellipsis) {
            let keyword_commitment = self.expect_kw(TokenKind::Ellipsis, "`...`")?;
            return Ok(Condition::Structured {
                expr: Expr::Placeholder { keyword_commitment },
            });
        }
        if matches!(self.peek_kind(), Some(TokenKind::String(_))) {
            return Ok(Condition::Natural {
                text: self.fuzzy_string()?,
            });
        }
        let expr = self.parse_expr(0)?;
        Ok(Condition::Structured { expr })
    }

    pub(super) fn parse_expr(&mut self, min_prec: u8) -> Result<Expr, ParseError> {
        self.parse_expr_depth(min_prec, 0)
    }

    fn parse_expr_depth(&mut self, min_prec: u8, depth: u32) -> Result<Expr, ParseError> {
        if depth > EXPR_RECURSION_LIMIT {
            let (line, col) = self.current_pos();
            return Err(ParseError::unexpected_token(
                "expression too deeply nested".into(),
                "expression".into(),
                line,
                col,
            ));
        }
        let mut lhs = self.parse_primary_depth(depth)?;
        loop {
            let (op, prec, right_assoc) = match self.peek_kind() {
                Some(TokenKind::Or) => (BinOp::Or, 1u8, false),
                Some(TokenKind::And) => (BinOp::And, 2u8, false),
                Some(TokenKind::In) => (BinOp::In, 3u8, false),
                Some(TokenKind::EqEq) => (BinOp::Cmp(CompareOp::Eq), 3u8, false),
                Some(TokenKind::NotEq) => (BinOp::Cmp(CompareOp::Ne), 3u8, false),
                Some(TokenKind::Lt) => (BinOp::Cmp(CompareOp::Lt), 3u8, false),
                Some(TokenKind::Gt) => (BinOp::Cmp(CompareOp::Gt), 3u8, false),
                Some(TokenKind::Le) => (BinOp::Cmp(CompareOp::Le), 3u8, false),
                Some(TokenKind::Ge) => (BinOp::Cmp(CompareOp::Ge), 3u8, false),
                Some(TokenKind::Pipe) => (BinOp::BitOr, 4u8, false),
                Some(TokenKind::BitXor) => (BinOp::BitXor, 5u8, false),
                Some(TokenKind::BitAnd) => (BinOp::BitAnd, 6u8, false),
                Some(TokenKind::Shl) => (BinOp::Shl, 7u8, false),
                Some(TokenKind::Shr) => (BinOp::Shr, 7u8, false),
                Some(TokenKind::Plus) => (BinOp::Add, 8u8, false),
                Some(TokenKind::Minus) => (BinOp::Sub, 8u8, false),
                Some(TokenKind::Star) => (BinOp::Mul, 9u8, false),
                Some(TokenKind::Slash) => (BinOp::Div, 9u8, false),
                Some(TokenKind::Power) => (BinOp::Pow, 10u8, true),
                Some(TokenKind::At) => (BinOp::MatMul, 9u8, false),
                _ => break,
            };
            if prec < min_prec {
                break;
            }
            let keyword_commitment = match op {
                BinOp::Or => self.expect_kw(TokenKind::Or, "`or`")?,
                BinOp::And => self.expect_kw(TokenKind::And, "`and`")?,
                BinOp::In => self.expect_kw(TokenKind::In, "`in`")?,
                BinOp::Cmp(_) => {
                    self.advance();
                    Commitment::None
                }
                _ => {
                    self.advance();
                    Commitment::None
                }
            };
            let next_min = if right_assoc { prec } else { prec + 1 };
            let rhs = self.parse_expr_depth(next_min, depth + 1)?;
            lhs = match op {
                BinOp::Or => Expr::Or {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                    keyword_commitment,
                },
                BinOp::And => Expr::And {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                    keyword_commitment,
                },
                BinOp::In => Expr::In {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                    keyword_commitment,
                },
                BinOp::Cmp(op) => Expr::Compare {
                    left: Box::new(lhs),
                    op,
                    right: Box::new(rhs),
                    keyword_commitment,
                },
                BinOp::BitOr => Expr::BitOr {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::BitXor => Expr::BitXor {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::BitAnd => Expr::BitAnd {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::Shl => Expr::Shl {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::Shr => Expr::Shr {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::Add => Expr::Add {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::Sub => Expr::Sub {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::Mul => Expr::Mul {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::Div => Expr::Div {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::Pow => Expr::Pow {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
                BinOp::MatMul => Expr::MatMul {
                    left: Box::new(lhs),
                    right: Box::new(rhs),
                },
            };
        }
        Ok(lhs)
    }

    fn parse_primary_depth(&mut self, depth: u32) -> Result<Expr, ParseError> {
        if depth > EXPR_RECURSION_LIMIT {
            let (line, col) = self.current_pos();
            return Err(ParseError::unexpected_token(
                "expression too deeply nested".into(),
                "expression".into(),
                line,
                col,
            ));
        }
        if self.check(&TokenKind::Not) {
            let keyword_commitment = self.expect_kw(TokenKind::Not, "`not`")?;
            let inner = self.parse_expr_depth(12, depth + 1)?;
            return Ok(Expr::Not {
                expr: Box::new(inner),
                keyword_commitment,
            });
        }
        if self.check(&TokenKind::Minus) {
            let keyword_commitment = self.expect_kw(TokenKind::Minus, "`-`")?;
            let inner = self.parse_expr_depth(12, depth + 1)?;
            return Ok(Expr::Neg {
                expr: Box::new(inner),
                keyword_commitment,
            });
        }
        if self.check(&TokenKind::BitNot) {
            let keyword_commitment = self.expect_kw(TokenKind::BitNot, "`~`")?;
            let inner = self.parse_expr_depth(12, depth + 1)?;
            return Ok(Expr::BitNot {
                expr: Box::new(inner),
                keyword_commitment,
            });
        }
        match self.peek_kind() {
            Some(TokenKind::Ident(_)) => {
                let id = self.fuzzy_ident()?;
                let mut expr = Expr::Ident { value: id };
                expr = self.parse_postfix_depth(expr, depth)?;
                Ok(expr)
            }
            Some(TokenKind::String(_)) => {
                let s = self.fuzzy_string()?;
                Ok(Expr::String { value: s })
            }
            Some(TokenKind::Number(_)) => {
                let n = self.advance();
                let TokenKind::Number(value) = &n.kind else {
                    unreachable!("advance() guaranteed Number token");
                };
                let value = value.clone();
                Ok(Expr::Number { value })
            }
            Some(TokenKind::True) => {
                self.advance();
                let keyword_commitment = self.commitment()?;
                Ok(Expr::Bool {
                    value: true,
                    keyword_commitment,
                })
            }
            Some(TokenKind::False) => {
                self.advance();
                let keyword_commitment = self.commitment()?;
                Ok(Expr::Bool {
                    value: false,
                    keyword_commitment,
                })
            }
            Some(TokenKind::LParen) => {
                self.advance();
                let inner = self.parse_expr_depth(0, depth + 1)?;
                self.expect(TokenKind::RParen, "`)`")?;
                Ok(inner)
            }
            Some(TokenKind::LBracket) => {
                self.advance();
                let mut items = Vec::new();
                if !self.check(&TokenKind::RBracket) {
                    items.push(self.parse_expr_depth(0, depth + 1)?);
                    while self.matches(&TokenKind::Comma) {
                        items.push(self.parse_expr_depth(0, depth + 1)?);
                    }
                }
                self.expect(TokenKind::RBracket, "`]`")?;
                Ok(Expr::List { items })
            }
            _ => {
                let (found, line, col) = match self.peek() {
                    Some(t) => (t.kind.to_string(), t.line, t.col),
                    None => ("EOF".into(), 0, 0),
                };
                Err(ParseError::unexpected_token(
                    found,
                    "expression".into(),
                    line,
                    col,
                ))
            }
        }
    }

    fn parse_postfix_depth(&mut self, expr: Expr, depth: u32) -> Result<Expr, ParseError> {
        let mut expr = expr;
        loop {
            if self.matches(&TokenKind::Dot) {
                let field = self.fuzzy_ident()?;
                expr = Expr::Index {
                    object: Box::new(expr),
                    field,
                };
            } else if self.check(&TokenKind::LParen) {
                let save = self.pos;
                self.advance();
                let mut args = Vec::new();
                if !self.check(&TokenKind::RParen) {
                    args.push(self.parse_expr_depth(0, depth + 1)?);
                    while self.matches(&TokenKind::Comma) {
                        args.push(self.parse_expr_depth(0, depth + 1)?);
                    }
                }
                if self.check(&TokenKind::RParen) {
                    self.advance();
                    expr = Expr::Call {
                        callee: Box::new(expr),
                        args,
                    };
                } else {
                    self.pos = save;
                    break;
                }
            } else if self.check(&TokenKind::LBracket) {
                self.advance();
                let mut indices = Vec::new();
                if !self.check(&TokenKind::RBracket) {
                    indices.push(self.parse_expr_depth(0, depth + 1)?);
                    while self.matches(&TokenKind::Comma) {
                        indices.push(self.parse_expr_depth(0, depth + 1)?);
                    }
                }
                self.expect(TokenKind::RBracket, "`]`")?;
                expr = Expr::Subscript {
                    object: Box::new(expr),
                    indices,
                };
            } else {
                break;
            }
        }
        Ok(expr)
    }

    pub(super) fn parse_math_block(&mut self) -> Result<MathBlock, ParseError> {
        let keyword_commitment = self.expect_kw(TokenKind::Math, "`math`")?;
        self.expect(TokenKind::Colon, "`:`")?;
        let statements = self.parse_block(|p| p.parse_math_statement());
        Ok(MathBlock {
            statements,
            keyword_commitment,
        })
    }

    pub(super) fn parse_math_statement(&mut self) -> Result<MathStatement, ParseError> {
        let expr = self.parse_expr(0)?;
        let stmt = if self.check(&TokenKind::Assign) {
            self.advance();
            let value = self.parse_expr(0)?;
            MathStatement::Define {
                target: expr,
                value,
            }
        } else {
            MathStatement::Expr { expr }
        };
        if !self.line_will_end() {
            let (found, line, col) = match self.peek() {
                Some(t) => (t.kind.to_string(), t.line, t.col),
                None => ("EOF".into(), 0, 0),
            };
            return Err(ParseError::unexpected_token(
                found,
                "end of math statement".into(),
                line,
                col,
            ));
        }
        Ok(stmt)
    }
}