ParseTreeCleaner + SyntaxTreeRebalancer

This commit is contained in:
ChUrl
2021-01-31 20:10:04 +01:00
parent 108a2001e8
commit 7e233d02a0
5 changed files with 181 additions and 119 deletions

View File

@ -7,28 +7,42 @@ import java.util.HashSet;
import static util.Logger.log;
public final class ASTCompacter {
/**
* Wendet in der Grammatik definierte Regeln auf einen Parsebaum an.
* Dies ist der erste Schritt zum Abstrakten Syntaxbaum.
*
* <ul>
* <li>Löscht redundante Knoten</li>
* <li>Löscht leere Knoten</li>
* <li>Komprimiert Äste, welche nur Informationen hochpropagieren</li>
* <li>Führt Umbenennungen durch</li>
* <li>Verschiebt Informationen in Knoten-Namen und -Wert</li>
* </ul>
*/
public final class ParseTreeCleaner {
private ASTCompacter() {}
private ParseTreeCleaner() {}
public static void clean(SyntaxTree tree, Grammar grammar) {
deleteChildren(tree, grammar);
deleteIfEmpty(tree, grammar);
promote(tree, grammar);
public static void clean(SyntaxTree parseTree, Grammar grammar) {
deleteChildren(parseTree, grammar);
deleteIfEmpty(parseTree, grammar);
promote(parseTree, grammar);
renameTo(tree, grammar);
nameToValue(tree, grammar);
valueToValue(tree, grammar);
renameTo(parseTree, grammar);
nameToValue(parseTree, grammar);
valueToValue(parseTree, grammar);
log("\nCleaned Tree:\n" + tree);
log("\nCleaned Tree:\n" + parseTree);
log("-".repeat(100));
System.out.println(" - Compressing syntax-tree...");
}
// Entfernt [promote]-able Nodes (Reicht Werte nach oben)
public static void promote(SyntaxTree tree, Grammar grammar) {
/**
* Es werden Werte nach oben gereicht von [promote]-able Nodes.
*/
public static void promote(SyntaxTree parseTree, Grammar grammar) {
log("\nPromoting nodes:");
promote(tree.getRoot(), grammar);
promote(parseTree.getRoot(), grammar);
}
private static void promote(SyntaxTreeNode root, Grammar grammar) {
@ -49,17 +63,19 @@ public final class ASTCompacter {
root.setValue(child.getValue());
root.setChildren(child.getChildren());
child.setValue("REMOVE"); // If both childs have the same identity both are removed
child.setValue("REMOVE"); // If both childs have the same identity both are removed, so change one
toRemove.add(child);
}
root.getChildren().removeAll(toRemove);
}
// Entfernt [delIfEmpty] Nodes (löscht Nodes ohne Inhalt)
public static void deleteIfEmpty(SyntaxTree tree, Grammar grammar) {
/**
* Löscht leere Knoten mit [delIfEmpty].
*/
public static void deleteIfEmpty(SyntaxTree parseTree, Grammar grammar) {
log("\nDeleting empty nodes:");
deleteIfEmpty(tree.getRoot(), grammar);
deleteIfEmpty(parseTree.getRoot(), grammar);
}
private static void deleteIfEmpty(SyntaxTreeNode root, Grammar grammar) {
@ -74,17 +90,19 @@ public final class ASTCompacter {
log("Removing " + child.getName());
child.setValue("REMOVE"); // If both childs have the same identity both are removed
child.setValue("REMOVE"); // If both childs have the same identity both are removed, so change one
toRemove.add(child);
}
root.getChildren().removeAll(toRemove);
}
// Löscht redundante Informationen in [delChildren]-Nodes (z.b. IF-child von COND) und Epsilon-Nodes
public static void deleteChildren(SyntaxTree tree, Grammar grammar) {
/**
* Löscht redundante Informationen in [delChildren]-Nodes (z.b. IF-child von COND) und Epsilon-Nodes.
*/
public static void deleteChildren(SyntaxTree parseTree, Grammar grammar) {
log("Removing redundant children:");
deleteChildren(tree.getRoot(), grammar);
deleteChildren(parseTree.getRoot(), grammar);
}
private static void deleteChildren(SyntaxTreeNode root, Grammar grammar) {
@ -99,17 +117,19 @@ public final class ASTCompacter {
log("Removing " + root.getName() + " -> " + child.getName());
child.setValue("REMOVE"); // If both childs have the same identity both are removed
child.setValue("REMOVE"); // If both childs have the same identity both are removed, so change one
toRemove.add(child);
}
root.getChildren().removeAll(toRemove);
}
// Umbenennungen
private static void renameTo(SyntaxTree tree, Grammar grammar) {
/**
* Führt Umbenennungen durch.
*/
private static void renameTo(SyntaxTree parseTree, Grammar grammar) {
log("\nRenaming nodes:");
renameTo(tree.getRoot(), grammar);
renameTo(parseTree.getRoot(), grammar);
}
private static void renameTo(SyntaxTreeNode root, Grammar grammar) {
@ -126,9 +146,12 @@ public final class ASTCompacter {
}
}
public static void nameToValue(SyntaxTree tree, Grammar grammar) {
/**
* Verschiebt Knotennamen von [nametoval]-Nodes in Parent-Values und löscht das Child.
*/
public static void nameToValue(SyntaxTree parseTree, Grammar grammar) {
log("\nMoving names to values:");
nameToValue(tree.getRoot(), grammar);
nameToValue(parseTree.getRoot(), grammar);
}
private static void nameToValue(SyntaxTreeNode root, Grammar grammar) {
@ -146,17 +169,21 @@ public final class ASTCompacter {
root.setValue(child.getName());
child.setValue("REMOVE"); // If both childs have the same identity both are removed
child.setValue("REMOVE"); // If both childs have the same identity both are removed, so change one
toRemove.add(child);
}
root.getChildren().removeAll(toRemove);
}
// Assignment bekommt den Identifier als Value anstatt als Child
public static void valueToValue(SyntaxTree tree, Grammar grammar) {
/**
* [valtoval]-Nodes bekommen den Child-Namen als Value anstatt als Child.
* Wird z.B. durchgeführt bei Assignments: Der Assignment-Node bekommt den
* Variablennamen als Wert anstatt als Child-Node.
*/
public static void valueToValue(SyntaxTree parseTree, Grammar grammar) {
log("\nMoving values to values:");
valueToValue(tree.getRoot(), grammar);
valueToValue(parseTree.getRoot(), grammar);
}
private static void valueToValue(SyntaxTreeNode root, Grammar grammar) {
@ -165,36 +192,31 @@ public final class ASTCompacter {
for (SyntaxTreeNode child : root.getChildren()) {
valueToValue(child, grammar);
if (!grammar.hasValToVal(root, child)) {
continue;
}
if (!root.getValue().isBlank()) {
// Do not overwrite
if (!grammar.hasValToVal(root, child) || !root.getValue().isBlank()) {
continue;
}
if (root.getChildren().size() == 2
&& root.getChildren().get(0).getName().equals(root.getChildren().get(1).getName())) {
// Special case where variable is assigned another variable
// Case where variable is assigned another variable with the same name
log("Special case: Var to var assignment");
log("Moving " + root.getChildren().get(1).getValue() + " to value of " + root.getName());
log(root.toString());
root.setValue(root.getChildren().get(1).getValue());
root.getChildren().get(1).setValue("REMOVE"); // If both childs have the same identity both are removed
root.getChildren().get(1).setValue("REMOVE"); // If both childs have the same identity both are removed, so change one
toRemove.add(root.getChildren().get(1));
continue;
} else {
// Usual case where an expression is assigned
log("Moving " + child.getValue() + " to value of " + root.getName());
log(root.toString());
root.setValue(child.getValue());
toRemove.add(child);
}
log("Moving " + child.getValue() + " to value of " + root.getName());
log(root.toString());
root.setValue(child.getValue());
toRemove.add(child);
}
root.getChildren().removeAll(toRemove);

View File

@ -23,8 +23,8 @@ public class SyntaxTree {
public static SyntaxTree toAbstractSyntaxTree(SyntaxTree concreteSyntaxTree, Grammar grammar) {
final SyntaxTree abstractSyntaxTree = concreteSyntaxTree.deepCopy();
ASTCompacter.clean(abstractSyntaxTree, grammar);
ASTBalancer.balance(abstractSyntaxTree);
ParseTreeCleaner.clean(abstractSyntaxTree, grammar);
SyntaxTreeRebalancer.rebalance(abstractSyntaxTree);
System.out.println("Tree processing successful.");
return abstractSyntaxTree;

View File

@ -6,11 +6,19 @@ import java.util.Set;
import static util.Logger.log;
public final class ASTBalancer {
/**
* Ein SyntaxTree wird an bestimmten Stellen rotiert, sodass bestimmte Eigenschaften
* korrekt repräsentiert werden (Operatorpräzedenz, Linkassoziativität etc.).
*/
public final class SyntaxTreeRebalancer {
private static final Map<String, Integer> priority;
private static final Set<String> unary;
private static final Set<String> commutative;
/**
* Jedem Operator wird eine Priorität zugewiesen, 0 ist die höchste.
*/
private static final Map<String, Integer> operatorPriority;
private static final Set<String> unaryOperators;
private static final Set<String> commutativeOperators;
//!: Operatorpräzedenz
// 0 - Unary: -, +, !
@ -21,44 +29,56 @@ public final class ASTBalancer {
// 5 - Logical AND: &&
// 6 - Logical OR: ||
static {
priority = Map.ofEntries(Map.entry("NOT", 0),
Map.entry("MUL", 1),
Map.entry("DIV", 1),
Map.entry("MOD", 1),
Map.entry("ADD", 2),
Map.entry("SUB", 2),
Map.entry("LESS", 3),
Map.entry("LESS_EQUAL", 3),
Map.entry("GREATER", 3),
Map.entry("GREATER_EQUAL", 3),
Map.entry("EQUAL", 4),
Map.entry("NOT_EQUAL", 4),
Map.entry("AND", 5),
Map.entry("OR", 6));
operatorPriority = Map.ofEntries(Map.entry("NOT", 0),
Map.entry("MUL", 1),
Map.entry("DIV", 1),
Map.entry("MOD", 1),
Map.entry("ADD", 2),
Map.entry("SUB", 2),
Map.entry("LESS", 3),
Map.entry("LESS_EQUAL", 3),
Map.entry("GREATER", 3),
Map.entry("GREATER_EQUAL", 3),
Map.entry("EQUAL", 4),
Map.entry("NOT_EQUAL", 4),
Map.entry("AND", 5),
Map.entry("OR", 6));
unary = Set.of("NOT", "ADD", "SUB");
unaryOperators = Set.of("NOT", "ADD", "SUB");
commutative = Set.of("ADD", "MUL", "EQUAL", "NOT_EQUAL", "AND", "OR");
commutativeOperators = Set.of("ADD", "MUL", "EQUAL", "NOT_EQUAL", "AND", "OR");
}
private ASTBalancer() {}
private SyntaxTreeRebalancer() {}
public static void balance(SyntaxTree tree) {
flip(tree);
leftPrecedence(tree);
operatorPrecedence(tree);
flipCommutativeExpr(tree);
/**
* Ein Abstrakter Syntaxbaum wird umbalanciert.
*
* <ul>
* <li>Baum wird gespiegelt, damit die Ausdrücke vorwárts laufen (Tiefste Ebenen müssen nach links)</li>
* <li>Linkspräzedenz wird durch Links-Rotationen durchgesetzt</li>
* <li>Operatorpräzedenz wird durch Rechtsrotationen durchgesetzt</li>
* <li>Kommutative Ausdrücke werden gespiegelt, damit die tiefen Teilausdrücke zuerst berechnet werden</li>
* </ul>
*/
public static void rebalance(SyntaxTree abstractSyntaxTree) {
flip(abstractSyntaxTree);
leftPrecedence(abstractSyntaxTree);
operatorPrecedence(abstractSyntaxTree);
flipCommutativeExpr(abstractSyntaxTree);
log(tree.toString());
log(abstractSyntaxTree.toString());
log("-".repeat(100));
System.out.println(" - Balancing syntax-tree...");
}
// Baum spiegeln, damit höhere Ebenen links sind und EXPR vorwärts laufen
public static void flip(SyntaxTree tree) {
/**
* Baum spiegeln, damit höhere Ebenen links sind und EXPR vorwärts laufen.
*/
public static void flip(SyntaxTree abstractSyntaxTree) {
log("Flipping tree for ltr evaluation");
flip(tree.getRoot());
flip(abstractSyntaxTree.getRoot());
}
private static void flip(SyntaxTreeNode root) {
@ -69,9 +89,12 @@ public final class ASTBalancer {
Collections.reverse(root.getChildren());
}
public static void flipCommutativeExpr(SyntaxTree tree) {
/**
* Kommutative Ausdrücke werden gespiegelt, damit die tiefen Teilexpressions zuerst berechnet werden.
*/
public static void flipCommutativeExpr(SyntaxTree abstractSyntaxTree) {
log("Flipping commutative expressions for stack efficiency");
flipCommutativeExpr(tree.getRoot());
flipCommutativeExpr(abstractSyntaxTree.getRoot());
}
private static void flipCommutativeExpr(SyntaxTreeNode root) {
@ -79,9 +102,12 @@ public final class ASTBalancer {
flipCommutativeExpr(child);
}
if ("expr".equals(root.getName()) && commutative.contains(root.getValue())) {
if ("expr".equals(root.getName()) && commutativeOperators.contains(root.getValue())) {
// Ausdruck ist kommutativ
if (root.getChildren().size() == 2 && root.getChildren().get(0).size() < root.getChildren().get(1).size()) {
// Make the bigger subtree the left one
log("Flipping " + root.getName() + ": " + root.getValue() + " for stack efficiency.");
log(root.toString());
@ -90,14 +116,15 @@ public final class ASTBalancer {
}
}
// Führt Linksrotationen durch
// Es werden EXPR-Nodes (2 Childs, 1 davon EXPR, Kein Wert) solange wie möglich linksrotiert
public static void leftPrecedence(SyntaxTree tree) {
/**
* Führt Linksrotationen durch für Linkspräzedenz.
* Es werden EXPR-Nodes (2 Childs, 1 davon EXPR, Kein Wert) solange wie möglich linksrotiert.
*/
public static void leftPrecedence(SyntaxTree abstractSyntaxTree) {
log("Left-rotating expressions for left-precedence");
leftPrecedence(tree.getRoot());
leftPrecedence(abstractSyntaxTree.getRoot());
}
// Es wird solange rotiert bis die letzte "Rotation" durchgeführt wurde
private static void leftPrecedence(SyntaxTreeNode root) {
for (SyntaxTreeNode child : root.getChildren()) {
leftPrecedence(child);
@ -116,7 +143,12 @@ public final class ASTBalancer {
} while (change);
}
// Die Letzte Rotation ist keine richtige Rotation, dort wird false zurückgegeben
/**
* Führt eine Linksrotation durch.
* Diese ist nicht regulär, da der Operator linksvererbt wird.
*
* @return Es wird false zurückgegeben, sobald keine weitere Rotation mehr möglich ist.
*/
private static boolean specialLeftRotate(SyntaxTreeNode root) {
log("Special-Left-Rotation around " + root.getName());
log(root.toString());
@ -157,13 +189,18 @@ public final class ASTBalancer {
return root.getChildren().size() == 1;
}
public static void operatorPrecedence(SyntaxTree tree) {
/**
* Führt Rechtsrotationen durch für Operatorpräzedenz.
* Es wird solange rechtsrotiert, bis alle Operatoren mit hoher Priorität tiefer stehen
* als die Operatoren mit niedriger Priorität.
*/
public static void operatorPrecedence(SyntaxTree abstractSyntaxTree) {
log("Right-rotating expressions for operator-precedence");
boolean changed;
do {
changed = operatorPrecedence(tree.getRoot());
changed = operatorPrecedence(abstractSyntaxTree.getRoot());
} while (changed);
}
@ -182,6 +219,9 @@ public final class ASTBalancer {
return changed;
}
/**
* Ermittelt, ob der ParentNode höhere Priorität als der ChildNode hat.
*/
private static boolean preceding(SyntaxTreeNode parent, SyntaxTreeNode child) {
if (!"expr".equals(parent.getName()) || parent.getValue().isEmpty()
|| !"expr".equals(child.getName()) || child.getValue().isEmpty()) {
@ -189,13 +229,13 @@ public final class ASTBalancer {
}
// Unary operators have the highest precedence
if (child.getChildren().size() == 1 && unary.contains(child.getValue())) {
if (child.getChildren().size() == 1 && unaryOperators.contains(child.getValue())) {
return false;
}
// Less equals higher
{
return priority.get(parent.getValue()) < priority.get(child.getValue());
return operatorPriority.get(parent.getValue()) < operatorPriority.get(child.getValue());
}
}

View File

@ -17,21 +17,21 @@ import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
class SyntaxTreeCompacterTest {
class ParseTreeCleanerTest {
private static Grammar grammar;
private static StupsParser parser;
@BeforeAll
static void init() throws IOException, URISyntaxException {
final Path path = Paths.get(SyntaxTreeCompacterTest.class.getClassLoader().getResource("exampleGrammars/Grammar.grammar").toURI());
final Path path = Paths.get(ParseTreeCleanerTest.class.getClassLoader().getResource("exampleGrammars/Grammar.grammar").toURI());
grammar = Grammar.fromFile(path);
parser = StupsParser.fromGrammar(grammar);
}
private static SyntaxTree getTree(String program) {
try {
final Path path = Paths.get(SyntaxTreeCompacterTest.class.getClassLoader().getResource("examplePrograms/" + program).toURI());
final Path path = Paths.get(ParseTreeCleanerTest.class.getClassLoader().getResource("examplePrograms/" + program).toURI());
final String programCode = Files.readString(path, StandardCharsets.US_ASCII);
final Lexer lex = new StupsLexer(CharStreams.fromString(programCode));
return parser.parse(lex.getAllTokens(), lex.getVocabulary());
@ -47,7 +47,7 @@ class SyntaxTreeCompacterTest {
final SyntaxTree tree = getTree("GeneralOperator.stups");
final long before = tree.size();
ASTCompacter.deleteChildren(tree, grammar);
ParseTreeCleaner.deleteChildren(tree, grammar);
assertThat(before - tree.size()).isEqualTo(3);
}
@ -57,7 +57,7 @@ class SyntaxTreeCompacterTest {
final SyntaxTree tree = getTree("GeneralOperator.stups");
final long before = tree.size();
ASTCompacter.promote(tree, grammar);
ParseTreeCleaner.promote(tree, grammar);
assertThat(before - tree.size()).isEqualTo(14);
}
@ -65,10 +65,10 @@ class SyntaxTreeCompacterTest {
@Test
void testDeleteEmpty() {
final SyntaxTree tree = getTree("GeneralOperator.stups");
ASTCompacter.deleteChildren(tree, grammar);
ParseTreeCleaner.deleteChildren(tree, grammar);
final long before = tree.size();
ASTCompacter.deleteIfEmpty(tree, grammar);
ParseTreeCleaner.deleteIfEmpty(tree, grammar);
assertThat(before - tree.size()).isEqualTo(2);
}
@ -77,7 +77,7 @@ class SyntaxTreeCompacterTest {
void testClean() {
final SyntaxTree tree = getTree("GeneralOperator.stups");
ASTCompacter.clean(tree, grammar);
ParseTreeCleaner.clean(tree, grammar);
assertThat(tree.size()).isEqualTo(28);
}

View File

@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class SyntaxTreeBalancerTest {
class SyntaxTreeRebalancerTest {
//expr
// expr: SUB
@ -84,7 +84,7 @@ class SyntaxTreeBalancerTest {
void testTree1Flip() {
final SyntaxTree tree = tree1();
ASTBalancer.flip(tree);
SyntaxTreeRebalancer.flip(tree);
assertThat(tree.getRoot().getChildren().get(0).getName()).isEqualTo("INTEGER_LIT");
assertThat(tree.getRoot().getChildren().get(1).getName()).isEqualTo("expr");
@ -94,8 +94,8 @@ class SyntaxTreeBalancerTest {
void testTree1Flip2x() {
final SyntaxTree tree = tree1();
ASTBalancer.flip(tree);
ASTBalancer.flip(tree);
SyntaxTreeRebalancer.flip(tree);
SyntaxTreeRebalancer.flip(tree);
assertThat(tree).isEqualTo(tree1());
}
@ -104,7 +104,7 @@ class SyntaxTreeBalancerTest {
void testTree2Flip() {
final SyntaxTree tree = tree2();
ASTBalancer.flip(tree);
SyntaxTreeRebalancer.flip(tree);
assertThat(tree.getRoot().getChildren().get(0).getName()).isEqualTo("INTEGER_LIT");
assertThat(tree.getRoot().getChildren().get(1).getName()).isEqualTo("expr");
@ -116,8 +116,8 @@ class SyntaxTreeBalancerTest {
void testTree2Flip2x() {
final SyntaxTree tree = tree2();
ASTBalancer.flip(tree);
ASTBalancer.flip(tree);
SyntaxTreeRebalancer.flip(tree);
SyntaxTreeRebalancer.flip(tree);
assertThat(tree).isEqualTo(tree2());
}
@ -125,9 +125,9 @@ class SyntaxTreeBalancerTest {
@Test
void testTree1LeftPrecedence() {
final SyntaxTree tree = tree1();
ASTBalancer.flip(tree);
SyntaxTreeRebalancer.flip(tree);
ASTBalancer.leftPrecedence(tree);
SyntaxTreeRebalancer.leftPrecedence(tree);
assertThat(tree.size()).isEqualTo(3);
assertThat(tree.getRoot().getValue()).isEqualTo("SUB");
@ -136,9 +136,9 @@ class SyntaxTreeBalancerTest {
@Test
void testTree2LeftPrecedence() {
final SyntaxTree tree = tree2();
ASTBalancer.flip(tree);
SyntaxTreeRebalancer.flip(tree);
ASTBalancer.leftPrecedence(tree);
SyntaxTreeRebalancer.leftPrecedence(tree);
assertThat(tree.size()).isEqualTo(5);
assertThat(tree.getRoot().getValue()).isEqualTo("SUB");
@ -147,14 +147,14 @@ class SyntaxTreeBalancerTest {
@Test
void testTree2OperatorPrecedence() {
final SyntaxTree tree = tree2();
ASTBalancer.flip(tree);
ASTBalancer.leftPrecedence(tree);
SyntaxTreeRebalancer.flip(tree);
SyntaxTreeRebalancer.leftPrecedence(tree);
final SyntaxTree tree1 = tree2();
ASTBalancer.flip(tree1);
ASTBalancer.leftPrecedence(tree1);
SyntaxTreeRebalancer.flip(tree1);
SyntaxTreeRebalancer.leftPrecedence(tree1);
ASTBalancer.operatorPrecedence(tree);
SyntaxTreeRebalancer.operatorPrecedence(tree);
assertThat(tree).isEqualTo(tree1);
}
@ -162,12 +162,12 @@ class SyntaxTreeBalancerTest {
@Test
void testTree3OperatorPrecedence() {
final SyntaxTree tree = tree3();
ASTBalancer.flip(tree);
ASTBalancer.leftPrecedence(tree);
SyntaxTreeRebalancer.flip(tree);
SyntaxTreeRebalancer.leftPrecedence(tree);
assertThat(tree.getRoot().getValue()).isEqualTo("MUL");
ASTBalancer.operatorPrecedence(tree);
SyntaxTreeRebalancer.operatorPrecedence(tree);
assertThat(tree.size()).isEqualTo(5);
assertThat(tree.getRoot().getValue()).isEqualTo("SUB");