From b3a98c98d503f98826a29e96ff5ce528cd44152d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Teodor=20K=C3=A4llman?=
 <94024065+RedPhoenixQ@users.noreply.github.com>
Date: Thu, 4 Jan 2024 05:32:25 +0100
Subject: [PATCH] Parse many literals along side idents in names (#398)

* Parse many literals along side idents in names

* Accept ints as literals

We will not accept floats because `123.123` is a float literal,
but `123 .123` is a int literal followed by a class called `123`.
This could be confusing so it will not be accepted.

Ints can have leading zeros, like `0123`, but this is not guarranteed by
the rust compiler to always work, which could cause future errors.
An example would be truncating `001` to `1`.

* Limit accepted literals using existing function

* Update error output for non-string-literal

* Test output of ints with specified type

This outputs exactly what is written, which is the obvious behaviour

* Use nightly version to generate output

Previous verison was not using nightly, causing errors in the automated
test that are using nightly

* Replace "byte_string" with "raw_string" in test

---------

Co-authored-by: Chris Wong <lambda.fairy@gmail.com>
---
 maud/tests/basic_syntax.rs                    | 14 +++++--
 maud/tests/warnings/non-string-literal.stderr | 24 +++---------
 maud_macros/src/parse.rs                      | 37 ++++++++++++-------
 3 files changed, 41 insertions(+), 34 deletions(-)

diff --git a/maud/tests/basic_syntax.rs b/maud/tests/basic_syntax.rs
index 96e0235..c2c5dc5 100644
--- a/maud/tests/basic_syntax.rs
+++ b/maud/tests/basic_syntax.rs
@@ -201,11 +201,19 @@ fn raw_string_literals_in_attribute_names() {
 
 #[test]
 fn other_literals_in_attribute_names() {
-    let result =
-        html! { this b"byte_string"="false" 123="123" 2.5 true 'a'="a" b'b'="b" of-course {} };
+    let result = html! { this r#"raw_string"#="false" 123="123" 123usize "2.5" true of-course {} };
     assert_eq!(
         result.into_string(),
-        r#"<this byte_string="false" 123="123" 2.5 true a="a" b="b" of-course></this>"#
+        r#"<this raw_string="false" 123="123" 123usize 2.5 true of-course></this>"#
+    );
+}
+
+#[test]
+fn idents_and_literals_in_names() {
+    let result = html! { custom:element-001 test:123-"test"="123" .m-2.p-2 {} };
+    assert_eq!(
+        result.into_string(),
+        r#"<custom:element-001 class="m-2 p-2" test:123-test="123"></custom:element-001>"#
     );
 }
 
diff --git a/maud/tests/warnings/non-string-literal.stderr b/maud/tests/warnings/non-string-literal.stderr
index 635617b..7755e3f 100644
--- a/maud/tests/warnings/non-string-literal.stderr
+++ b/maud/tests/warnings/non-string-literal.stderr
@@ -1,41 +1,29 @@
-error: literal must be double-quoted: `"42"`
- --> $DIR/non-string-literal.rs:5:9
-  |
-5 |         42
-  |         ^^
-
-error: literal must be double-quoted: `"42usize"`
- --> $DIR/non-string-literal.rs:6:9
-  |
-6 |         42usize
-  |         ^^^^^^^
-
 error: literal must be double-quoted: `"42.0"`
- --> $DIR/non-string-literal.rs:7:9
+ --> tests/warnings/non-string-literal.rs:7:9
   |
 7 |         42.0
   |         ^^^^
 
 error: literal must be double-quoted: `"a"`
- --> $DIR/non-string-literal.rs:8:9
+ --> tests/warnings/non-string-literal.rs:8:9
   |
 8 |         'a'
   |         ^^^
 
 error: expected string
- --> $DIR/non-string-literal.rs:9:9
+ --> tests/warnings/non-string-literal.rs:9:9
   |
 9 |         b"a"
   |         ^^^^
 
 error: expected string
-  --> $DIR/non-string-literal.rs:10:9
+  --> tests/warnings/non-string-literal.rs:10:9
    |
 10 |         b'a'
    |         ^^^^
 
 error: attribute value must be a string
-  --> $DIR/non-string-literal.rs:13:24
+  --> tests/warnings/non-string-literal.rs:13:24
    |
 13 |         input disabled=true;
    |                        ^^^^
@@ -44,7 +32,7 @@ error: attribute value must be a string
    = help: to toggle the attribute, use square brackets: `disabled[some_boolean_flag]`
 
 error: attribute value must be a string
-  --> $DIR/non-string-literal.rs:14:24
+  --> tests/warnings/non-string-literal.rs:14:24
    |
 14 |         input disabled=false;
    |                        ^^^^^
diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 662b830..1b85d1f 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -204,7 +204,13 @@ impl Parser {
             }
             // Boolean literals are idents, so `Lit::Bool` is handled in
             // `markup`, not here.
-            Lit::Int(..) | Lit::Float(..) => {
+            Lit::Int(lit_int) => {
+                return ast::Markup::Literal {
+                    content: lit_int.to_string(),
+                    span: SpanRange::single_span(literal.span()),
+                };
+            }
+            Lit::Float(..) => {
                 emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal);
             }
             Lit::Char(lit_char) => {
@@ -702,27 +708,32 @@ impl Parser {
     /// Parses an identifier, without dealing with namespaces.
     fn try_name(&mut self) -> Option<TokenStream> {
         let mut result = Vec::new();
-        match self.peek() {
-            Some(token @ TokenTree::Ident(_)) | Some(token @ TokenTree::Literal(_)) => {
-                self.advance();
-                result.push(token);
-            }
-            _ => return None,
-        };
-        let mut expect_ident = false;
+        let mut expect_ident_or_literal = true;
         loop {
-            expect_ident = match self.peek() {
+            expect_ident_or_literal = match self.peek() {
                 Some(TokenTree::Punct(ref punct)) if punct.as_char() == '-' => {
                     self.advance();
                     result.push(TokenTree::Punct(punct.clone()));
                     true
                 }
-                Some(TokenTree::Ident(ref ident)) if expect_ident => {
+                Some(token @ TokenTree::Ident(_)) if expect_ident_or_literal => {
                     self.advance();
-                    result.push(TokenTree::Ident(ident.clone()));
+                    result.push(token);
                     false
                 }
-                _ => break,
+                Some(TokenTree::Literal(ref literal)) if expect_ident_or_literal => {
+                    self.literal(literal.clone());
+                    self.advance();
+                    result.push(TokenTree::Literal(literal.clone()));
+                    false
+                }
+                _ => {
+                    if result.is_empty() {
+                        return None;
+                    } else {
+                        break;
+                    }
+                }
             };
         }
         Some(result.into_iter().collect())