2017-09-10 2 views
1

Ich experimentierte mit Rust macro_rules und wollte ein Makro erstellen, das eine HTML-ähnliche Syntax parsen und das HTML einfach als String wiedergeben konnte. Die unten Makro wird die meisten der Weg dorthin:Überwindung der "lokalen Mehrdeutigkeit: mehrere Analyseoptionen:" in Rust Makros

macro_rules! html { 
    () => (""); 
    ($text:tt) => {{ 
     format!("{}", $text) 
    }}; 
    (<$open:ident>[$($children:tt)*]</$close:ident>$($rest:tt)*) => {{ 
     format!("<{}>{}</{}>{}", 
      stringify!($open), 
      html!($($children)*), 
      stringify!($close), 
      html!($($rest)*)) 
    }}; 
} 

und dann das Makro zu verwenden:

println!("{}", 
    html!(
     <html>[ 
      <head>[ 
       <title>["Some Title"]</title> 
      ]</head> 
      <body>[ 
       <h1>["This is a header!"]</h1> 
      ]</body> 
     ]</html> 
    ) 
); 

Allerdings würde ich wirklich die äußere Öffnung entfernen mögen und eckigen Klammern schließen. Ich versuche, das zu tun, wie folgt:

macro_rules! html_test { 
    () => (""); 
    ($text:tt) => {{ 
     format!("{}", $text) 
    }}; 
    (<$open:ident>$($children:tt)*</$close:ident>$($rest:tt)*) => {{ 
     format!("<{}>{}</{}>{}", 
      stringify!($open), 
      html!($($children)*), 
      stringify!($close), 
      html!($($rest)*)) 
    }}; 
} 

aber wenn ich dieses Makro zu verwenden:

println!("{}", 
    html_test!(
     <html> 
      <head> 
       <title>"Some Title"</title> 
      </head> 
      <body> 
       <h1>"This is a header!"</h1> 
      </body> 
     </html> 
    ) 
); 

ich die error: local ambiguity: multiple parsing options: built-in NTs tt ('children') or 1 other option.

bekommen

ich die allgemeine Lösung kennen, um diesen Fehler zu Fügen Sie eine Syntax hinzu, um die Fälle zu unterscheiden (z. B. die eckigen Klammern hinzufügen). Gibt es in diesem speziellen Fall ein anderes Problem? Ich weiß, dass die Verwendung von prozeduralen Makros eine extreme Lösung wäre, aber ich würde es vorziehen, macro_rules zu verwenden, wenn überhaupt möglich.

Ich realisiere mit einem Makro, um einfach eine Zeichenfolge mit HTML ist Overkill, aber es war nur für diese Fragen. Potenziell könnte man mit dem Makro viel interessantere Dinge tun, wie beispielsweise Funktionen aufrufen, um einen Baum aufzubauen, der die HTML-Struktur darstellt.

Antwort

4

Möchten Sie, dass das Makro tatsächlich verwendbar ist? Dann nein. Warum überhaupt ein Makro überhaupt benutzen? Egal was du tust, du wirst irgendwann gegen den Rust-Lexer kämpfen. Schreiben Sie einfach den HTML-Code in einem String-Literal wie:

r##"<html> 
    <head> 
     <title>Some Title</title> 
    </head> 
    <body> 
     <h1>This is a header!</h1> 
    </body> 
</html>"## 

Das oder akzeptieren, dass die Makroeingabe nicht Spiel eigentlichen HTML-Syntax, in der Nähe Registerkarte bewegen.


Sie sind immer noch hier? Oh, so Sie nicht kümmern sich um Benutzerfreundlichkeit oder Leistung? Sie wirklich wollen eine marginale Verbesserung der Syntax, unabhängig von den Kosten? * Roll-up Ärmel *

Seien Sie vorsichtig, was Sie wünschen.

Sie müssen einen inkrementellen Parser verwenden, mit dem Sie einige der mehrdeutigen Analyseprobleme umgehen können. Anstatt zu versuchen, eine nicht begrenzte Gruppe zu finden (was nicht möglich ist), passen Sie stattdessen rekursiv eindeutige Präfixe an. das zu tun führt zu:

