Skip to content
Snippets Groups Projects
Commit 4d2109d3 authored by Johannes Mey's avatar Johannes Mey
Browse files

fix bug with dropping

parent cd73382e
No related branches found
No related tags found
No related merge requests found
Pipeline #13875 passed
......@@ -14,7 +14,8 @@ aspect CleanupAttributes {
}
syn double ASTNode.time(); // TODO can this be avoided?
eq RobotIsNotReadyToPick.time() = 1;
eq RobotIsNotReadyToPick.time() = 2;
eq RobotIsNotReadyToDrop.time() = 2;
eq WaitForEmptyTable.time() = 1;
eq ASTNode.time() {
throw new UnsupportedOperationException("Invalid use of attribute time():double");
......@@ -22,6 +23,7 @@ aspect CleanupAttributes {
inh double Wait.time();
eq RobotIsNotReadyToPick.getWait().time() = time();
eq RobotIsNotReadyToDrop.getWait().time() = time();
eq WaitForEmptyTable.getWait().time() = time();
syn String ASTNode.place(); // TODO can this be avoided?
......
......@@ -2,9 +2,12 @@ Tidy : MotionGrammarElement ::= MoveObjectToCorrectPlace* WaitForEmptyTable* Emp
MoveObjectToCorrectPlace : MotionGrammarElement ::= ObjectAtWrongPlace/*provides object*/ PickUpObject/*uses object*/ DropObjectAtRightPlace/*uses object*/;
PickUpObject/*requires object*/ : MotionGrammarElement ::= RobotIsReadyToPick Pick/*uses object*/;
abstract RobotIsReadyToPick : MotionGrammarElement;
RobotIsReallyReadyToPick : RobotIsReadyToPick ::= RobotIsFree;
RobotIsNotReadyToPick : RobotIsReadyToPick ::= RobotIsBusy Wait/*uses const "1sec"*/ RobotIsReadyToPick;
DropObjectAtRightPlace/*requires object*/ : MotionGrammarElement ::= RightPlace/*uses object, provides place*/ Drop/*uses place*/;
RobotIsReallyReadyToPick : RobotIsReadyToPick ::= RobotIsReadyToPickToken;
RobotIsNotReadyToPick : RobotIsReadyToPick ::= RobotIsNotReadyToPickToken Wait/*uses const "1sec"*/ RobotIsReadyToPick;
abstract RobotIsReadyToDrop : MotionGrammarElement;
RobotIsReallyReadyToDrop : RobotIsReadyToDrop ::= RobotIsReadyToDropToken;
RobotIsNotReadyToDrop : RobotIsReadyToDrop ::= RobotIsNotReadyToDropToken Wait/*uses const "1sec"*/ RobotIsReadyToDrop;
DropObjectAtRightPlace/*requires object*/ : MotionGrammarElement ::= RobotIsReadyToDrop RightPlace/*uses object, provides place*/ Drop/*uses place*/;
WaitForEmptyTable : MotionGrammarElement ::= NotEmptyTable Wait/*uses const "1sec"*/;
// Tokens
......@@ -12,10 +15,14 @@ EmptyTable : Token;
NotEmptyTable : Token; // TODO should be improved to express EmptyTable can not be parsed
ObjectAtWrongPlace : Token ::= <Object:String>; // the Object is a variable of the token
Pick/*requires object*/ : Token;
RobotIsFree : Token ::= RobotIsIdle RobotHasNoItemAttached; // combined token. both individual tokens are parsed in parallel
RobotIsBusy : Token; // TODO should be improved to express RobotIsFree can not be parsed
RobotIsReadyToPickToken : Token ::= RobotIsIdle RobotHasNoItemAttached; // combined token. both individual tokens are parsed in parallel
RobotIsNotReadyToPickToken : Token ::= [RobotIsNotIdle] /*or*/ [RobotHasItemAttached]; // TODO negated RobotIsReadyToPickToken
RobotIsReadyToDropToken : Token ::= RobotIsIdle RobotHasItemAttached; // combined token. both individual tokens are parsed in parallel
RobotIsNotReadyToDropToken : Token ::= [RobotIsNotIdle] /*or*/ [RobotHasNoItemAttached]; // TODO negated RobotIsReadyToDropToken
RobotIsIdle : Token;
RobotHasNoItemAttached : Token;
RobotIsNotIdle : Token; // TODO negated RobotIsIdle
RobotHasItemAttached : Token;
RobotHasNoItemAttached : Token; // TODO negated RobotHasItemAttached
Wait/*requires time*/ : Token; // artificial token, which parsing takes a specified amount of time
RightPlace/*requires object*/ : Token ::= <Place:String>;
Drop/*requires object and place*/ : Token;
......@@ -12,11 +12,16 @@ aspect RobotWorld {
}
public static TokenType EmptyTable.type() { return TokenType.of("EMPTY_TABLE"); }
public static TokenType NotEmptyTable.type() { return TokenType.of("NOT_EMPTY_TABLE"); }
public static TokenType ObjectAtWrongPlace.type() { return TokenType.of("OBJECT_AT_WRONG_PLACE"); }
public static TokenType Pick.type() { return TokenType.of("PICK"); }
public static TokenType RobotIsFree.type() { return TokenType.of("ROBOT_IS_FREE"); }
public static TokenType RobotIsBusy.type() { return TokenType.of("ROBOT_IS_BUSY"); }
public static TokenType RobotIsReadyToPickToken.type() { return TokenType.of("ROBOT_IS_READY_TO_PICK_TOKEN"); }
public static TokenType RobotIsNotReadyToPickToken.type() { return TokenType.of("ROBOT_IS_NOT_READY_TO_PICK_TOKEN"); }
public static TokenType RobotIsReadyToDropToken.type() { return TokenType.of("ROBOT_IS_READY_TO_DROP_TOKEN"); }
public static TokenType RobotIsNotReadyToDropToken.type() { return TokenType.of("ROBOT_IS_NOT_READY_TO_DROP_TOKEN"); }
public static TokenType RobotIsIdle.type() { return TokenType.of("ROBOT_IS_IDLE"); }
public static TokenType RobotIsNotIdle.type() { return TokenType.of("ROBOT_IS_NOT_IDLE"); }
public static TokenType RobotHasItemAttached.type() { return TokenType.of("ROBOT_HAS_ITEM_ATTACHED"); }
public static TokenType RobotHasNoItemAttached.type() { return TokenType.of("ROBOT_HAS_NO_ITEM_ATTACHED"); }
public static TokenType Wait.type() { return TokenType.of("WAIT"); }
public static TokenType RightPlace.type() { return TokenType.of("RIGHT_PLACE"); }
......@@ -95,32 +100,66 @@ aspect RobotWorld {
}
}
public RobotIsFree World.parseRobotIsFree() {
public RobotIsReadyToPickToken World.parseRobotIsReadyToPickToken() {
return null;
}
public RobotIsFree RobotScene.parseRobotIsFree() {
System.out.println("Trying to parse token <RobotIsFree> by parsing sub-tokens...");
public RobotIsReadyToPickToken RobotScene.parseRobotIsReadyToPickToken() {
System.out.println("Trying to parse token <RobotIsReadyToPickToken> by parsing ALL sub-tokens...");
RobotIsIdle idle = parseRobotIsIdle();
RobotHasNoItemAttached noItem = parseRobotHasNoItemAttached();
if (idle != null && noItem != null) {
System.out.println("Trying to parse token <RobotIsFree> by parsing sub-tokens... success");
return new RobotIsFree(idle, noItem);
System.out.println("Trying to parse token <RobotIsReadyToPickToken> by parsing ALL sub-tokens... success");
return new RobotIsReadyToPickToken(idle, noItem);
} else {
System.out.println("Trying to parse token <RobotIsFree> by parsing sub-tokens... failure");
System.out.println("Trying to parse token <RobotIsReadyToPickToken> by parsing ALL sub-tokens... failure");
return null;
}
}
public RobotIsBusy World.parseRobotIsBusy(){
public RobotIsNotReadyToPickToken World.parseRobotIsNotReadyToPickToken() {
return null;
}
public RobotIsBusy RobotScene.parseRobotIsBusy(){
System.out.print("Trying to parse token <RobotIsBusy>... ");
if (!getRobot().getIsIdle()) {
System.out.println("success");
return new RobotIsBusy();
public RobotIsNotReadyToPickToken RobotScene.parseRobotIsNotReadyToPickToken() {
System.out.println("Trying to parse token <RobotIsNotReadyToPickToken> by parsing ANY sub-tokens...");
RobotIsNotIdle notIdle = parseRobotIsNotIdle();
RobotHasItemAttached item = parseRobotHasItemAttached();
if (notIdle != null || item != null) {
System.out.println("Trying to parse token <RobotIsNotReadyToPickToken> by parsing ANY sub-tokens... success");
return new RobotIsNotReadyToPickToken(new Opt<>(notIdle), new Opt<>(item));
} else {
System.out.println("failure");
System.out.println("Trying to parse token <RobotIsNotReadyToPickToken> by parsing ANY sub-tokens... failure");
return null;
}
}
public RobotIsReadyToDropToken World.parseRobotIsReadyToDropToken() {
return null;
}
public RobotIsReadyToDropToken RobotScene.parseRobotIsReadyToDropToken() {
System.out.println("Trying to parse token <RobotIsReadyToDropToken> by parsing ALL sub-tokens...");
RobotIsIdle idle = parseRobotIsIdle();
RobotHasItemAttached item = parseRobotHasItemAttached();
if (idle != null && item != null) {
System.out.println("Trying to parse token <RobotIsReadyToDropToken> by parsing ALL sub-tokens... success");
return new RobotIsReadyToDropToken(idle, item);
} else {
System.out.println("Trying to parse token <RobotIsReadyToDropToken> by parsing ALL sub-tokens... failure");
return null;
}
}
public RobotIsNotReadyToDropToken World.parseRobotIsNotReadyToDropToken() {
return null;
}
public RobotIsNotReadyToDropToken RobotScene.parseRobotIsNotReadyToDropToken() {
System.out.println("Trying to parse token <RobotIsNotReadyToDropToken> by parsing ANY sub-tokens...");
RobotIsNotIdle notIdle = parseRobotIsNotIdle();
RobotHasNoItemAttached noItem = parseRobotHasNoItemAttached();
if (notIdle != null || noItem != null) {
System.out.println("Trying to parse token <RobotIsNotReadyToDropToken> by parsing ANY sub-tokens... success");
return new RobotIsNotReadyToDropToken(new Opt<>(notIdle), new Opt<>(noItem));
} else {
System.out.println("Trying to parse token <RobotIsNotReadyToDropToken> by parsing ANY sub-tokens... failure");
return null;
}
}
......@@ -139,6 +178,34 @@ aspect RobotWorld {
}
}
public RobotIsNotIdle World.parseRobotIsNotIdle() {
return null;
}
public RobotIsNotIdle RobotScene.parseRobotIsNotIdle() {
System.out.print("Trying to parse token <RobotIsNotIdle>... ");
if (!getRobot().getIsIdle()) {
System.out.println("success");
return new RobotIsNotIdle();
} else {
System.out.println("failure");
return null;
}
}
public RobotHasItemAttached World.parseRobotHasItemAttached() {
return null;
}
public RobotHasItemAttached RobotScene.parseRobotHasItemAttached() {
System.out.print("Trying to parse token <RobotHasItemAttached>... ");
if (getRobot().hasAttachedItem()) {
System.out.println("success");
return new RobotHasItemAttached();
} else {
System.out.println("failure");
return null;
}
}
public RobotHasNoItemAttached World.parseRobotHasNoItemAttached() {
return null;
}
......
......@@ -20,9 +20,13 @@ public final class RobotParser extends MotionGrammarParser<Tidy> {
private NotEmptyTable peekedNotEmptyTable_ = null;
private ObjectAtWrongPlace peekedObjectAtWrongPlace_ = null;
private Pick peekedPick_ = null;
private RobotIsFree peekedRobotIsFree_ = null;
private RobotIsBusy peekedRobotIsBusy_ = null;
private RobotIsReadyToPickToken peekedRobotIsReadyToPickToken_ = null;
private RobotIsNotReadyToPickToken peekedRobotIsNotReadyToPickToken_ = null;
private RobotIsReadyToDropToken peekedRobotIsReadyToDropToken_ = null;
private RobotIsNotReadyToDropToken peekedRobotIsNotReadyToDropToken_ = null;
private RobotIsIdle peekedRobotIsIdle_ = null;
private RobotIsNotIdle peekedRobotIsNotIdle_ = null;
private RobotHasItemAttached peekedRobotHasItemAttached_ = null;
private RobotHasNoItemAttached peekedRobotHasNoItemAttached_ = null;
private Wait peekedWait_ = null;
private RightPlace peekedRightPlace_ = null;
......@@ -91,8 +95,9 @@ public final class RobotParser extends MotionGrammarParser<Tidy> {
DropObjectAtRightPlace result = new DropObjectAtRightPlace();
parent.setChild(result, index);
parseRightPlace(result, 0);
parseDrop(result, 1);
parseRobotIsReadyToDrop(result, 0);
parseRightPlace(result, 1);
parseDrop(result, 2);
// semantic action for DropObjectAtRightPlace
result.action(getWorld());
......@@ -174,17 +179,17 @@ public final class RobotParser extends MotionGrammarParser<Tidy> {
private void parseRobotIsReadyToPick(ASTNode<?> parent, int index) throws ParseException {
RobotIsReadyToPick result;
// try to parse a T
if (peekRobotIsFree()) {
// try to parse a RobotIsReadyToPickToken
if (peekRobotIsReadyToPickToken()) {
result = new RobotIsReallyReadyToPick();
parent.setChild(result, index);
parseRobotIsReallyReadyToPick(parent, index);
} else if (peekRobotIsBusy()) {
} else if (peekRobotIsNotReadyToPickToken()) {
result = new RobotIsNotReadyToPick();
parent.setChild(result, index);
parseRobotIsNotReadyToPick(parent, index);
} else {
throw new ParseException("RobotIsReadyToPick", RobotIsFree.type(), RobotIsBusy.type());
throw new ParseException("RobotIsReadyToPick", RobotIsReadyToPickToken.type(), RobotIsNotIdle.type());
}
// semantic action for T1
......@@ -195,73 +200,195 @@ public final class RobotParser extends MotionGrammarParser<Tidy> {
private void parseRobotIsReallyReadyToPick(ASTNode<?> parent, int index) throws ParseException {
RobotIsReallyReadyToPick result = (RobotIsReallyReadyToPick) parent.getChild(index);
parseRobotIsFree(result, 0);
parseRobotIsReadyToPickToken(result, 0);
// semantic action for T1
result.action(getWorld());
printAST("parseRobotIsReallyReadyToPick", result);
}
private boolean peekRobotIsFree() {
peekedRobotIsFree_ = getWorld().parseRobotIsFree();
return peekedRobotIsFree_ != null;
private boolean peekRobotIsReadyToPickToken() {
peekedRobotIsReadyToPickToken_ = getWorld().parseRobotIsReadyToPickToken();
return peekedRobotIsReadyToPickToken_ != null;
}
private void parseRobotIsFree(ASTNode<?> parent, int index) throws ParseException {
RobotIsFree result;
private void parseRobotIsReadyToPickToken(ASTNode<?> parent, int index) throws ParseException {
RobotIsReadyToPickToken result;
if (peekedRobotIsFree_ != null) {
result = peekedRobotIsFree_;
peekedRobotIsFree_ = null; // TODO check if all peeked values are actually parsed afterwards
if (peekedRobotIsReadyToPickToken_ != null) {
result = peekedRobotIsReadyToPickToken_;
peekedRobotIsReadyToPickToken_ = null; // TODO check if all peeked values are actually parsed afterwards
} else {
result = getWorld().parseRobotIsFree();
result = getWorld().parseRobotIsReadyToPickToken();
if (result == null) {
throw new ParseException(RobotIsFree.type());
throw new ParseException(RobotIsReadyToPickToken.type());
}
}
parent.setChild(result, index);
// semantic action for RobotIsFree
// semantic action for RobotIsReadyToPickToken
result.action(getWorld());
printAST("parseRobotIsFree", result);
printAST("parseRobotIsReadyToPickToken", result);
}
private boolean peekRobotIsNotReadyToPickToken() {
peekedRobotIsNotReadyToPickToken_ = getWorld().parseRobotIsNotReadyToPickToken();
return peekedRobotIsNotReadyToPickToken_ != null;
}
private void parseRobotIsNotReadyToPickToken(ASTNode<?> parent, int index) throws ParseException {
RobotIsNotReadyToPickToken result;
if (peekedRobotIsNotReadyToPickToken_ != null) {
result = peekedRobotIsNotReadyToPickToken_;
peekedRobotIsNotReadyToPickToken_ = null; // TODO check if all peeked values are actually parsed afterwards
} else {
result = getWorld().parseRobotIsNotReadyToPickToken();
if (result == null) {
throw new ParseException(RobotIsNotReadyToPickToken.type());
}
}
parent.setChild(result, index);
// semantic action for RobotIsNotReadyToPickToken
result.action(getWorld());
printAST("parseRobotIsNotReadyToPickToken", result);
}
private void parseRobotIsNotReadyToPick(ASTNode<?> parent, int index) throws ParseException {
RobotIsNotReadyToPick result = (RobotIsNotReadyToPick) parent.getChild(index);
parseRobotIsBusy(result, 0);
parseRobotIsNotReadyToPickToken(result, 0);
parseWait(result, 1);
parseRobotIsReadyToPick(result, 2);
// semantic action for T1
// semantic action for RobotIsNotReadyToPick
result.action(getWorld());
printAST("parseRobotIsNotReadyToPick", result);
}
private boolean peekRobotIsBusy() {
peekedRobotIsBusy_ = getWorld().parseRobotIsBusy();
return peekedRobotIsBusy_ != null;
// ----
private void parseRobotIsReadyToDrop(ASTNode<?> parent, int index) throws ParseException {
RobotIsReadyToDrop result;
// try to parse a RobotIsReadyToDropToken
if (peekRobotIsReadyToDropToken()) {
result = new RobotIsReallyReadyToDrop();
parent.setChild(result, index);
parseRobotIsReallyReadyToDrop(parent, index);
} else if (peekRobotIsNotReadyToDropToken()) {
result = new RobotIsNotReadyToDrop();
parent.setChild(result, index);
parseRobotIsNotReadyToDrop(parent, index);
} else {
throw new ParseException("RobotIsReadyToDrop", RobotIsReadyToDropToken.type(), RobotIsNotIdle.type());
}
// semantic action for T1
result.action(getWorld());
printAST("parseRobotIsReadyToDrop", result);
}
private void parseRobotIsReallyReadyToDrop(ASTNode<?> parent, int index) throws ParseException {
RobotIsReallyReadyToDrop result = (RobotIsReallyReadyToDrop) parent.getChild(index);
parseRobotIsReadyToDropToken(result, 0);
// semantic action for RobotIsReallyReadyToDrop
result.action(getWorld());
printAST("parseRobotIsReallyReadyToDrop", result);
}
private boolean peekRobotIsReadyToDropToken() {
peekedRobotIsReadyToDropToken_ = getWorld().parseRobotIsReadyToDropToken();
return peekedRobotIsReadyToDropToken_ != null;
}
private void parseRobotIsReadyToDropToken(ASTNode<?> parent, int index) throws ParseException {
RobotIsReadyToDropToken result;
if (peekedRobotIsReadyToDropToken_ != null) {
result = peekedRobotIsReadyToDropToken_;
peekedRobotIsReadyToDropToken_ = null; // TODO check if all peeked values are actually parsed afterwards
} else {
result = getWorld().parseRobotIsReadyToDropToken();
if (result == null) {
throw new ParseException(RobotIsReadyToDropToken.type());
}
}
parent.setChild(result, index);
// semantic action for RobotIsReadyToDropToken
result.action(getWorld());
printAST("parseRobotIsReadyToDropToken", result);
}
private boolean peekRobotIsNotReadyToDropToken() {
peekedRobotIsNotReadyToDropToken_ = getWorld().parseRobotIsNotReadyToDropToken();
return peekedRobotIsNotReadyToDropToken_ != null;
}
private void parseRobotIsNotReadyToDropToken(ASTNode<?> parent, int index) throws ParseException {
RobotIsNotReadyToDropToken result;
if (peekedRobotIsNotReadyToDropToken_ != null) {
result = peekedRobotIsNotReadyToDropToken_;
peekedRobotIsNotReadyToDropToken_ = null; // TODO check if all peeked values are actually parsed afterwards
} else {
result = getWorld().parseRobotIsNotReadyToDropToken();
if (result == null) {
throw new ParseException(RobotIsNotReadyToDropToken.type());
}
}
parent.setChild(result, index);
// semantic action for RobotIsNotReadyToDropToken
result.action(getWorld());
printAST("parseRobotIsNotReadyToDropToken", result);
}
private void parseRobotIsNotReadyToDrop(ASTNode<?> parent, int index) throws ParseException {
RobotIsNotReadyToDrop result = (RobotIsNotReadyToDrop) parent.getChild(index);
parseRobotIsNotReadyToDropToken(result, 0);
parseWait(result, 1);
parseRobotIsReadyToDrop(result, 2);
// semantic action for RobotIsNotReadyToDrop
result.action(getWorld());
printAST("parseRobotIsNotReadyToDrop", result);
}
// ----
private boolean peekRobotIsNotIdle() {
peekedRobotIsNotIdle_ = getWorld().parseRobotIsNotIdle();
return peekedRobotIsNotIdle_ != null;
}
private void parseRobotIsBusy(ASTNode<?> parent, int index) throws ParseException {
RobotIsBusy result;
private void parseRobotIsNotIdle(ASTNode<?> parent, int index) throws ParseException {
RobotIsNotIdle result;
if (peekedRobotIsBusy_ != null) {
result = peekedRobotIsBusy_;
peekedRobotIsBusy_ = null; // TODO check if all peeked values are actually parsed afterwards
if (peekedRobotIsNotIdle_ != null) {
result = peekedRobotIsNotIdle_;
peekedRobotIsNotIdle_ = null; // TODO check if all peeked values are actually parsed afterwards
} else {
result = getWorld().parseRobotIsBusy();
result = getWorld().parseRobotIsNotIdle();
if (result == null) {
throw new ParseException(RobotIsBusy.type());
throw new ParseException(RobotIsNotIdle.type());
}
}
parent.setChild(result, index);
// semantic action for RobotIsBusy
// semantic action for RobotIsNotIdle
result.action(getWorld());
printAST("parseRobotIsBusy", result);
printAST("parseRobotIsNotIdle", result);
}
private void parseWait(ASTNode<?> parent, int index) throws ParseException {
......
......@@ -2,6 +2,7 @@ package de.tudresden.inf.st.mg.common;
import de.tudresden.inf.st.jastadd.dumpAst.ast.Dumper;
import de.tudresden.inf.st.jastadd.dumpAst.ast.SkinParamBooleanSetting;
import de.tudresden.inf.st.jastadd.dumpAst.ast.SkinParamStringSetting;
import de.tudresden.inf.st.mg.jastadd.model.*;
import java.io.IOException;
......@@ -42,8 +43,10 @@ public abstract class MotionGrammarParser<T extends MotionGrammarElement> {
try {
for (var contextEntry : contexts_.entrySet()) {
// System.out.println("REL: " + ((RobotScene) (contextEntry.getValue())).getRobot().getAttachedItem());
Dumper.read(contextEntry.getValue())
.setNameMethod(o -> o == null ? "null" : o.getClass().getSimpleName())
.includeRelationsFor(".*", ".*")
.dumpAsPNG(astDiagramDir_.resolve("Context-" + contextEntry.getKey() + "-" + String.format("%03d", timeStep_) + "-" + step + ".png"));
}
// TODO remove this once the issue in relast2uml has been resolved
......@@ -60,7 +63,9 @@ public abstract class MotionGrammarParser<T extends MotionGrammarElement> {
return result + o.getClass().getSimpleName();
})
.skinParam(SkinParamBooleanSetting.Shadowing, false)
.skinParam(SkinParamStringSetting.backgroundColor, "white")
// .dumpAsSource(astDiagramDir_.resolve("AST-" + String.format("%03d", timeStep_) + "-" + step + ".puml"))
// .dumpAsSVG(astDiagramDir_.resolve("AST-" + String.format("%03d", timeStep_) + "-" + step + ".svg"));
.dumpAsPNG(astDiagramDir_.resolve("AST-" + String.format("%03d", timeStep_) + "-" + step + ".png"));
}
timeStep_++;
......
......@@ -55,9 +55,11 @@ public class ParserTest {
// for some reason, the best random seed value here is 1 and not 0???
World world = RobotScene.initialWorld(new Random(1));
// create a parser using the world
RobotParser parser = new RobotParser(world);
parser.setDebugDiagramDir(TIDY_AST_DIAGRAM_DIR);
// parse (synchonously, long-running)
var result = parser.parse();
assertThat(result).isNotNull().isInstanceOf(Tidy.class);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment