diff --git a/src/ast.rs b/src/ast.rs index 2adfc6214..e6ca60c59 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -266,7 +266,7 @@ pub struct InlineMacro { #[derive(Clone)] #[allow(missing_docs)] pub enum Word { - Number(String, f64), + Number(Result), Char(String), String(String), MultilineString(Vec>), @@ -301,7 +301,7 @@ pub enum Word { impl PartialEq for Word { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Number(_, a), Self::Number(_, b)) => a == b, + (Self::Number(a), Self::Number(b)) => a == b, (Self::Char(a), Self::Char(b)) => a == b, (Self::String(a), Self::String(b)) => a == b, (Self::Label(a), Self::Label(b)) => a == b, diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 22a1e7bab..fbde39577 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -1123,7 +1123,11 @@ code: } fn word(&mut self, word: Sp) -> UiuaResult { Ok(match word.value { - Word::Number(_, n) => Node::new_push(n), + Word::Number(Ok(n)) => Node::new_push(n), + Word::Number(Err(s)) => { + self.add_error(word.span.clone(), format!("Invalid number `{s}`")); + Node::new_push(0.0) + } Word::Char(c) => { let val: Value = if c.chars().count() == 1 { c.chars().next().unwrap().into() @@ -1905,7 +1909,7 @@ code: && prim.subscript_sig(Some(2)).is_some_and(|sig| sig == (1, 1)) => { Node::from_iter([ - self.word(sub.n.span.sp(Word::Number(n.to_string(), n as f64)))?, + self.word(sub.n.span.sp(Word::Number(Ok(n as f64))))?, self.primitive(prim, span), ]) } diff --git a/src/format.rs b/src/format.rs index 33b57ba69..ee29e41eb 100644 --- a/src/format.rs +++ b/src/format.rs @@ -904,40 +904,42 @@ impl<'a> Formatter<'a> { } fn format_word(&mut self, word: &Sp, depth: usize) { match &word.value { - Word::Number(s, n) => { + Word::Number(Ok(n)) => { let grid_str = n.grid_string(false); - let formatted = if !grid_str.contains('…') - && grid_str.chars().count() < s.trim_end_matches('i').chars().count() - && !["tau", "pi", "eta"].iter().any(|name| s.contains(name)) - && !grid_str.contains("τ/") - { - grid_str - } else { - fn format_frag(s: &str) -> Cow { - let mut s = Cow::Borrowed(s); - if s.contains('`') { - s = Cow::Owned(s.replace('`', "¯")); - } - for (name, glyph) in [("eta", "η"), ("pi", "π"), ("tau", "τ")] { - if s.contains(name) { - s = Cow::Owned(s.replace(name, glyph)); + let formatted = word.span.as_str(self.inputs, |s| { + if !grid_str.contains('…') + && grid_str.chars().count() < s.trim_end_matches('i').chars().count() + && !["tau", "pi", "eta"].iter().any(|name| s.contains(name)) + && !grid_str.contains("τ/") + { + grid_str + } else { + fn format_frag(s: &str) -> Cow { + let mut s = Cow::Borrowed(s); + if s.contains('`') { + s = Cow::Owned(s.replace('`', "¯")); } - } - for i in (3..="infinity".len()).rev() { - if s.contains(&"infinity"[..i]) { - s = Cow::Owned(s.replace(&"infinity"[..i], "∞")); + for (name, glyph) in [("eta", "η"), ("pi", "π"), ("tau", "τ")] { + if s.contains(name) { + s = Cow::Owned(s.replace(name, glyph)); + } } + for i in (3..="infinity".len()).rev() { + if s.contains(&"infinity"[..i]) { + s = Cow::Owned(s.replace(&"infinity"[..i], "∞")); + } + } + s + } + if let Some((num, denom)) = s.split_once('/') { + let num = format_frag(num); + let denom = format_frag(denom); + format!("{num}/{denom}") + } else { + format_frag(s).into_owned() } - s - } - if let Some((num, denom)) = s.split_once('/') { - let num = format_frag(num); - let denom = format_frag(denom); - format!("{num}/{denom}") - } else { - format_frag(s).into_owned() } - }; + }); if formatted.starts_with(|c: char| c.is_ascii_digit()) && (self .output @@ -950,6 +952,7 @@ impl<'a> Formatter<'a> { } self.push(&word.span, &formatted); } + Word::Number(Err(s)) => self.push(&word.span, s), Word::Label(label) => self.push(&word.span, &format!("${label}")), Word::Char(_) | Word::String(_) | Word::FormatString(_) => self .output diff --git a/src/lsp.rs b/src/lsp.rs index d4aa697eb..43b4de935 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -542,9 +542,11 @@ impl Spanner { let mut spans = Vec::new(); 'words: for word in words { match &word.value { - Word::Number(s, _) => { + Word::Number(_) => { for prim in Primitive::all().filter(|p| p.is_constant()) { - if prim.name().starts_with(s) || prim.to_string() == *s { + if word.span.as_str(self.inputs(), |s| { + prim.name().starts_with(s) || prim.to_string() == *s + }) { spans.push(word.span.clone().sp(SpanKind::Primitive(prim, None))); continue 'words; } diff --git a/src/parse.rs b/src/parse.rs index e6cf879c3..9991e7fe5 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -17,7 +17,6 @@ use crate::{ pub enum ParseError { Lex(LexError), Expected(Vec, Option), - InvalidNumber(String), Unexpected(Token), InvalidArgCount(String), InvalidOutCount(String), @@ -82,7 +81,6 @@ impl fmt::Display for ParseError { } Ok(()) } - ParseError::InvalidNumber(s) => write!(f, "Invalid number `{s}`"), ParseError::Unexpected(token) => write!(f, "Unexpected token {token}"), ParseError::InvalidArgCount(n) => write!(f, "Invalid argument count `{n}`"), ParseError::InvalidOutCount(n) => write!(f, "Invalid output count `{n}`"), @@ -758,8 +756,9 @@ impl<'i> Parser<'i> { Some(span.sp(Signature::new(args, outs))) } fn sig_inner(&mut self) -> Option<(usize, usize)> { - let sn = self.num()?; - Some(if let Some((a, o)) = sn.value.0.split_once('.') { + let range = self.num()?.span.byte_range(); + let s = &self.input[range]; + Some(if let Some((a, o)) = s.split_once('.') { let a = match a.parse() { Ok(a) => a, Err(_) => { @@ -778,11 +777,11 @@ impl<'i> Parser<'i> { }; (a, o) } else { - let a = match sn.value.0.parse() { + let a = match s.parse() { Ok(a) => a, Err(_) => { self.errors - .push(self.prev_span().sp(ParseError::InvalidArgCount(sn.value.0))); + .push(self.prev_span().sp(ParseError::InvalidArgCount(s.into()))); 1 } }; @@ -1072,8 +1071,8 @@ impl<'i> Parser<'i> { prim.map(Word::Primitive) } else if let Some(refer) = self.ref_() { refer - } else if let Some(sn) = self.num() { - sn.map(|(s, n)| Word::Number(s, n)) + } else if let Some(n) = self.num() { + n.map(Word::Number) } else if let Some(c) = self.next_token_map(Token::as_char) { c.map(Into::into).map(Word::Char) } else if let Some(s) = self.next_token_map(Token::as_string) { @@ -1174,9 +1173,9 @@ impl<'i> Parser<'i> { } Some(word) } - fn num(&mut self) -> Option> { + fn num(&mut self) -> Option>> { let span = self.exact(Token::Number)?; - let s = self.input[span.byte_range()].to_string(); + let s = &self.input[span.byte_range()]; fn parse(s: &str) -> Option { let mut s = s.replace(['`', '¯'], "-"); // Replace pi multiples @@ -1200,19 +1199,17 @@ impl<'i> Parser<'i> { } s.parse().ok() } - let n: f64 = match parse(&s) { - Some(n) => n, + let n: Result = match parse(s) { + Some(n) => Ok(n), None => { if let Some((n, d)) = s.split_once('/').and_then(|(n, d)| parse(n).zip(parse(d))) { - n / d + Ok(n / d) } else { - self.errors - .push(self.prev_span().sp(ParseError::InvalidNumber(s.clone()))); - 0.0 + Err(s.into()) } } }; - Some(span.sp((s, n))) + Some(span.sp(n)) } fn prim(&mut self) -> Option> { for prim in Primitive::all() { diff --git a/todo.md b/todo.md index cf3d68d66..e7d7f1089 100644 --- a/todo.md +++ b/todo.md @@ -3,7 +3,6 @@ ## 0.14 The next version of Uiua -- Validate number literals at compile time - Stabilize `backward`, `case` - `do` function pack - Allow for multi-value constant bindings