macro_rules! html_test { 
    (@soup {$($parts:expr,)*}, [],) => { 
     concat!($($parts),*) 
    }; 

    (@soup $parts:tt, [$head:ident $($stack:ident)*],) => { 
     compile_error!(
      concat!(
       "unexpected end of HTML; the following elements need closing: ", 
       stringify!($head), 
       $(",", stringify!($stack),)* 
       "." 
      ) 
     ) 
    }; 

    (@soup {$($parts:tt)*}, [$ex_close:ident $($stack:ident)*], </$got_close:ident> $($tail:tt)*) => { 
     { 
      macro_rules! cmp { 
       ($ex_close) => { 
        html_test!(
         @soup 
         {$($parts)* "</", stringify!($ex_close), ">",}, 
         [$($stack)*], $($tail)* 
        ) 
       }; 
       ($got_close) => { 
        compile_error!(
         concat!(
          "closing element mismatch: expected `", 
          stringify!($ex_close), 
          "`, got `", 
          stringify!($got_close), 
          "`" 
         ) 
        ) 
       }; 
      } 
      cmp!($got_close) 
     } 
    }; 

    (@soup {$($parts:tt)*}, $stack:tt, <img $($tail:tt)*) => { 
     html_test!(@tag {$($parts)* "<img",}, $stack, $($tail)*) 
    }; 

    (@soup {$($parts:tt)*}, [$($stack:ident)*], <$open:ident $($tail:tt)*) => { 
     html_test!(
      @tag 
      {$($parts)* "<", stringify!($open),}, 
      [$open $($stack)*], 
      $($tail)* 
     ) 
    }; 

    (@soup {$($parts:tt)*}, $stack:tt, $text:tt $($tail:tt)*) => { 
     html_test!(@soup {$($parts)* $text,}, $stack, $($tail)*) 
    }; 

    (@tag {$($parts:tt)*}, $stack:tt, > $($tail:tt)*) => { 
     html_test!(@soup {$($parts)* ">",}, $stack, $($tail)*) 
    }; 

    (@tag {$($parts:tt)*}, $stack:tt, $name:ident=$value:tt $($tail:tt)*) => { 
     html_test!(
      @tag 
      {$($parts)* " ", stringify!($name), "=", stringify!($value),}, 
      $stack, $($tail)* 
     ) 
    }; 

    ($($tts:tt)*) => { 
     html_test! { @soup {}, [], $($tts)* } 
    }; 
} 

Diese kriechend über die Eingabe-Token arbeitet, der String-Stücke zu verfolgen, die (in $($parts)*), und die geöffneten Tags ausgegeben werden müssen, die zu schließen müssen (in $($stack)*). Sobald die Eingabe beendet ist und der Stapel leer ist, werden alle Teile zusammen concat! s sein, wodurch ein einzelnes statisches Zeichenfolgenliteral erzeugt wird.

Dies hat vier Probleme:

  1. Diese kaut durch Rekursionsebenen wie verrückt. Wenn Sie nicht mehr aktiv sind, müssen Benutzer das Rekursionslimit global aufstocken.

  2. Makros wie diese sind langsam.

  3. Fehler melden saugt. Obwohl dabei überprüft wird, ob die schließenden Tags den entsprechenden öffnenden Tags entsprechen, werden Probleme an keiner bestimmten Stelle im Aufruf gemeldet.

  4. Sie können immer noch nicht vermeiden, String-Literale zu verwenden. Sie können nicht mit einem Ausdruck übereinstimmen, der von < oder einem anderen Ausdruck gefolgt wird, so dass die Strings übereinstimmen muss die (einzige) Fallback-Regel sein.

Sie also können die Trennzeichen entfernen, aber ich würde es nicht empfehlen. Zitieren Sie den HTML-Code einfach wie eine vernünftige Person.


Als beiseite, hier ist ein alternative version of the macro mit einer etwas anderen Struktur, die die cmp Makrofaktoren aus, und ist einfacher zu erweitern für Elemente ohne Tags zu schließen. Beachten Sie, dass ich diese Version nicht geschrieben habe.

+0

Danke für Ihre Antwort. Mir ist klar, dass die Verwendung dieses Makros zum einfachen Aufbau einer Zeichenkette ein bisschen lächerlich ist, aber das war nur der Frage zuliebe. Ich kann mir vorstellen, viel interessantere Dinge zu tun. Ich habe meine Frage bearbeitet, um dies zu erwähnen. Der größte Schmerzpunkt für mich sind die eckigen Klammern, die ich hinter die Notwendigkeit, String-Literale zu zitieren, schauen kann. Weißt du, ob das neue "Makro" -System das rosten würde? Würden Sie ein Prozedurmakro dafür empfehlen? – user7400966

+0

@ user7400966: Rust's neues Makro-System existiert noch nicht, so kurz vor Zeitreisen, ich kann das nicht beantworten. Wie bei der Verwendung von prozeduralen Makros könnten Sie wahrscheinlich mit einigen Verrenkungen umgehen, die nur für Ableitungen unterstützt werden. –

Verwandte Themen