hwcalc_lib 0.2.0

Backend for the hwcalc calculator
Documentation
use super::ir;
use super::lex;

pub(crate) fn parse(src: &str, symtab: &mut super::SymbolTable) -> Result<ir::ExprList, Error> {
    let mut parser = Parser::new(src, symtab);
    let exprs = parser.expr_list()?;
    assert_eq!(parser.depth, 0);
    Ok(exprs)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span(u32, u32);

#[cfg(not(any(test, small_max)))]
type LiteralExponent = i32;
#[cfg(any(test, small_max))]
type LiteralExponent = i8;

impl Span {
    pub(crate) fn new(start: usize, end: usize) -> Self {
        assert!(start <= end);
        Span(start.try_into().unwrap(), end.try_into().unwrap())
    }

    #[must_use]
    pub fn start(self) -> usize {
        self.0 as usize
    }

    #[must_use]
    pub fn of(self, input: &str) -> &str {
        &input[self.0 as usize..self.1 as usize]
    }
}

impl std::ops::Add for Span {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        assert!(self.0 <= rhs.1);
        Span(self.0, rhs.1)
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct Error(pub ErrorKind, pub Span);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
    InvalidToken,
    LiteralTooLarge,
    RecursionLimitExceeded,
    UnexpectedEof,
    UnexpectedToken,
    WidthOutOfRange,
}

impl From<Error> for super::Error {
    fn from(e: Error) -> Self {
        Self {
            kind: super::ErrorKind::Parse(e.0),
            span: e.1,
        }
    }
}

impl std::fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Self::InvalidToken => write!(f, "invalid token"),
            Self::LiteralTooLarge => write!(f, "literal too large"),
            Self::RecursionLimitExceeded => write!(f, "recursion limit exceeded"),
            Self::UnexpectedEof => write!(f, "unexpected EOF"),
            Self::UnexpectedToken => write!(f, "unexpected token"),
            Self::WidthOutOfRange => write!(f, "bit width out of range"),
        }
    }
}

struct Parser<'a> {
    src: &'a str,
    symtab: &'a mut super::SymbolTable,
    /// Next token to be eaten.
    next_token: lex::Token,
    /// Position after `next_token`.
    pos: usize,
    /// Span of last eaten token.
    span: Span,
    /// Current recursion depth, track to avoid stack overflow
    depth: usize,
}

impl<'a> Parser<'a> {
    #[must_use]
    fn new(src: &'a str, symtab: &'a mut super::SymbolTable) -> Self {
        let next_token = lex::Lexer::new(src).next_token();
        Self {
            src,
            symtab,
            pos: next_token.len,
            next_token,
            span: Span::new(0, 0),
            depth: 0,
        }
    }

    fn step(&mut self) -> lex::Token {
        let token = lex::Lexer::new(&self.src[self.pos..]).next_token();
        self.pos += token.len;
        std::mem::replace(&mut self.next_token, token)
    }

    fn eat(&mut self) -> lex::TokenKind {
        loop {
            let end = self.pos;
            let token = self.step();
            if !matches!(token.kind, lex::TokenKind::Whitespace) {
                self.span = Span::new(end - token.len, end);
                return token.kind;
            }
        }
    }

    fn peek(&mut self) -> &lex::TokenKind {
        if matches!(self.next_token.kind, lex::TokenKind::Whitespace) {
            let _whitespace = self.step();
        }
        &self.next_token.kind
    }

    fn token_src(&self) -> &str {
        self.span.of(self.src)
    }

    fn err(&self, kind: ErrorKind) -> Error {
        let span = self.span;
        Error(kind, span)
    }

    fn err_eat(&mut self, kind: ErrorKind) -> Error {
        self.eat();
        self.err(kind)
    }

    fn expr_list(&mut self) -> Result<ir::ExprList, Error> {
        let mut list = ir::ExprList::new();

        loop {
            match self.peek() {
                lex::TokenKind::Eof => break,
                lex::TokenKind::Semicolon => {
                    self.eat(); // ;
                }
                _ => {
                    let elem = self.expr()?;
                    list.push(elem);
                }
            };
        }

        Ok(list)
    }

