Ich habe einen einfachen Phrasenanalysator zur Verwendung in meinem eigenen Java-Konsolenprogramm erstellt. Ich habe eine Token-Klasse in Vorheriger Artikel erstellt. Diesmal ist dies der Hauptteil des Phrasenanalysators. Für die Phrasenanalyse ist es einfacher, verschiedene Bibliotheken zu verwenden. Da sie jedoch am Arbeitsplatz verwendet werden, an dem das Herunterladen von externen Websites verboten ist, wird sie nur mit Java8-Funktionen von Grund auf neu erstellt.
22.10.2018 Die Quelle wurde geändert. 2018/10/24 Es wurde ein Fehler behoben, durch den eine Kopie der Analysezielzeichenfolge im Konstruktor gespeichert wurde.
Beim Erstellen verweise ich auf Implementieren einer einfachen Phrasenanalyse in Java.
Diesmal sind die Quelle und ihre Erklärung lang, siehe Vorheriger Artikel.
LexicalAnalyzer.java
package console;
public class LexicalAnalyzer {
public static LexicalAnalyzer create(String target) {
if ( target == null || target.trim().isEmpty() ) { target = ""; }
return new LexicalAnalyzer(target);
}
public java.util.List<Token> analyze() {
char c;
while ( (c = next()) != '\0' ) {
if ( isSymbol_1(c) ) {
tokens_.add( Token.create(c) );
continue;
}
if ( isQuote(c) ) {
quotedText(c);
continue;
}
text(c);
}
return new java.util.ArrayList<>(tokens_);
}
// query methods ================================================================================
public boolean isEmpty() { return tokens_.size() == 0;}
public boolean isValid() {
return !isEmpty() && tokens_.stream().noneMatch( e -> e.kind() == Token.Kinds.Unknown );
}
// internal methods ======================================================================
/**Schneiden Sie bis zum nächsten Anführungszeichen als Token-Block aus. */
private void quotedText(char quote) {
tokens_.add( Token.create(quote)); // create token of begin quote
java.lang.StringBuilder builder = new java.lang.StringBuilder();
char c;
while ( (c = nextAll()) != '\0' && c != quote) { builder.append(c); }
if ( builder.length() != 0 ) {
tokens_.add( Token.create(builder.toString()) ); // append string
}
tokens_.add( Token.create(c) ); // append token of end quote
}
/**Schneiden Sie Trennzeichen und leere Zeichen als Token-Block aus. */
private void text(char first) {
java.lang.StringBuilder builder = new java.lang.StringBuilder();
builder.append(first);
char c;
while ( (c = nextAll()) != '\0' && !isSeparator(c) && !isWhitespace(c) ) {
builder.append(c);
}
tokens_.add( Token.create(builder.toString()) );
// append separator token, if not end of text
if ( isEnd() ) { return; }
tokens_.add( isWhitespace(c) ? Token.create(' ') : Token.create(c) );
}
private char next() {
skipSpace();
return nextAll();
}
private char nextAll() {
char c = aChar();
++pos_;
return c;
}
private char aChar() { return isEnd() ? '\0' : target_.charAt(pos_); }
private void skipSpace() {
while ( !isEnd() && Character.isWhitespace(aChar()) ) { pos_++; }
}
private boolean isEnd() { return length_ <= pos_; }
private boolean isSeparator(char c) { return exists(separators_, c); }
private boolean isQuote(char c) { return exists(quotes_, c); }
private boolean isSymbol_1(char c) { return exists(symbol1_, c); }
private boolean isWhitespace(char c) { return Character.isWhitespace(c); }
private boolean exists(char[] arr, char c) {
return java.util.Arrays.binarySearch(arr, c) >= 0;
}
private LexicalAnalyzer(String target) {
target_ = target;
length_ = target.length();
}
// internal fields ======================================================================
private static final char[] separators_ = { ':', ',', '=', '(', ')', '{', '}' };
static { Arrays.sort(separators_); }
private static final char[] quotes_ = { '"', '\'' };
static { Arrays.sort(quotes_); }
private static final char[] symbol1_ = {'(', ')', '{', '}', ':', ',', '=', '&' };
static { Arrays.sort(symbol1_); }
final String target_; // analyze target string
final int length_; // length of target string
int pos_ = 0; // "next" analyzing position
java.util.List<Token> tokens_ = new java.util.ArrayList<>(); // result
}
Ursprünglich möchte ich die zu analysierende Zeichenfolge als Argument obligatorisch machen, aber es gibt keine Möglichkeit, sie dem Aufrufer aufzuzwingen. Setzen Sie daher eine leere Zeichenfolge auf die Zielzeichenfolge. Dies befreit den Anrufer von der Mühe der Nullprüfung und der Ausnahmebehandlung. Die Argumente im Konstruktor müssen übrigens nicht überprüft werden.
** <Ändern> ** Legen Sie beim Festlegen der Zeichenfolge eine Kopie des Arguments fest. Wenn Sie es nicht kopieren, können Sie die Zielzeichenfolge extern ändern.
public static LexicalAnalyzer create(String target) {
if ( target == null || target.trim().isEmpty() ) { target = ""; }
return new LexicalAnalyzer(target);
}
private LexicalAnalyzer(String target) {
target_ = new String(target);
length_ = target.length();
}
final String target_; // analyze target string
final int length_; // length of target string
Da es sich um einen Phrasenanalysator handelt, extrahiert er die Zielzeichenfolge zeichenweise und verarbeitet sie. Die Grundfunktionen hierfür sind die folgende Methodengruppe.
`static {...}`
unmittelbar nach der Array-Initialisierung) Die Sortierung erfolgt mit. private char next() {
skipSpace();
return nextAll();
}
private char nextAll() {
char c = aChar();
++pos_;
return c;
}
private char aChar() { return isEnd() ? '\0' : target_.charAt(pos_); }
private void skipSpace() {
while ( !isEnd() && Character.isWhitespace(aChar()) ) { pos_++; }
}
private boolean isEnd() { return length_ <= pos_; }
private boolean isSeparator(char c) { return exists(separators_, c); }
private boolean isQuote(char c) { return exists(quotes_, c); }
private boolean isSymbol_1(char c) { return exists(symbol1_, c); }
private boolean isWhitespace(char c) { return Character.isWhitespace(c); }
private boolean exists(char[] arr, char c) {
return java.util.Arrays.binarySearch(arr, c) >= 0;
}
private static final char[] separators_ = { ',', '=', '(', ')', '{', '}', ':' };
static { Arrays.sort(separators_); }
private static final char[] quotes_ = { '"', '\'' };
static { Arrays.sort(quotes_); }
private static final char[] symbol1_ = { '(', ')', '{', '}', ':', ',', '=', '&' };
static { Arrays.sort(symbol1_); }
int pos_ = 0; // "next" analyzing position
Führt eine lexikalische Analyse durch und gibt das Ergebnis als Liste zuvor erstellter Token-Objekte zurück. Der Rückgabewert ist eine Kopie der Token-Liste tokens_, die intern gespeichert ist. Da es sich um eine Standardliste handelt, kann der Aufrufer Elemente hinzufügen / entfernen, was sich auf die vom Objekt gehaltenen Token_ auswirkt (die sich genau auf dasselbe Objekt beziehen). Wenn Sie analyse () ein zweites Mal oder später aufrufen, wird eine Kopie der bereits erstellten Token-Liste zurückgegeben. Es wird keine erneute Analyse durchgeführt.
Durch Ändern der Implementierung von next () (Überspringen von Leerzeichen) und Hinzufügen einer Beurteilungsmethode ist diese sauberer als die Steuerung durch die vorherige switch-Anweisung. Die Absicht des Urteils spiegelt sich übrigens im Methodennamen wider, sodass kein Kommentar erforderlich ist. (Ist es nicht?)
public java.util.List<Token> analyze() {
char c;
while ( (c = next()) != '\0' ) {
if ( isSymbol_1(c) ) {
tokens_.add( Token.create(c) );
continue;
}
if ( isQuote(c) ) {
quotedText(c);
continue;
}
text(c);
}
return new java.util.ArrayList<>(tokens_);
}
java.util.List<Token> tokens_ = new java.util.ArrayList<>(); // result
Machen Sie die Zeichenfolge in Anführungszeichen zu einem einzelnen Token, einschließlich Leerzeichen. Der Leerraum zwischen dem Anführungszeichen und dem ersten Zeichen sowie zwischen dem letzten Zeichen und dem Anführungszeichen wird entfernt, aber der Leerraum zwischen den anderen Zeichenfolgen, einschließlich des Zeilenvorschubs und der Tabulatorzeichen, bleibt erhalten ..
/**Schneiden Sie bis zum nächsten Anführungszeichen als Token-Block aus. */
private void quotedText(char quote) {
tokens_.add( Token.create(quote)); // create token of begin quote
java.lang.StringBuilder builder = new java.lang.StringBuilder();
char c;
while ( (c = nextAll()) != '\0' && c != quote) { builder.append(c); }
if ( builder.length() != 0 ) {
tokens_.add( Token.create(builder.toString()) ); // append string
}
tokens_.add( Token.create(c) ); // append token of end quote
}
Schneiden Sie ein Zeichenfolgentoken aus, das nicht in Anführungszeichen steht.
/**Schneiden Sie Trennzeichen und leere Zeichen als Token-Block aus. */
private void text(char first) {
java.lang.StringBuilder builder = new java.lang.StringBuilder();
builder.append(first);
char c;
while ( (c = nextAll()) != '\0' && !isSeparator(c) && !Character.isWhitespace(c) ) {
builder.append(c);
}
tokens_.add( Token.create(builder.toString()) );
if ( isEnd() ) { return; }
tokens_.add( isWhitespace(c) ? Token.create(' ') : Token.create(c) );
}
LexicalAnalyzer-Statuserfassungsmethode.
public boolean isEmpty() { return tokens_.size() == 0;}
public boolean isValid() {
return !isEmpty() && tokens_.stream().noneMatch( e -> e.kind() == Token.Kinds.Unknown );
}
Last but not least finden Sie hier die main () -Methode zum Testen und die Verarbeitungsergebnisse. ~~ Ich kenne JUnit nicht ~~ Das Trennzeichen-Token wird ausgeblendet, um die Anzahl der Zeilen im Verarbeitungsergebnis zu verringern.
public static void main(String[] args) {
String s;
java.util.List<Token> tokens;
LexicalAnalyzer lex;
System.out.println("test 1 -----------------------------------------------------------------------------------");
LexicalAnalyzer.create(null).analyze().stream().filter( e -> e.kind() != Token.Kinds.Separator )
.forEach( e -> System.out.println(e) );
System.out.printf("isEmpty() after analyze : %s\r\n", lex.isEmpty());
System.out.println("\r\ntest 2 -------------------------------------------------------------------------------");
s = "s";
lex = LexicalAnalyzer.create(s);
System.out.printf("isEmpty() before analyze() : %s\r\n", lex.isEmpty());
lex.analyze().stream().filter( e -> e.kind() != Token.Kinds.Separator )
.forEach( e -> System.out.println(e) );
System.out.printf("isEmpty() after analyze() : %s\r\n", lex.isEmpty());
System.out.println("test 3 -----------------------------------------------------------------------------------");
s = " [some] -c \" text document \", \r\n (sequence1, \"seq 2\r\n quoted\",seq3 seq4 'seq 5 ')\"ss";
lex = LexicalAnalyzer.create(s);
System.out.printf("isValid() before analyze() : %s\r\n", lex.isValid());
lex.analyze().stream().filter( e -> e.kind() != Token.Kinds.Separator )
.forEach( e -> System.out.println(e) );
System.out.printf("isValid() after analyze() : %s\r\n", lex.isValid());
}
Bei der Factory-Methode wird eine leere Zeichenfolge so festgelegt, dass sie fehlerfrei funktioniert. Selbst wenn analyse () und der nachfolgende stream () ausgeführt werden, ist dies kein Problem. Es wird nichts angezeigt. "isEmpty () ..." ist das Testergebnis der isEmpty () -Methode.
Anstatt die Analyseergebnisse zu überprüfen, überprüfen Sie die Spezifikationen von isEmpty () vor und nach der Ausführung von analyse ().
Da "seq 2 ..." einen Zeilenumbruch im Anführungszeichen enthält, enthält das Verarbeitungsergebnis auch einen Zeilenumbruch. Der Grund, warum isValid () am Ende falsch ist, ist, dass die Analyse als Anführungszeichen gestartet wurde, aber kein Anführungszeichenpaar vorhanden war und die Zeichenfolge beendet wurde. Wenn es richtig geschlossen wird, ist es wahr.
test 1 -------------------------------------------------------------------------------
isEmpty() after analyze : true
test 2 -------------------------------------------------------------------------------
isEmpty() before analyze() : true
[String : "s"]
isEmpty() after analyze() : false
test 3 -------------------------------------------------------------------------------
isValid() before analyze() : false
[String : "[some]"]
[String : "-c"]
[DoubleQuote : """]
[String : "text document"]
[DoubleQuote : """]
[LeftParenthesis : "("]
[String : "sequence1"]
[DoubleQuote : """]
[String : "seq 2
quoted"]
[DoubleQuote : """]
[String : "seq3"]
[String : "seq4"]
[SingleQuote : "'"]
[String : "seq 5"]
[SingleQuote : "'"]
[RightParenthesis: ")"]
[DoubleQuote : """]
[String : "ss"]
[Unknown : "**Unknown**"]
isValid() after analyze() : false
Es ist lange her, aber dies ist das Ende der Erstellung eines Phrasenanalysators.
Wie wäre es bisher mit Syntaxanalyse? Ich kann es jedoch nicht schreiben, da die Syntax des Benutzers nicht festgelegt wurde. Die erwartete Grammatik wurde bis zu einem gewissen Grad festgelegt, aber es ist unentschlossen, wann sie geschrieben werden soll, da die Vorbereitung dafür nicht aufholt. (Sie müssen zuerst die Konsolenklasse abschließen ...)
Recommended Posts