Können Sie sich einen eleganteren Weg zu ‚tokenize‘ c # Code für die HTML-Formatierung vorschlagen?
-
04-07-2019 - |
Frage
( Diese Frage über F # Code Refactoring hat mich eine nach unten Stimme, aber auch einige interessante und nützliche Antworten. und 62 F # Fragen aus dem 32.000 auf sO scheint erbärmlich, also werde ich das Risiko von mehr Ablehnung nehmen!)
Ich habe versucht, ein Stück Code auf einem Blogger-Blog gestern zu schreiben, und wandte sich href="http://manoli.net/csharpformat/" rel="nofollow noreferrer"> dieser Seite
So (wie jeder Hacker), dachte ich, „wie schwer es sein kann?“ und rollte mit meinem eigenen in <100 Zeilen F #. Hier ist das ‚Fleisch‘ des Codes, der eine Eingabezeichenfolge in eine Liste von ‚Token‘ dreht. Beachten Sie, dass diese Token sind nicht mit dem lexing / Parsing-Stil Token verwechselt werden. Ich habe bei diesen kurzen Blick, und obwohl ich kaum etwas verstanden, ich verstand, dass sie mir geben würde, nur Token, während ich möchte, dass meine ursprüngliche Zeichenfolge halten. Die Frage ist: Gibt es eine elegantere Möglichkeit, dies zu tun? Ich weiß nicht, wie die n Wieder Definitionen s erforderlich, um jeden Token-String aus der Eingabezeichenfolge zu entfernen, aber es ist schwierig, die Zeichenfolge in potentielle Token im Voraus zu spalten, weil Dinge wie Kommentare, Strings und die #region Richtlinie (die enthält ein nicht-Wort-Zeichen). (Wenn jemand wirklich interessiert ist, ich bin glücklich, den Rest des Codes zu schreiben) Bearbeiten
Mit Hilfe der ausgezeichneter Vorschlag von aktive Muster von KVB, das zentrale Bit wie folgt aussieht, viel besser! //Types of tokens we are going to detect
type Token =
| Whitespace of string
| Comment of string
| Strng of string
| Keyword of string
| Text of string
| EOF
//turn a string into a list of recognised tokens
let tokenize (s:String) =
//this is the 'parser' - should we look at compiling the regexs in advance?
let nexttoken (st:String) =
match st with
| st when Regex.IsMatch(st, "^\s+") -> Whitespace(Regex.Match(st, "^\s+").Value)
| st when Regex.IsMatch(st, "^//.*?\r?\n") -> Comment(Regex.Match(st, "^//.*?\r?\n").Value) //this is double slash-style comments
| st when Regex.IsMatch(st, "^/\*(.|[\r?\n])*?\*/") -> Comment(Regex.Match(st, "^/\*(.|[\r?\n])*?\*/").Value) // /* */ style comments http://ostermiller.org/findcomment.html
| st when Regex.IsMatch(st, @"^""([^""\\]|\\.|"""")*""") -> Strng(Regex.Match(st, @"^""([^""\\]|\\.|"""")*""").Value) // unescaped = "([^"\\]|\\.|"")*" http://wordaligned.org/articles/string-literals-and-regular-expressions
| st when Regex.IsMatch(st, "^#(end)?region") -> Keyword(Regex.Match(st, "^#(end)?region").Value)
| st when st <> "" ->
match Regex.Match(st, @"^[^""\s]*").Value with //all text until next whitespace or quote (this may be wrong)
| x when iskeyword x -> Keyword(x) //iskeyword uses Microsoft.CSharp.CSharpCodeProvider.IsValidIdentifier - a bit fragile...
| x -> Text(x)
| _ -> EOF
//tail-recursive use of next token to transform string into token list
let tokeneater s =
let rec loop s acc =
let t = nexttoken s
match t with
| EOF -> List.rev acc //return accumulator (have to reverse it because built backwards with tail recursion)
| Whitespace(x) | Comment(x)
| Keyword(x) | Text(x) | Strng(x) ->
loop (s.Remove(0, x.Length)) (t::acc) //tail recursive
loop s []
tokeneater s
let nexttoken (st:String) =
match st with
| Matches "^\s+" s -> Whitespace(s)
| Matches "^//.*?\r?(\n|$)" s -> Comment(s) //this is double slash-style comments
| Matches "^/\*(.|[\r?\n])*?\*/" s -> Comment(s) // /* */ style comments http://ostermiller.org/findcomment.html
| Matches @"^@?""([^""\\]|\\.|"""")*""" s -> Strng(s) // unescaped regexp = ^@?"([^"\\]|\\.|"")*" http://wordaligned.org/articles/string-literals-and-regular-expressions
| Matches "^#(end)?region" s -> Keyword(s)
| Matches @"^[^""\s]+" s -> //all text until next whitespace or quote (this may be wrong)
match s with
| IsKeyword x -> Keyword(s)
| _ -> Text(s)
| _ -> EOF
Lösung
Ich würde ein aktives Muster verwenden, um die Regex.IsMatch und Regex.Match Paare zu verkapseln, etwa so:
let (|Matches|_|) re s =
let m = Regex(re).Match(s)
if m.Success then
Some(Matches (m.Value))
else
None
Dann ist Ihre Funktion kann nexttoken wie folgt aussehen:
let nexttoken (st:String) =
match st with
| Matches "^s+" s -> Whitespace(s)
| Matches "^//.*?\r?\n" s -> Comment(s)
...