    fn expr(&mut self) -> Result<ir::Expr, Error> {
        self.expr_prec(0)
    }

    fn expr_prec(&mut self, min_precedence: u8) -> Result<ir::Expr, Error> {
        self.depth += 1;
        if self.depth > super::RECURSION_LIMIT {
            return Err(self.err(ErrorKind::RecursionLimitExceeded));
        }

        let mut lhs = match self.eat() {
            lex::TokenKind::OpenParen => {
                let span_start = self.span;
                let expr = self.expr()?;
                let token_kind = self.eat();
                let span_end = self.span;
                if matches!(token_kind, lex::TokenKind::CloseParen) {
                    ir::Expr {
                        kind: expr.kind,
                        span: span_start + span_end,
                    }
                } else if matches!(token_kind, lex::TokenKind::Eof) {
                    return Err(self.err(ErrorKind::UnexpectedEof));
                } else {
                    assert!(matches!(token_kind, lex::TokenKind::Semicolon));
                    return Err(self.err(ErrorKind::UnexpectedToken));
                }
            }
            lex::TokenKind::Sym(s) => {
                let op = match s {
                    lex::Symbol::Minus => ir::UnOp::Neg,
                    lex::Symbol::Bang => ir::UnOp::Not,
                    _ => return Err(self.err(ErrorKind::UnexpectedToken)),
                };
                let span_op = self.span;
                let expr = self.expr_prec(op.precedence())?;
                let span = span_op + expr.span;
                let kind = ir::ExprKind::Un(op, Box::new(expr));
                ir::Expr { kind, span }
            }
            lex::TokenKind::Num(n) => {
                let span_num = self.span;
                ir::Expr {
                    kind: ir::ExprKind::Num(self.num(&n)?),
                    span: span_num + self.span,
                }
            }
            lex::TokenKind::Ident => {
                let span = self.span;
                let kind = ir::ExprKind::Ident(self.symtab.insert(self.span.of(self.src)));
                ir::Expr { kind, span }
            }
            lex::TokenKind::Eof => return Err(self.err(ErrorKind::UnexpectedEof)),
            lex::TokenKind::Invalid => return Err(self.err(ErrorKind::InvalidToken)),
            _ => return Err(self.err(ErrorKind::UnexpectedToken)),
        };

        loop {
            match self.peek() {
                lex::TokenKind::CloseParen | lex::TokenKind::Eof | lex::TokenKind::Semicolon => {
                    break
                }
                lex::TokenKind::Ty(..) => {
                    let ty = self.ty()?;
                    let span = lhs.span + self.span;
                    let kind = ir::ExprKind::Cast {
                        ty,
                        expr: Box::new(lhs),
                    };
                    lhs = ir::Expr { kind, span };
                }
                lex::TokenKind::Sym(lex::Symbol::Eq) => {
                    self.eat(); // =
                    if let ir::ExprKind::Ident(s) = lhs.kind {
                        let rhs = self.expr_prec(1)?;
                        let span = lhs.span + self.span;
                        let kind = ir::ExprKind::Assign(s, rhs.into());
                        lhs = ir::Expr { kind, span };
                    } else {
                        return Err(self.err(ErrorKind::UnexpectedToken));
                    }
                }
                lex::TokenKind::Sym(s) => {
                    let op = match s {
                        lex::Symbol::Plus => ir::BinOp::Add,
                        lex::Symbol::Minus => ir::BinOp::Sub,
                        lex::Symbol::Star => ir::BinOp::Mul,
                        lex::Symbol::Star2 => ir::BinOp::Pow,
                        lex::Symbol::Slash => ir::BinOp::Div,
                        lex::Symbol::Percentage => ir::BinOp::Rem,
                        lex::Symbol::Ampersand => ir::BinOp::And,
                        lex::Symbol::Caret => ir::BinOp::Xor,
                        lex::Symbol::Pipe => ir::BinOp::Or,
                        lex::Symbol::Lt2 => ir::BinOp::Shl,
                        lex::Symbol::Gt2 => ir::BinOp::Shr,
                        _ => return Err(self.err_eat(ErrorKind::UnexpectedToken)),
                    };
                    let lfix = op.precedence();
                    if lfix < min_precedence {
                        break;
                    }

                    self.eat(); // op
                    let rhs = self.expr_prec(lfix + 1)?;
                    let span = lhs.span + self.span;
                    let kind = ir::ExprKind::Bin(op, Box::new(lhs), Box::new(rhs));
                    lhs = ir::Expr { kind, span };
                }
                lex::TokenKind::Invalid => return Err(self.err_eat(ErrorKind::InvalidToken)),
                _ => return Err(self.err_eat(ErrorKind::UnexpectedToken)),
            }
        }

        self.depth -= 1;

        Ok(lhs)
    }

