Skip to content

Commit

Permalink
Issue #1688: improvements of textly
Browse files Browse the repository at this point in the history
- typecheck corrections
- rewrite TestTypecheck
- add ev3 common tests (specific tests are missing)
tests
  • Loading branch information
rbudde committed Oct 9, 2024
1 parent 67847a1 commit 17d0eb5
Show file tree
Hide file tree
Showing 22 changed files with 857 additions and 633 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import de.fhg.iais.roberta.blockly.generated.Block;
import de.fhg.iais.roberta.transformer.AnnotationHelper;
import de.fhg.iais.roberta.typecheck.BlocklyType;
import de.fhg.iais.roberta.typecheck.InfoCollector;
import de.fhg.iais.roberta.typecheck.NepoInfo;
import de.fhg.iais.roberta.typecheck.NepoInfos;
import de.fhg.iais.roberta.util.ast.AstFactory;
Expand Down Expand Up @@ -160,20 +159,6 @@ public final void addTextlyError(String error, boolean typecheckError) {
this.infos.addInfo(typecheckError ? NepoInfo.error("PROGRAM_ERROR_EXPRBLOCK_TYPECHECK " + error) : NepoInfo.error("PROGRAM_ERROR_EXPRBLOCK_PARSE " + error));
}

/**
* retrieve typecheck errors from the AST sub-tree of an eval block.
* They must be elevated to this block, because the EvalExpr block is explicitly designed to hide the sub-tree.
*/
public boolean elevateNepoInfosAndCheckForCorrectness() {
boolean errorFree = true;
List<NepoInfo> infos = InfoCollector.collectInfos(this);
for ( NepoInfo info : infos ) {
addNepoInfo(info);
errorFree = false;
}
return errorFree;
}

