Skip to content

Commit

Permalink
validate number literals at compile time
Browse files Browse the repository at this point in the history
  • Loading branch information
kaikalii committed Nov 27, 2024
1 parent 9a3b578 commit fca75ff
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 53 deletions.
4 changes: 2 additions & 2 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ pub struct InlineMacro {
#[derive(Clone)]
#[allow(missing_docs)]
pub enum Word {
Number(String, f64),
Number(Result<f64, String>),
Char(String),
String(String),
MultilineString(Vec<Sp<String>>),
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 6 additions & 2 deletions src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,11 @@ code:
}
fn word(&mut self, word: Sp<Word>) -> UiuaResult<Node> {
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()
Expand Down Expand Up @@ -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),
])
}
Expand Down
61 changes: 32 additions & 29 deletions src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,40 +904,42 @@ impl<'a> Formatter<'a> {
}
fn format_word(&mut self, word: &Sp<Word>, 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<str> {
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<str> {
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
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
31 changes: 14 additions & 17 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use crate::{
pub enum ParseError {
Lex(LexError),
Expected(Vec<Expectation>, Option<EcoString>),
InvalidNumber(String),
Unexpected(Token),
InvalidArgCount(String),
InvalidOutCount(String),
Expand Down Expand Up @@ -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}`"),
Expand Down Expand Up @@ -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(_) => {
Expand All @@ -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
}
};
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1174,9 +1173,9 @@ impl<'i> Parser<'i> {
}
Some(word)
}
fn num(&mut self) -> Option<Sp<(String, f64)>> {
fn num(&mut self) -> Option<Sp<Result<f64, String>>> {
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<f64> {
let mut s = s.replace(['`', '¯'], "-");
// Replace pi multiples
Expand All @@ -1200,19 +1199,17 @@ impl<'i> Parser<'i> {
}
s.parse().ok()
}
let n: f64 = match parse(&s) {
Some(n) => n,
let n: Result<f64, String> = 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<Sp<Primitive>> {
for prim in Primitive::all() {
Expand Down
1 change: 0 additions & 1 deletion todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit fca75ff

Please sign in to comment.