    fn num(&mut self, num: &lex::Num) -> Result<ir::Num, Error> {
        let src = self.token_src();
        let src = if num.truncated {
            &src[..src.len() - 2]
        } else {
            src
        };

        let int_start = if num.prefixed { 2 } else { 0 } + if num.infinite_digit { 3 } else { 0 };
        let int_end = num.point.or(num.exp).map_or(src.len(), usize::from);
        let int = &src[int_start..int_end];
        let int = int.replace('_', "");
        let int_len = int.len();
        let int = if int.is_empty() {
            ir::Int::default()
        } else {
            ir::Int::parse_bytes(int.as_bytes(), num.base.radix().into()).unwrap()
        };
        let int = if num.infinite_digit {
            let bits_int = int_len * num.base.bits_per_digit() as usize;
            let inf_ones = !((ir::Int::from(1) << bits_int) - ir::Int::from(1));
            inf_ones | int
        } else {
            int
        };

        let frac_start = num
            .point
            .map(|d| {
                d.checked_add(1)
                    .ok_or_else(|| self.err(ErrorKind::InvalidToken))
            })
            .transpose()?
            .or(num.exp)
            .map_or(src.len(), usize::from);
        let frac_end = num.repeat.or(num.exp).map_or(src.len(), usize::from);
        let frac = &src[frac_start..frac_end];
        let frac = frac.replace('_', "");
        let numer = if frac.is_empty() {
            ir::Int::default()
        } else {
            ir::Int::parse_bytes(frac.as_bytes(), num.base.radix().into()).unwrap()
        };
        let denom = ir::Int::from(num.base.radix()).pow(frac.len().try_into().unwrap());
        let frac = ir::Val::from((numer, denom.clone()));

        let frac_repeating = if let Some(start) = num.repeat {
            let repeat = &src[usize::from(start) + 1..num.exp.map_or(src.len(), usize::from) - 1];
            let repeat = repeat.replace('_', "");
            let n = repeat.len().try_into().unwrap();
            if n > 0 {
                let numer =
                    ir::Int::parse_bytes(repeat.as_bytes(), num.base.radix().into()).unwrap();
                let denom = denom * (ir::Int::from(num.base.radix()).pow(n) - ir::Int::from(1));
                ir::Val::new(numer, denom)
            } else {
                ir::Val::default()
            }
        } else {
            ir::Val::default()
        };

        let frac = frac + frac_repeating;

        let exp: LiteralExponent = if let Some(e) = num.exp {
            let exp = &src[e as usize + 1..];
            let exp = exp.replace('_', "");
            assert_eq!(num.base, super::Base::Dec);
            if let Ok(e) = exp.parse() {
                e
            } else {
                return Err(self.err(ErrorKind::WidthOutOfRange));
            }
        } else {
            0
        };
        #[allow(clippy::useless_conversion)]
        let exp = ir::Val::from(ir::Int::from(10)).pow(exp.into());

        let val = (ir::Val::from_integer(int) + frac) * exp;
        let ty = self.ty()?;

        ir::Num::new(val)
            .ok_or_else(|| self.err(ErrorKind::LiteralTooLarge))
            .map(|v| v.with_ty(ty))
    }