/**
* add a NepoInfo object (error, warning e.g.) to this phrase
*
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package de.fhg.iais.roberta.typecheck;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import de.fhg.iais.roberta.syntax.Phrase;
import de.fhg.iais.roberta.util.dbc.DbcException;
import de.fhg.iais.roberta.util.visitor.IInfoCollectable;
import de.fhg.iais.roberta.visitor.IVisitor;

/**
* This class is a visitor and responsible for processing the infos generated (and stored in the Phrases) during validation and typechecking for an AST.
* It does this by traversing the AST using <b>reflection</b>. Be careful when changing this visitor and when changing the overall structure of an AST.
*/
public class NepoInfoProcessor {

private final List<NepoInfo> infos = new ArrayList<>();
private boolean deleteOldInfo;

/**
* initialize the info collector visitor.
*/
private NepoInfoProcessor(boolean deleteOldInfo) {
this.deleteOldInfo = deleteOldInfo;
}

/**
* collect the infos (of type info and type error) generated (and stored in the Phrases) during validation and typechecking for an AST
*
* @param phrase whose infos should be collected
* @return list of collected infos
*/
public static List<NepoInfo> collectNepoInfos(Phrase phrase) //
{
NepoInfoProcessor phraseVisitor = new NepoInfoProcessor(false);
try {
phraseVisitor.collect(phrase);
} catch ( IllegalAccessException e ) {
throw new DbcException("Cannot collect nepo infos", e);
}
return phraseVisitor.infos;
}

/**
* collect the infos only of type error generated (and stored in the Phrases) during validation and typechecking for an AST
*
* @param phrase whose errors should be collected
* @return list of collected errors
*/
public static List<NepoInfo> collectNepoErrors(Phrase phrase) //
{
List<NepoInfo> errors = new ArrayList<>();
for (NepoInfo nepoInfo : collectNepoInfos(phrase)) {
if (nepoInfo.getSeverity().equals(NepoInfo.Severity.ERROR)) {
errors.add(nepoInfo);
}
}
return errors;
}

/**
* process the infos generated (and stored in the Phrases) during validation and typechecking for an AST
* - store them in the topmost phrase and<br>
* - delete them in the lower-level phrase
*
* @param phrase whose infos should be processed
* @return list of collected infos
*/
public static List<NepoInfo> elevateNepoInfos(Phrase phrase) //
{
NepoInfoProcessor phraseVisitor = new NepoInfoProcessor(true);
try {
phraseVisitor.collect(phrase);
} catch ( IllegalAccessException e ) {
throw new DbcException("Cannot collect nepo infos", e);
}
for (NepoInfo nepoInfo : phraseVisitor.infos) {
phrase.addNepoInfo(nepoInfo);
}
return phraseVisitor.infos;
}

/**
* collect infos generated by the typechecker by reflection. It traverses the AST by identifying all fields recursively, that are Phrases and extracts the
* infos from them.
*
* @param object a phrase or an arbitrary object. Decide how to handle it.
*/
private void collect(Object object) throws IllegalAccessException {
if ( object != null ) {
Class<?> clazz = object.getClass();
if ( List.class.isAssignableFrom(clazz) ) {
for ( Object item : (List) object ) {
collect(item);
}
} else if ( Phrase.class.isAssignableFrom(clazz) ) {
Phrase phrase = (Phrase) object;
for ( NepoInfo info : phrase.getInfos().getInfos() ) {
infos.add(info);
}
if ( deleteOldInfo) {
phrase.getInfos().clear();
}
for ( Field field : clazz.getFields() ) {
collect(field.get(phrase));
}
} else if ( IInfoCollectable.class.isAssignableFrom(clazz) ) {
IInfoCollectable collectable = (IInfoCollectable) object;
for ( Field field : clazz.getDeclaredFields() ) {
collect(field.get(collectable));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ public void addInfo(NepoInfo info) {
this.infos.add(info);
}
}

public void clear() {
infos.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ private BlocklyType typeCheck(Phrase phraseToCheck, List<BlocklyType> actualPara
if ( this.paramTypes[i].equalAsTypes(BlocklyType.CAPTURED_TYPE) ) {
if ( capturedType == null ) {
capturedType = actualParamType;
returnTypeIfError = capturedType;
if (returnTypeIfError.equalAsTypes(BlocklyType.CAPTURED_TYPE)) {
returnTypeIfError = capturedType;
}
}
if ( returnType == BlocklyType.VOID && !capturedType.isArray() ) {
String message = "a list type was expected, but found was type: " + b2m(actualParamType);
Expand All @@ -97,21 +99,23 @@ private BlocklyType typeCheck(Phrase phraseToCheck, List<BlocklyType> actualPara
} else if ( this.paramTypes[i].equalAsTypes(BlocklyType.CAPTURED_TYPE_ARRAY_ITEM) ) {
if ( capturedType == null ) {
capturedType = actualParamType.getMatchingArrayTypeForElementType();
returnTypeIfError = capturedType;
if (returnTypeIfError.equalAsTypes(BlocklyType.CAPTURED_TYPE_ARRAY_ITEM)) {
returnTypeIfError = capturedType;
}
} else if ( !actualParamType.equalAsTypes(capturedType.getMatchingElementTypeForArrayType()) ) {
String message = "For the " + n2m(phraseToCheck) + " the expected type for one of the parameters is: " + b2m(capturedType.getMatchingElementTypeForArrayType()) + ", but found was type: " + b2m(actualParamType);
String message = "For parameter " + (i+1) + " of " + n2m(phraseToCheck) + " the expected type: " + b2m(capturedType.getMatchingElementTypeForArrayType()) + ", but found was type: " + b2m(actualParamType);
phraseToCheck.addTextlyError(message, true);
return returnTypeIfError;
}
} else {
if ( phraseToCheck instanceof Binary ) {
String[] op = ((Binary) phraseToCheck).op.values;
String leftOrRight = i==0 ? "left" : "right";
String message = "For the binary op \"" + op[0] + "\" the expected type for the " + leftOrRight + " parameter is: " + b2m(this.paramTypes[i]) + ", but found was type: " + b2m(actualParamType);
String message = "For binary op \"" + op[0] + "\" the expected type for the " + leftOrRight + " parameter is: " + b2m(this.paramTypes[i]) + ", but found was type: " + b2m(actualParamType);
phraseToCheck.addTextlyError(message, true);
return returnTypeIfError;
} else {
String message = "For the parameter " + (i+1) + " of " + n2m(phraseToCheck) + " the expected type: " + b2m(this.paramTypes[i]) + ", but found was type: " + b2m(actualParamType);
String message = "For parameter " + (i+1) + " of " + n2m(phraseToCheck) + " the expected type is: " + b2m(this.paramTypes[i]) + ", but found was: " + b2m(actualParamType);
phraseToCheck.addTextlyError(message, true);
return returnTypeIfError;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package de.fhg.iais.roberta.util.visitor;

import de.fhg.iais.roberta.typecheck.NepoInfoProcessor;

/**
* Interface to declare, that components of a class, which implements this interface, contain infos, that the
* class {@link de.fhg.iais.roberta.typecheck.InfoCollector} should collect
* class {@link NepoInfoProcessor} should collect
*/
public interface IInfoCollectable {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package de.fhg.iais.roberta.visitor.validate;

import java.util.HashMap;
import java.util.List;

import com.google.common.collect.ClassToInstanceMap;

Expand Down Expand Up @@ -78,8 +77,7 @@
import de.fhg.iais.roberta.syntax.lang.stmt.WaitStmt;
import de.fhg.iais.roberta.syntax.lang.stmt.WaitTimeStmt;
import de.fhg.iais.roberta.typecheck.BlocklyType;
import de.fhg.iais.roberta.typecheck.InfoCollector;
import de.fhg.iais.roberta.typecheck.NepoInfo;
import de.fhg.iais.roberta.typecheck.NepoInfoProcessor;
import de.fhg.iais.roberta.util.ast.BlocklyProperties;
import de.fhg.iais.roberta.util.syntax.FunctionNames;

Expand All @@ -106,27 +104,14 @@ public Void visitAssertStmt(AssertStmt assertStmt) {
@Override
public Void visitEvalExpr(EvalExpr evalExpr) {
requiredComponentVisited(evalExpr, evalExpr.exprAsBlock);
List<NepoInfo> infosOfSubAst = InfoCollector.collectInfos(evalExpr);
if ( !infosOfSubAst.isEmpty() ) {
for ( NepoInfo info : infosOfSubAst ) {
String message = info.getMessage();
addErrorToPhrase(evalExpr, message);
}
}

NepoInfoProcessor.elevateNepoInfos(evalExpr);
return null;
}

@Override
public Void visitEvalStmts(EvalStmts stmtEvalExpr) {
requiredComponentVisited(stmtEvalExpr, stmtEvalExpr.stmtsAsBlock);
List<NepoInfo> infosOfSubAst = InfoCollector.collectInfos(stmtEvalExpr);
if ( !infosOfSubAst.isEmpty() ) {
for ( NepoInfo info : infosOfSubAst ) {
String message = info.getMessage();
addErrorToPhrase(stmtEvalExpr, message);
}
}
public Void visitEvalStmts(EvalStmts evalStmt) {
requiredComponentVisited(evalStmt, evalStmt.stmtsAsBlock);
NepoInfoProcessor.elevateNepoInfos(evalStmt);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import de.fhg.iais.roberta.syntax.sensor.generic.TimerReset;
import de.fhg.iais.roberta.syntax.sensor.generic.TimerSensor;
import de.fhg.iais.roberta.typecheck.BlocklyType;
import de.fhg.iais.roberta.typecheck.NepoInfoProcessor;
import de.fhg.iais.roberta.typecheck.Sig;
import de.fhg.iais.roberta.util.dbc.Assert;
import de.fhg.iais.roberta.util.syntax.FunctionNames;
Expand All @@ -105,8 +106,6 @@ public abstract class TypecheckCommonLanguageVisitor extends BaseVisitor<Blockly

/**
* initialize the typecheck visitor.
*
* @param phrase to typecheck
*/
public TypecheckCommonLanguageVisitor(UsedHardwareBean usedHardwareBean) {
this.usedHardwareBean = usedHardwareBean;
Expand All @@ -115,14 +114,14 @@ public TypecheckCommonLanguageVisitor(UsedHardwareBean usedHardwareBean) {
public BlocklyType visitEvalExpr(EvalExpr evalExpr) {
BlocklyType expectedType = evalExpr.getBlocklyType();
Sig.of(expectedType, expectedType).typeCheckPhrases(evalExpr, this, evalExpr.exprAsBlock);
evalExpr.elevateNepoInfosAndCheckForCorrectness();
NepoInfoProcessor.elevateNepoInfos(evalExpr);
return expectedType;
}

public BlocklyType visitEvalStmts(EvalStmts evalStmts) {
BlocklyType expectedType = evalStmts.getBlocklyType();
Sig.of(expectedType, expectedType).typeCheckPhrases(evalStmts, this, evalStmts.stmtsAsBlock);
evalStmts.elevateNepoInfosAndCheckForCorrectness();
NepoInfoProcessor.elevateNepoInfos(evalStmts);
return expectedType;
}

Expand Down
Loading

0 comments on commit 17d0eb5

Please sign in to comment.