//! Corresponds to `ast::lit`. use chumsky::prelude::*; use crate::ast::{Expr, Lit, NumLit, NumLitStr, StringLit, TableLit, TableLitElem}; use crate::builtin::Builtin; use super::basic::{ident, space, Error}; fn builtin_lit() -> impl Parser { just('\'').ignore_then(choice(( text::keyword("get").to(Builtin::Get), text::keyword("set").to(Builtin::Set), text::keyword("getraw").to(Builtin::GetRaw), text::keyword("setraw").to(Builtin::SetRaw), text::keyword("getmeta").to(Builtin::GetMeta), text::keyword("setmeta").to(Builtin::SetMeta), text::keyword("scope").to(Builtin::Scope), text::keyword("arg").to(Builtin::Arg), text::keyword("destructure").to(Builtin::Destructure), ))) } fn num_lit_str_radix(radix: u32) -> impl Parser + Clone { // Minimum amount of digits required to represent i64::MAX. The rest of this // code assumes that any value that can be represented using this amount of // digits fits into an u64. let max_digits = match radix { 2 => 63, 10 => 19, 16 => 16, _ => panic!("unsupported radix"), }; // Representations of i64::MAX. let max_value = match radix { 2 => "0b_1111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111", 10 => "9_223_372_036_854_775_807", 16 => "0x_7fff_ffff_ffff_ffff", _ => panic!("unsupported radix"), }; let constructor = match radix { 2 => NumLitStr::Bin, 10 => NumLitStr::Dec, 16 => NumLitStr::Hex, _ => panic!("unsupported radix"), }; filter(move |c: &char| c.is_digit(radix) || *c == '_') .repeated() .at_least(1) .collect::() .try_map(move |s, span| { let digits = s.chars().filter(|c| *c != '_').collect::(); if digits.is_empty() { let msg = "integer literal needs to contain at least one digit"; return Err(Simple::custom(span, msg)); } else if digits.len() > max_digits { let msg = format!("integer literal too large, the maximum value is {max_value}"); return Err(Simple::custom(span, msg)); } let value = u64::from_str_radix(&digits, radix).unwrap(); if value <= i64::MAX as u64 { Ok((value as i64, constructor(s))) } else { let msg = format!("integer literal too large, the maximum value is {max_value}"); Err(Simple::custom(span, msg)) } }) } pub fn num_lit() -> impl Parser + Clone { (just("0b").ignore_then(num_lit_str_radix(2))) .or(just("0x").ignore_then(num_lit_str_radix(16))) .or(num_lit_str_radix(10)) .map_with_span(|(value, str), span| NumLit { value, str, span }) } pub fn string_lit() -> impl Parser { // TODO Parse string literals filter(|_| false).map(|_| unreachable!()) } pub fn table_lit_elem( expr: impl Parser + Clone, ) -> impl Parser { let positional = expr .clone() .map(|value| TableLitElem::Positional(Box::new(value))); let named = ident() .then(space()) .then_ignore(just(':')) .then(space()) .then(expr) .map_with_span(|(((name, s0), s1), value), span| TableLitElem::Named { name, s0, s1, value: Box::new(value), span, }); named.or(positional) } pub fn table_lit( expr: impl Parser + Clone, ) -> impl Parser { let elem = space() .then(table_lit_elem(expr)) .then(space()) .map(|((s0, elem), s1)| (s0, elem, s1)); let trailing_comma = just(",").ignore_then(space()).or_not(); let elems = elem.separated_by(just(",")).then(trailing_comma); just("'{") .ignore_then(elems) .then_ignore(just("}")) .map_with_span(|(elems, trailing_comma), span| TableLit { elems, trailing_comma, span, }) } pub fn lit( expr: impl Parser + Clone, ) -> impl Parser { let nil = text::keyword("nil").map_with_span(|_, span| Lit::Nil(span)); let r#true = text::keyword("true").map_with_span(|_, span| Lit::Bool(true, span)); let r#false = text::keyword("false").map_with_span(|_, span| Lit::Bool(false, span)); let builtin = builtin_lit().map_with_span(Lit::Builtin); let num = num_lit().map(Lit::Num); let string = string_lit().map(Lit::String); let table = table_lit(expr).map(Lit::Table); nil.or(r#true) .or(r#false) .or(builtin) .or(num) .or(string) .or(table) }