    fn ty(&mut self) -> Result<ir::Ty, Error> {
        let ty = if matches!(self.peek(), lex::TokenKind::Ty(..)) {
            if let lex::TokenKind::Ty(ty) = self.eat() {
                ty
            } else {
                unreachable!()
            }
        } else {
            return Ok(ir::Ty::default());
        };

        let signed = ty.prefix.map(lex::TyPrefix::signed);
        let fixed_point = ty.prefix.map_or(true, lex::TyPrefix::fixed_point);

        let width_start = usize::from(ty.has_apostrophe) + ty.prefix.map_or(0, lex::TyPrefix::len);
        let width_str = &self.token_src()[width_start..];
        let mut widths = width_str.split('.');
        let w = widths
            .next()
            .and_then(|s| if s.is_empty() { None } else { Some(s) })
            .map(str::parse)
            .transpose()
            .map_err(|_| self.err(ErrorKind::WidthOutOfRange))?;
        let wf = if fixed_point {
            widths
                .next()
                .map(str::parse)
                .transpose()
                .map_err(|_| self.err(ErrorKind::WidthOutOfRange))?
        } else {
            assert!(widths.next().is_none());
            Some(0)
        };

        ir::Ty::new(signed, w, wf).ok_or_else(|| self.err(ErrorKind::WidthOutOfRange))
    }
}

#[cfg(test)]
mod test {
    use crate::ir;
    use crate::SymbolTable;
    use ir::BinOp::*;
    use ir::ExprKind::*;
    use ir::Num;
    use ir::UnOp::*;

    impl ir::ExprKind {
        fn span(self, start: usize, end: usize) -> ir::Expr {
            ir::Expr {
                kind: self,
                span: super::Span::new(start, end),
            }
        }
    }

    impl super::ErrorKind {
        pub fn span(self, start: usize, end: usize) -> super::Error {
            super::Error(self, super::Span::new(start, end))
        }
    }

    use super::ErrorKind::*;

    macro_rules! test_parse {
        ($src:expr $(, $($expected:expr),*)? $(,)?) => {
            let mut st = SymbolTable::default();
            test_parse!(&mut st; $src $(, $($expected),*)?);
        };
        ($st:expr; $src:expr $(, $($expected:expr),*)? $(,)?) => {
            let actual = super::parse($src, $st).unwrap();
            let expected = &[$($($expected),*)?];
            assert_eq!(actual, expected, "{}", $src);
        };
    }

    #[test]
    fn expr_list() {
        test_parse!("");
        test_parse!(";");
        test_parse!("1;2", Num(Num::n(1)).span(0, 1), Num(Num::n(2)).span(2, 3));
        test_parse!(";1", Num(Num::n(1)).span(1, 2));
        test_parse!("1;", Num(Num::n(1)).span(0, 1));
        test_parse!("1;;2", Num(Num::n(1)).span(0, 1), Num(Num::n(2)).span(3, 4));
    }

    #[test]
    fn expr_int() {
        test_parse!("100", Num(Num::n(100)).span(0, 3));
        test_parse!("22u", Num(Num::u(22)).span(0, 3));
        test_parse!("22'u", Num(Num::u(22)).span(0, 4));
        test_parse!("0b1101u", Num(Num::u(0b1101)).span(0, 7));
        test_parse!("0xffi8", Num(Num::i(0xff).w(8)).span(0, 6));
        // max value
        test_parse!(
            "0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff",
            Num(Num::new(ir::Val::from_integer(
                (ir::Int::from(1) << 255) - ir::Int::from(1)
            ))
            .unwrap())
            .span(0, 81),
        );
    }

    #[test]
    fn expr_int_inf() {
        test_parse!("0b(1)010", Num(Num::n(-6)).span(0, 8));
        test_parse!("0o(7)123", Num(Num::n(-429)).span(0, 8));
        test_parse!("0x(f)abc", Num(Num::n(-1348)).span(0, 8));
    }

