replace inferredType.istype() by infix form

This commit is contained in:
Irmen de Jong 2021-10-15 00:28:23 +02:00
parent 15a02d7664
commit 761aac7a23
15 changed files with 50 additions and 47 deletions

View File

@ -341,7 +341,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement val statement = expr.containingStatement
val dt = expr.indexer.indexExpr.inferType(program) val dt = expr.indexer.indexExpr.inferType(program)
val register = if(dt.istype(DataType.UBYTE) || dt.istype(DataType.BYTE)) "r9L" else "r9" val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...) // replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
// assign the indexing expression to the helper variable, but only if that hasn't been done already // assign the indexing expression to the helper variable, but only if that hasn't been done already
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position) val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)

View File

@ -153,11 +153,11 @@ internal class AstChecker(private val program: Program,
val to = range.to as? NumericLiteralValue val to = range.to as? NumericLiteralValue
if(from != null) if(from != null)
checkValueTypeAndRange(loopvar.datatype, from) checkValueTypeAndRange(loopvar.datatype, from)
else if(!range.from.inferType(program).istype(loopvar.datatype)) else if(range.from.inferType(program) isnot loopvar.datatype)
errors.err("range start value is incompatible with loop variable type", range.position) errors.err("range start value is incompatible with loop variable type", range.position)
if(to != null) if(to != null)
checkValueTypeAndRange(loopvar.datatype, to) checkValueTypeAndRange(loopvar.datatype, to)
else if(!range.to.inferType(program).istype(loopvar.datatype)) else if(range.to.inferType(program) isnot loopvar.datatype)
errors.err("range end value is incompatible with loop variable type", range.position) errors.err("range end value is incompatible with loop variable type", range.position)
} }
} }
@ -429,12 +429,12 @@ internal class AstChecker(private val program: Program,
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) { if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
if(targetDt.isIterable) if(targetDt.isIterable)
errors.err("cannot assign value to string or array", assignment.value.position) errors.err("cannot assign value to string or array", assignment.value.position)
else if(!(valueDt.istype(DataType.STR) && targetDt.istype(DataType.UWORD))) else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
errors.err("type of value doesn't match target", assignment.value.position) errors.err("type of value doesn't match target", assignment.value.position)
} }
if(assignment.value is TypecastExpression) { if(assignment.value is TypecastExpression) {
if(assignment.isAugmentable && targetDt.istype(DataType.FLOAT)) if(assignment.isAugmentable && targetDt istype DataType.FLOAT)
errors.err("typecasting a float value in-place makes no sense", assignment.value.position) errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
} }
@ -597,7 +597,7 @@ internal class AstChecker(private val program: Program,
val declValue = decl.value val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR) { if(declValue!=null && decl.type==VarDeclType.VAR) {
if (!declValue.inferType(program).istype(decl.datatype)) { if (declValue.inferType(program) isnot decl.datatype) {
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position) err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
} }
} }
@ -916,7 +916,7 @@ internal class AstChecker(private val program: Program,
// warn about sgn(unsigned) this is likely a mistake // warn about sgn(unsigned) this is likely a mistake
if(functionCall.target.nameInSource.last()=="sgn") { if(functionCall.target.nameInSource.last()=="sgn") {
val sgnArgType = functionCall.args.first().inferType(program) val sgnArgType = functionCall.args.first().inferType(program)
if(sgnArgType.istype(DataType.UBYTE) || sgnArgType.istype(DataType.UWORD)) if(sgnArgType istype DataType.UBYTE || sgnArgType istype DataType.UWORD)
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position) errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
} }
@ -985,7 +985,7 @@ internal class AstChecker(private val program: Program,
if(functionCallStatement.target.nameInSource.last() == "sort") { if(functionCallStatement.target.nameInSource.last() == "sort") {
// sort is not supported on float arrays // sort is not supported on float arrays
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
if(idref!=null && idref.inferType(program).istype(DataType.ARRAY_F)) { if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position) errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
} }
} }
@ -1119,7 +1119,7 @@ internal class AstChecker(private val program: Program,
// check index value 0..255 // check index value 0..255
val dtxNum = arrayIndexedExpression.indexer.indexExpr.inferType(program) val dtxNum = arrayIndexedExpression.indexer.indexExpr.inferType(program)
if(!dtxNum.istype(DataType.UBYTE) && !dtxNum.istype(DataType.BYTE)) if(dtxNum isnot DataType.UBYTE && dtxNum isnot DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position) errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
super.visit(arrayIndexedExpression) super.visit(arrayIndexedExpression)
@ -1157,7 +1157,7 @@ internal class AstChecker(private val program: Program,
when { when {
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position) constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position) constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
!conditionType.istype(constvalue.type) -> errors.err("choice value datatype differs from condition value", whenChoice.position) conditionType isnot constvalue.type -> errors.err("choice value datatype differs from condition value", whenChoice.position)
} }
} }
} else { } else {
@ -1201,7 +1201,7 @@ internal class AstChecker(private val program: Program,
DataType.STR -> return err("string value expected") DataType.STR -> return err("string value expected")
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
// value may be either a single byte, or a byte arraysize (of all constant values), or a range // value may be either a single byte, or a byte arraysize (of all constant values), or a range
if(value.type.istype(targetDt)) { if(value.type istype targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySpecSize = arrayspec.constIndex() val arraySpecSize = arrayspec.constIndex()
@ -1220,7 +1220,7 @@ internal class AstChecker(private val program: Program,
} }
DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UW, DataType.ARRAY_W -> {
// value may be either a single word, or a word arraysize, or a range // value may be either a single word, or a word arraysize, or a range
if(value.type.istype(targetDt)) { if(value.type istype targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySpecSize = arrayspec.constIndex() val arraySpecSize = arrayspec.constIndex()
@ -1239,7 +1239,7 @@ internal class AstChecker(private val program: Program,
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
// value may be either a single float, or a float arraysize // value may be either a single float, or a float arraysize
if(value.type.istype(targetDt)) { if(value.type istype targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySize = value.value.size val arraySize = value.value.size

View File

@ -29,7 +29,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
if(vardecl!=null) { if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess) // adjust the datatype of the array (to an educated guess)
val arrayDt = array.type val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) { if(arrayDt isnot vardecl.datatype) {
val cast = array.cast(vardecl.datatype) val cast = array.cast(vardecl.datatype)
if (cast != null && cast !== array) if (cast != null && cast !== array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))

View File

@ -111,7 +111,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
} }
} }
is VarDecl -> { is VarDecl -> {
if(!leftDt.istype(parent.datatype)) { if(leftDt isnot parent.datatype) {
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position) val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr)) return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
} }

View File

@ -22,7 +22,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val declValue = decl.value val declValue = decl.value
if(decl.type==VarDeclType.VAR && declValue!=null) { if(decl.type==VarDeclType.VAR && declValue!=null) {
val valueDt = declValue.inferType(program) val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) { if(valueDt isnot decl.datatype) {
// don't add a typecast on an array initializer value // don't add a typecast on an array initializer value
if(valueDt.isInteger && decl.datatype in ArrayDatatypes) if(valueDt.isInteger && decl.datatype in ArrayDatatypes)
@ -217,7 +217,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val subroutine = returnStmt.definingSubroutine!! val subroutine = returnStmt.definingSubroutine!!
if(subroutine.returntypes.size==1) { if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first() val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program).istype(subReturnType)) if (returnValue.inferType(program) istype subReturnType)
return noModifications return noModifications
if (returnValue is NumericLiteralValue) { if (returnValue is NumericLiteralValue) {
val cast = returnValue.cast(subroutine.returntypes.single()) val cast = returnValue.cast(subroutine.returntypes.single())

View File

@ -71,7 +71,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
} }
val sourceDt = typecast.expression.inferType(program) val sourceDt = typecast.expression.inferType(program)
if(sourceDt.istype(typecast.type)) if(sourceDt istype typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent)) return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
return noModifications return noModifications

View File

@ -305,7 +305,7 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED)) val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
numericLiteral(memsizer.memorySize(elementDt) * length, position) numericLiteral(memsizer.memorySize(elementDt) * length, position)
} }
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position) dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position) else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
} }
} else { } else {

View File

@ -1464,11 +1464,11 @@ $label nop""")
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") { if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
val leftDt = pointerOffsetExpr.left.inferType(program) val leftDt = pointerOffsetExpr.left.inferType(program)
val rightDt = pointerOffsetExpr.left.inferType(program) val rightDt = pointerOffsetExpr.left.inferType(program)
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UBYTE)) if(leftDt istype DataType.UWORD && rightDt istype DataType.UBYTE)
return Pair(pointerOffsetExpr.left, pointerOffsetExpr.right) return Pair(pointerOffsetExpr.left, pointerOffsetExpr.right)
if(leftDt.istype(DataType.UBYTE) && rightDt.istype(DataType.UWORD)) if(leftDt istype DataType.UBYTE && rightDt istype DataType.UWORD)
return Pair(pointerOffsetExpr.right, pointerOffsetExpr.left) return Pair(pointerOffsetExpr.right, pointerOffsetExpr.left)
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UWORD)) { if(leftDt istype DataType.UWORD && rightDt istype DataType.UWORD) {
// could be that the index was a constant numeric byte but converted to word, check that // could be that the index was a constant numeric byte but converted to word, check that
val constIdx = pointerOffsetExpr.right.constValue(program) val constIdx = pointerOffsetExpr.right.constValue(program)
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) { if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
@ -1476,10 +1476,10 @@ $label nop""")
} }
// could be that the index was typecasted into uword, check that // could be that the index was typecasted into uword, check that
val rightTc = pointerOffsetExpr.right as? TypecastExpression val rightTc = pointerOffsetExpr.right as? TypecastExpression
if(rightTc!=null && rightTc.expression.inferType(program).istype(DataType.UBYTE)) if(rightTc!=null && rightTc.expression.inferType(program) istype DataType.UBYTE)
return Pair(pointerOffsetExpr.left, rightTc.expression) return Pair(pointerOffsetExpr.left, rightTc.expression)
val leftTc = pointerOffsetExpr.left as? TypecastExpression val leftTc = pointerOffsetExpr.left as? TypecastExpression
if(leftTc!=null && leftTc.expression.inferType(program).istype(DataType.UBYTE)) if(leftTc!=null && leftTc.expression.inferType(program) istype DataType.UBYTE)
return Pair(pointerOffsetExpr.right, leftTc.expression) return Pair(pointerOffsetExpr.right, leftTc.expression)
} }
@ -1492,7 +1492,7 @@ $label nop""")
fun evalBytevalueWillClobberA(expr: Expression): Boolean { fun evalBytevalueWillClobberA(expr: Expression): Boolean {
val dt = expr.inferType(program) val dt = expr.inferType(program)
if(!dt.istype(DataType.UBYTE) && !dt.istype(DataType.BYTE)) if(dt isnot DataType.UBYTE && dt isnot DataType.BYTE)
return true return true
return when(expr) { return when(expr) {
is IdentifierReference -> false is IdentifierReference -> false

View File

@ -715,11 +715,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val firstName = asmgen.asmVariableName(first) val firstName = asmgen.asmVariableName(first)
val secondName = asmgen.asmVariableName(second) val secondName = asmgen.asmVariableName(second)
val dt = first.inferType(program) val dt = first.inferType(program)
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) { if(dt istype DataType.BYTE || dt istype DataType.UBYTE) {
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName") asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
return return
} }
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) { if(dt istype DataType.WORD || dt istype DataType.UWORD) {
asmgen.out(""" asmgen.out("""
ldy $firstName ldy $firstName
lda $secondName lda $secondName
@ -732,7 +732,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
""") """)
return return
} }
if(dt.istype(DataType.FLOAT)) { if(dt istype DataType.FLOAT) {
asmgen.out(""" asmgen.out("""
lda #<$firstName lda #<$firstName
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1

View File

@ -137,7 +137,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
} }
} }
if(expr.inferType(program).istype(DataType.FLOAT)) { if(expr.inferType(program) istype DataType.FLOAT) {
val subExpr: BinaryExpression? = when { val subExpr: BinaryExpression? = when {
leftconst != null -> expr.right as? BinaryExpression leftconst != null -> expr.right as? BinaryExpression
rightconst != null -> expr.left as? BinaryExpression rightconst != null -> expr.left as? BinaryExpression
@ -277,7 +277,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
val numval = decl.value as? NumericLiteralValue val numval = decl.value as? NumericLiteralValue
if(decl.type== VarDeclType.CONST && numval!=null) { if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program) val valueDt = numval.inferType(program)
if(!valueDt.istype(decl.datatype)) { if(valueDt isnot decl.datatype) {
val cast = numval.cast(decl.datatype) val cast = numval.cast(decl.datatype)
if(cast.isValid) if(cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl)) return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))

View File

@ -21,7 +21,7 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
try { try {
val declConstValue = decl.value?.constValue(program) val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST) if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& !declConstValue.inferType(program).istype(decl.datatype)) { && declConstValue.inferType(program) isnot decl.datatype) {
// cast the numeric literal to the appropriate datatype of the variable // cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype) val cast = declConstValue.cast(decl.datatype)
if(cast.isValid) if(cast.isValid)

View File

@ -45,7 +45,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast) mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
} }
} else { } else {
if (typecast.expression.inferType(program).istype(typecast.type)) { if (typecast.expression.inferType(program) istype typecast.type) {
// remove duplicate cast // remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent) mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
} }
@ -289,13 +289,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val arg = functionCall.args[0] val arg = functionCall.args[0]
if(arg is TypecastExpression) { if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program) val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) { if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless lsb() of byte value that was typecasted to word // useless lsb() of byte value that was typecasted to word
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent)) return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
} }
} else { } else {
val argDt = arg.inferType(program) val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) { if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
// useless lsb() of byte value // useless lsb() of byte value
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent)) return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
} }
@ -305,7 +305,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val arg = functionCall.args[0] val arg = functionCall.args[0]
if(arg is TypecastExpression) { if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program) val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) { if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless msb() of byte value that was typecasted to word, replace with 0 // useless msb() of byte value that was typecasted to word, replace with 0
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
functionCall, functionCall,
@ -314,7 +314,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
} }
} else { } else {
val argDt = arg.inferType(program) val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) { if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
// useless msb() of byte value, replace with 0 // useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
functionCall, functionCall,

View File

@ -638,7 +638,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
} }
fun cast(targettype: DataType): ArrayLiteralValue? { fun cast(targettype: DataType): ArrayLiteralValue? {
if(type.istype(targettype)) if(type istype targettype)
return this return this
if(targettype in ArrayDatatypes) { if(targettype in ArrayDatatypes) {
val elementType = ArrayToElementTypes.getValue(targettype) val elementType = ArrayToElementTypes.getValue(targettype)

View File

@ -3,7 +3,6 @@ TODO
For next compiler release For next compiler release
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
replace uses of inferredType.istype(..) by infix form
replace checks against multiple types with .oneOf(...) replace checks against multiple types with .oneOf(...)
replace certain uses of inferredType.getOr(UNKNOWN) by i.getOrElse({ errorhandler }) replace certain uses of inferredType.getOr(UNKNOWN) by i.getOrElse({ errorhandler })

View File

@ -1,14 +1,18 @@
%import textio %import textio
main { main {
str myBar = "main.bar" sub start() {
ubyte xx
foo_bar: when xx {
; %asminclude "compiler/test/fixtures/foo_bar.asm22" ; FIXME: should be accessible from inside start() but give assembler error 2 -> {
}
sub start() { 3 -> {
txt.print(myBar) }
txt.print(&foo_bar) 50 -> {
return }
} else -> {
}
}
}
} }