    #[test]
    fn expr_fix() {
        test_parse!("0b1101.10", Num(Num::nd(0b1101_10, 0b1_00)).span(0, 9));
        test_parse!("10.75", Num(Num::nd(10_75, 1_00)).span(0, 5));
        // signed
        test_parse!("0xa.cq.2", Num(Num::q(0xa_c, 0x1_0).wf(2)).span(0, 8));
        // unsigned
        test_parse!("0b.1uq0.1", Num(Num::uq(1, 2).w(0).wf(1)).span(0, 9));
        test_parse!("0xa.cuq.2", Num(Num::uq(0xa_c, 0x1_0).wf(2)).span(0, 9));
        // truncated to int
        test_parse!("0b1q1.0", Num(Num::i(1).w(1).wf(0)).span(0, 7));
        // ignore truncation dots
        test_parse!(
            "0b1101.1010..",
            Num(Num::nd(0b1101_1010, 0b1_0000)).span(0, 13)
        );
    }

    #[test]
    fn expr_fix_repeat() {
        test_parse!("1.(1)", Num(Num::nd(10, 9)).span(0, 5));
        test_parse!("1.0(1)", Num(Num::nd(91, 90)).span(0, 6));
        test_parse!("1.(01)", Num(Num::nd(100, 99)).span(0, 6));
        test_parse!("0b.0(0011)", Num(Num::nd(1, 10)).span(0, 10));
        test_parse!("0.(9)", Num(Num::n(1)).span(0, 5));
        test_parse!("0.(999)", Num(Num::n(1)).span(0, 7));
        test_parse!("0._(_9_9_)", Num(Num::n(1)).span(0, 10));
        test_parse!("0.(_)", Num(Num::n(0)).span(0, 5));
        test_parse!("0.()", Num(Num::n(0)).span(0, 4));
    }

    #[test]
    fn expr_ident() {
        let mut st = SymbolTable::default();
        test_parse!(&mut st; "a", Ident(st.insert("a")).span(0, 1));
        test_parse!(
            &mut st;
            "5 + abc",
            Bin(
                Add,
                Num(Num::n(5)).span(0, 1).into(),
                Ident(st.insert("abc")).span(4, 7).into()
            )
            .span(0, 7)
        );
    }

    #[test]
    fn expr_assign() {
        let mut st = SymbolTable::default();
        test_parse!(
            &mut st;
            "ab = 5",
            Assign(st.insert("ab"), Num(Num::n(5)).span(5, 6).into()).span(0, 6)
        );
        test_parse!(
            &mut st;
            "ab = 5 + ab",
            Assign(
                st.insert("ab"),
                Bin(
                    Add,
                    Num(Num::n(5)).span(5, 6).into(),
                    Ident(st.insert("ab")).span(9, 11).into()
                )
                .span(5, 11)
                .into()
            )
            .span(0, 11)
        );
    }

    #[test]
    fn expr_un() {
        test_parse!("!22", Un(Not, Num(Num::n(22)).span(1, 3).into()).span(0, 3));
        test_parse!("-3", Un(Neg, Num(Num::n(3)).span(1, 2).into()).span(0, 2));
    }

    #[test]
    fn expr_bin() {
        test_parse!(
            "1 + 2",
            Bin(
                Add,
                Num(Num::n(1)).span(0, 1).into(),
                Num(Num::n(2)).span(4, 5).into()
            )
            .span(0, 5)
        );
        test_parse!(
            "100^0x4",
            Bin(
                Xor,
                Num(Num::n(100)).span(0, 3).into(),
                Num(Num::n(4)).span(4, 7).into()
            )
            .span(0, 7)
        );
        test_parse!(
            "1+2+3",
            Bin(
                Add,
                Box::new(
                    Bin(
                        Add,
                        Num(Num::n(1)).span(0, 1).into(),
                        Num(Num::n(2)).span(2, 3).into(),
                    )
                    .span(0, 3)
                ),
                Num(Num::n(3)).span(4, 5).into(),
            )
            .span(0, 5)
        );
        test_parse!(
            "1 + 2*3",
            Bin(
                Add,
                Num(Num::n(1)).span(0, 1).into(),
                Box::new(
                    Bin(
                        Mul,
                        Num(Num::n(2)).span(4, 5).into(),
                        Num(Num::n(3)).span(6, 7).into()
                    )
                    .span(4, 7)
                ),
            )
            .span(0, 7)
        );
        test_parse!(
            "5**2",
            Bin(
                Pow,
                Num(Num::n(5)).span(0, 1).into(),
                Num(Num::n(2)).span(3, 4).into(),
            )
            .span(0, 4)
        );
    }

    #[test]
    fn expr_paren() {
        test_parse!("((((((1))))))", Num(Num::n(1)).span(0, 13));
        test_parse!(
            "(1+2)*3",
            Bin(
                Mul,
                Box::new(
                    Bin(
                        Add,
                        Num(Num::n(1)).span(1, 2).into(),
                        Num(Num::n(2)).span(3, 4).into()
                    )
                    .span(0, 5)
                ),
                Num(Num::n(3)).span(6, 7).into(),
            )
            .span(0, 7)
        );
    }

    #[test]
    fn expr_cast() {
        test_parse!(
            "5u8'8",
            Cast {
                ty: ir::Ty::default().w(8),
                expr: Box::new(Num(Num::u(5).w(8).wf(0)).span(0, 3)),
            }
            .span(0, 5)
        );
        test_parse!(
            "5i8'i8",
            Cast {
                ty: ir::Ty::default().s().w(8).wf(0),
                expr: Box::new(Num(Num::i(5).w(8).wf(0)).span(0, 3)),
            }
            .span(0, 6)
        );
        test_parse!(
            "5i8'u8",
            Cast {
                ty: ir::Ty::default().u().w(8).wf(0),
                expr: Box::new(Num(Num::i(5).w(8).wf(0)).span(0, 3)),
            }
            .span(0, 6)
        );
    }

    #[test]
    fn expr_cast_f() {
        test_parse!(
            "5.5q7.8'8",
            Cast {
                ty: ir::Ty::default().w(8),
                expr: Box::new(Num(Num::q(11, 2).w(7).wf(8)).span(0, 7)),
            }
            .span(0, 9)
        );
        test_parse!(
            "(1.5)'uq1.2",
            Cast {
                ty: ir::Ty::default().u().w(1).wf(2),
                expr: Box::new(Num(Num::nd(3, 2)).span(0, 5)),
            }
            .span(0, 11)
        );
    }

    #[test]
    fn expr_exp() {
        test_parse!("1e1", Num(Num::n(10)).span(0, 3));
        test_parse!("0d1e1", Num(Num::n(10)).span(0, 5));
        test_parse!("1e0", Num(Num::n(1)).span(0, 3));
        test_parse!("0.01e3", Num(Num::n(10)).span(0, 6));
        test_parse!("5e12", Num(Num::n(5_000_000_000_000)).span(0, 4));
        test_parse!("5e_1_2_", Num(Num::n(5_000_000_000_000)).span(0, 7));
        test_parse!("5e-3", Num(Num::nd(1, 200)).span(0, 4));
        test_parse!("1e-9", Num(Num::nd(1, 1_000_000_000)).span(0, 4));
        test_parse!("1e-12", Num(Num::nd(1, 1_000_000_000_000)).span(0, 5));
        test_parse!("1.5(123)e3", Num(Num::nd(503600, 333)).span(0, 10));
        test_parse!("1e-_1", Num(Num::nd(1, 10)).span(0, 5));
    }

    macro_rules! test_err {
        ($src:expr, $expected:expr $(,)?) => {
            let mut st = SymbolTable::default();
            let actual = super::parse($src, &mut st);
            let expected = Err($expected);
            assert_eq!(actual, expected, "{}", $src);
        };
    }

    #[test]
    fn trailing() {
        test_err!("5 + 6)", UnexpectedToken.span(5, 6));
        test_err!("0)", UnexpectedToken.span(1, 2));
    }

    #[test]
    fn invalid_sym() {
        test_err!("*5", UnexpectedToken.span(0, 1));
        test_err!("#5", InvalidToken.span(0, 1));
        test_err!("5 ! 6", UnexpectedToken.span(2, 3));
        test_err!("5 # 6", InvalidToken.span(2, 3));
    }

    #[test]
    fn empty_grouping() {
        test_err!("()", UnexpectedToken.span(1, 2));
    }

    #[test]
    fn unclosed_paren() {
        test_err!("(", UnexpectedEof.span(1, 1));
        test_err!("((((1)))", UnexpectedEof.span(8, 8));
        test_err!("(5", UnexpectedEof.span(2, 2));
        test_err!("(5;", UnexpectedToken.span(2, 3));
    }

    #[test]
    fn unopened_paren() {
        test_err!(")", UnexpectedToken.span(0, 1));
    }

    #[test]
    fn invalid_op() {
        test_err!("5 ++ 2", UnexpectedToken.span(3, 4));
        test_err!("+5", UnexpectedToken.span(0, 1));
        test_err!("5+", UnexpectedEof.span(2, 2));
        test_err!("5!5", UnexpectedToken.span(1, 2));
    }

    #[test]
    fn invalid_width() {
        // 0 width
        test_err!("5i0", WidthOutOfRange.span(1, 3));
        test_err!("5q0.0", WidthOutOfRange.span(1, 5));
        // > max width
        test_err!("5i256", WidthOutOfRange.span(1, 5));
        test_err!("5q0.256", WidthOutOfRange.span(1, 7));
        // total > max width
        test_err!("1q255.1", WidthOutOfRange.span(1, 7));
    }

    #[test]
    fn invalid_width_exp() {
        // largest valid negative
        let src = format!("1e{}", super::LiteralExponent::MIN);
        let val = Num(Num::new(ir::Val::new(
            1.into(),
            ir::Int::from(10).pow(
                i32::from(super::LiteralExponent::MIN)
                    .abs()
                    .try_into()
                    .unwrap(),
            ),
        ))
        .unwrap())
        .span(0, src.len());
        test_parse!(&src, val);

        // too large negative
        let src = format!("1e{}", i64::from(super::LiteralExponent::MIN) - 1i64);
        test_err!(&src, WidthOutOfRange.span(0, src.len()));

        // larges valid positive exponent (resulting value too large, though)
        let src = format!("1e{}", super::LiteralExponent::MAX);
        test_err!(&src, LiteralTooLarge.span(0, src.len()));

        // too large positive
        let src = format!("1e{}", i64::from(super::LiteralExponent::MAX) + 1i64);
        test_err!(&src, WidthOutOfRange.span(0, src.len()));
    }

    #[test]
    fn invalid_value() {
        // above max width
        test_err!(
            "0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff",
            LiteralTooLarge.span(0, 81),
        );
    }

    #[test]
    fn invalid_token() {
        test_err!("ยค", InvalidToken.span(0, 2));
    }

    #[test]
    fn invalid_token_decimal_point_long() {
        const MAX: usize = crate::lex::W::MAX as usize;
        test_parse!(
            &format!("{}.1", "0".repeat(MAX - 1)),
            Num(Num::nd(1, 10)).span(0, MAX + 1)
        );
        test_err!(
            &format!("{}.1", "0".repeat(MAX)),
            InvalidToken.span(0, MAX + 2),
        );
        test_err!(
            &format!("{}.1", "0".repeat(MAX + 1)),
            InvalidToken.span(0, MAX + 1),
        );
    }

    #[test]
    fn invalid_assign() {
        test_err!("6 = 5", UnexpectedToken.span(2, 3));
    }
}