Skip to content

Commit 85793e0

Browse files
authored
Merge branch 'master' into meilers_fix_seq_matching_loop
2 parents 895279b + 07bb7d3 commit 85793e0

20 files changed

+142
-131
lines changed

src/main/scala/debugger/DebugExp.scala

+9-8
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ package viper.silicon.debugger
88

99
import viper.silicon.common.collections.immutable.InsertionOrderedSet
1010
import viper.silicon.decider.PathConditions
11-
import viper.silicon.state.terms.{And, Exists, Forall, Implies, Quantification, Term, Trigger, True, Var}
11+
import viper.silicon.state.terms.{And, Exists, Forall, Implies, Quantification, Term, Trigger, Var}
1212
import viper.silver.ast
13-
import viper.silver.ast.TrueLit
1413
import viper.silver.ast.utility.Simplifier
1514

1615
import scala.collection.mutable
@@ -139,17 +138,18 @@ class DebugExp(val id: Int,
139138
}
140139
}
141140

142-
def getTopLevelString(currDepth: Int): String = {
143-
val delimiter = if (finalExp.isDefined && description.isDefined) ": " else ""
144-
"\n\t" + ("\t"*currDepth) + "[" + id + "] " + description.getOrElse("") + delimiter + finalExp.getOrElse("")
141+
def getTopLevelString(currDepth: Int, config: DebugExpPrintConfiguration): String = {
142+
val toDisplay = if (config.printInternalTermRepresentation) term else finalExp
143+
val delimiter = if (toDisplay.isDefined && description.isDefined) ": " else ""
144+
"\n\t" + ("\t"*currDepth) + "[" + id + "] " + description.getOrElse("") + delimiter + toDisplay.getOrElse("")
145145
}
146146

147147

148148
def toString(currDepth: Int, maxDepth: Int, config: DebugExpPrintConfiguration): String = {
149149
if (isInternal_ && !config.isPrintInternalEnabled){
150150
return ""
151151
}
152-
getTopLevelString(currDepth) + childrenToString(currDepth, math.max(maxDepth, config.nodeToHierarchyLevelMap.getOrElse(id, 0)), config)
152+
getTopLevelString(currDepth, config) + childrenToString(currDepth, math.max(maxDepth, config.nodeToHierarchyLevelMap.getOrElse(id, 0)), config)
153153
}
154154

155155
def getExpWithId(id: Int, visited: mutable.HashSet[DebugExp]): Option[DebugExp] = {
@@ -198,7 +198,7 @@ class ImplicationDebugExp(id: Int,
198198
}
199199

200200
if (children.nonEmpty) {
201-
getTopLevelString(currDepth) + " ==> " + childrenToString(currDepth, math.max(maxDepth, config.nodeToHierarchyLevelMap.getOrElse(id, 0)), config)
201+
getTopLevelString(currDepth, config) + " ==> " + childrenToString(currDepth, math.max(maxDepth, config.nodeToHierarchyLevelMap.getOrElse(id, 0)), config)
202202
} else {
203203
"true"
204204
}
@@ -231,7 +231,7 @@ class QuantifiedDebugExp(id: Int,
231231
if (qvars.nonEmpty) {
232232
"\n\t" + ("\t"*currDepth) + "[" + id + "] " + (if (quantifier == "QA") "forall" else "exists") + " " + qvars.mkString(", ") + " :: " + childrenToString(currDepth, math.max(maxDepth, config.nodeToHierarchyLevelMap.getOrElse(id, 0)), config)
233233
} else {
234-
getTopLevelString(currDepth)
234+
getTopLevelString(currDepth, config)
235235
}
236236
}
237237
}
@@ -243,6 +243,7 @@ class DebugExpPrintConfiguration {
243243
var printHierarchyLevel: Int = 2
244244
var nodeToHierarchyLevelMap: Map[Int, Int] = Map.empty
245245
var isPrintAxiomsEnabled: Boolean = false
246+
var printInternalTermRepresentation: Boolean = false
246247

247248
def setPrintHierarchyLevel(level: String): Unit ={
248249
printHierarchyLevel = level match {

src/main/scala/debugger/DebugParser.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class DebugParser extends FastParser {
1818
((pp: (Position, Position)) => PVersionedIdnUseExp(name = parts(0), version = parts(1))(pp))
1919
}.pos
2020

21-
def debugOldLabel[$: P]: P[String] = (StringIn("debug") ~~ CharIn("@") ~~ CharIn("0-9", "A-Z", "a-z", "$_.").repX).!.opaque("debugOldLabel")
21+
def debugOldLabel[$: P]: P[String] = (StringIn("debug") ~~ CharIn("@") ~~ CharIn("0-9", "A-Z", "a-z", "#$_.:").repX).!.opaque("debugOldLabel")
2222

2323
def debugOldLabelUse[$: P]: P[PVersionedIdnUseExp] = P(debugOldLabel).map { case id =>
2424
val parts = id.split("@")

src/main/scala/debugger/SiliconDebugger.scala

+31-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import viper.silicon.interfaces.state.Chunk
66
import viper.silicon.interfaces.{Failure, SiliconDebuggingFailureContext, Success, VerificationResult}
77
import viper.silicon.resources.{FieldID, PredicateID}
88
import viper.silicon.rules.evaluator
9-
import viper.silicon.state.terms.Term
9+
import viper.silicon.state.terms.{Term, True}
1010
import viper.silicon.state.{BasicChunk, IdentifierFactory, MagicWandChunk, QuantifiedFieldChunk, QuantifiedMagicWandChunk, QuantifiedPredicateChunk, State}
1111
import viper.silicon.utils.ast.simplifyVariableName
1212
import viper.silicon.verifier.{MainVerifier, Verifier, WorkerVerifier}
@@ -26,7 +26,8 @@ case class ProofObligation(s: State,
2626
v: Verifier,
2727
proverEmits: Seq[String],
2828
preambleAssumptions: Seq[DebugAxiom],
29-
branchConditions: Seq[(ast.Exp, ast.Exp)],
29+
branchConditions: Seq[Term],
30+
branchConditionExps: Seq[(ast.Exp, ast.Exp)],
3031
assumptionsExp: InsertionOrderedSet[DebugExp],
3132
assertion: Term,
3233
eAssertion: DebugExp,
@@ -52,17 +53,31 @@ case class ProofObligation(s: State,
5253
}) +
5354
s"\n\t\t${originalErrorReason.readableMessage}\n\n"
5455

55-
private lazy val stateString: String =
56-
s"Store:\n\t\t${s.g.values.map(v => s"${v._1} -> ${v._2._2.get}").mkString("\n\t\t")}\n\nHeap:\n\t\t${s.h.values.map(chunkString).mkString("\n\t\t")}\n\n"
56+
private lazy val stateString: String = {
57+
if (printConfig.printInternalTermRepresentation)
58+
s"Store:\n\t\t${s.g.values.map(v => s"${v._1} -> ${v._2._1}").mkString("\n\t\t")}\n\nHeap:\n\t\t${s.h.values.map(chunkString).mkString("\n\t\t")}\n\n"
59+
else
60+
s"Store:\n\t\t${s.g.values.map(v => s"${v._1} -> ${v._2._2.get}").mkString("\n\t\t")}\n\nHeap:\n\t\t${s.h.values.map(chunkString).mkString("\n\t\t")}\n\n"
61+
}
5762

58-
private lazy val branchConditionString: String =
59-
s"Branch Conditions:\n\t\t${branchConditions.map(bc => Simplifier.simplify(bc._2, true)).filter(bc => bc != ast.TrueLit()()).mkString("\n\t\t")}\n\n"
63+
private lazy val branchConditionString: String = {
64+
if (printConfig.printInternalTermRepresentation)
65+
s"Branch Conditions:\n\t\t${branchConditions.filter(bc => bc != True).mkString("\n\t\t")}\n\n"
66+
else
67+
s"Branch Conditions:\n\t\t${branchConditionExps.map(bc => Simplifier.simplify(bc._2, true)).filter(bc => bc != ast.TrueLit()()).mkString("\n\t\t")}\n\n"
68+
}
6069

6170
private def chunkString(c: Chunk): String = {
71+
if (printConfig.printInternalTermRepresentation)
72+
return c.toString
6273
val res = c match {
6374
case bc: BasicChunk =>
75+
val snapExpString = bc.snapExp match {
76+
case Some(e) => s" -> ${Simplifier.simplify(e, true)}"
77+
case _ => ""
78+
}
6479
bc.resourceID match {
65-
case FieldID => s"acc(${bc.argsExp.get.head}.${bc.id}, ${Simplifier.simplify(bc.permExp.get, true)})"
80+
case FieldID => s"acc(${bc.argsExp.get.head}.${bc.id}, ${Simplifier.simplify(bc.permExp.get, true)})$snapExpString"
6681
case PredicateID => s"acc(${bc.id}(${bc.argsExp.mkString(", ")}), ${Simplifier.simplify(bc.permExp.get, true)})"
6782
}
6883
case mwc: MagicWandChunk =>
@@ -215,7 +230,7 @@ class SiliconDebugger(verificationResults: List[VerificationResult],
215230
}
216231

217232
val obl = Some(ProofObligation(failureContext.state.get, failureContext.verifier.get, failureContext.proverDecls, failureContext.preambleAssumptions,
218-
failureContext.branchConditions, failureContext.assumptions,
233+
failureContext.branchConditions, failureContext.branchConditionExps, failureContext.assumptions,
219234
failureContext.failedAssertion, failureContext.failedAssertionExp, None,
220235
new DebugExpPrintConfiguration, currResult.message.reason,
221236
new DebugResolver(this.pprogram, this.resolver.names), new DebugTranslator(this.pprogram, translator.getMembers())))
@@ -321,7 +336,7 @@ class SiliconDebugger(verificationResults: List[VerificationResult],
321336
obl.copy(timeout = Some(timeoutInt))
322337
}
323338
} catch {
324-
case e: NumberFormatException =>
339+
case _: NumberFormatException =>
325340
println("Invalid timeout value.")
326341
obl
327342
}
@@ -533,6 +548,13 @@ class SiliconDebugger(verificationResults: List[VerificationResult],
533548
case _ =>
534549
}
535550

551+
println(s"Enter the new value for printInternalTermRepresentation:")
552+
readLine().toLowerCase match {
553+
case "true" | "1" | "t" => obl.printConfig.printInternalTermRepresentation = true
554+
case "false" | "0" | "f" => obl.printConfig.printInternalTermRepresentation = false
555+
case _ =>
556+
}
557+
536558
//println(s"Enter the new value for nodeToHierarchyLevelMap:")
537559
//obl.printConfig.addHierarchyLevelForId(readLine())
538560
}

src/main/scala/interfaces/Verification.scala

+3-53
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ case class SiliconFailureContext(branchConditions: Seq[ast.Exp],
135135
override lazy val toString: String = branchConditionString + counterExampleString + reasonUnknownString
136136
}
137137

138-
case class SiliconDebuggingFailureContext(branchConditions: Seq[(ast.Exp, ast.Exp)],
138+
case class SiliconDebuggingFailureContext(branchConditions: Seq[Term],
139+
branchConditionExps: Seq[(ast.Exp, ast.Exp)],
139140
counterExample: Option[Counterexample],
140141
reasonUnknown: Option[String],
141142
state: Option[State],
@@ -147,59 +148,8 @@ case class SiliconDebuggingFailureContext(branchConditions: Seq[(ast.Exp, ast.Ex
147148
assumptions: InsertionOrderedSet[DebugExp],
148149
failedAssertion: Term,
149150
failedAssertionExp: DebugExp) extends FailureContext {
150-
lazy val branchConditionString: String = {
151-
if (branchConditions.nonEmpty) {
152-
val branchConditionsString =
153-
branchConditions
154-
.map(_._2)
155-
.map(bc => s"$bc [ ${bc.pos} ] ")
156-
.mkString("\t\t", " ~~> ", "")
157-
158-
s"\n\t\tunder branch conditions:\n$branchConditionsString"
159-
} else {
160-
""
161-
}
162-
}
163-
164-
lazy val counterExampleString: String = {
165-
counterExample.fold("")(ce => s"\n\t\tcounterexample:\n$ce")
166-
}
167-
168-
lazy val reasonUnknownString: String = {
169-
if (reasonUnknown.isDefined) {
170-
s"\nPotential prover incompleteness: ${reasonUnknown.get}"
171-
} else {
172-
""
173-
}
174-
}
175-
176-
lazy val stateString: String = {
177-
if (state.isDefined){
178-
s"\n\nStore:\n\t\t${state.get.g.values.mkString("\n\t\t")}\n\nHeap:\n\t\t${state.get.h.values.mkString("\n\t\t")}"
179-
} else {
180-
""
181-
}
182-
}
183-
184-
lazy val allAssumptionsString: String = {
185-
if (assumptions.nonEmpty) {
186-
val config = new DebugExpPrintConfiguration
187-
config.isPrintInternalEnabled = true
188-
s"\n\nassumptions:\n\t${assumptions.tail.foldLeft[String](assumptions.head.toString(config))((s, de) => de.toString(config) + s)}"
189-
} else {
190-
""
191-
}
192-
}
193-
194-
lazy val failedAssertionString: String ={
195-
if (failedAssertionExp.finalExp.isDefined){
196-
s"\n\nFailed Assertion:\n\t\t${failedAssertionExp.finalExp.get.toString}"
197-
} else {
198-
failedAssertionExp.description.get
199-
}
200-
}
201151

202-
override lazy val toString: String = branchConditionString + counterExampleString + reasonUnknownString + stateString + allAssumptionsString + failedAssertionString
152+
override lazy val toString: String = ""
203153
}
204154

205155
trait SiliconCounterexample extends Counterexample {

src/main/scala/interfaces/state/Chunks.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ trait NonQuantifiedChunk extends GeneralChunk {
3333
override def permMinus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk
3434
override def permPlus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk
3535
def withPerm(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk
36-
def withSnap(snap: Term): NonQuantifiedChunk
36+
def withSnap(snap: Term, snapExp: Option[ast.Exp]): NonQuantifiedChunk
3737
}
3838

3939
trait QuantifiedChunk extends GeneralChunk {

src/main/scala/logger/writer/SymbExLogReportWriter.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ object SymbExLogReportWriter {
3131
}
3232

3333
private def heapChunkToJSON(chunk: Chunk) = chunk match {
34-
case BasicChunk(PredicateID, id, args, _, snap, perm, _) =>
34+
case BasicChunk(PredicateID, id, args, _, snap, _, perm, _) =>
3535
JsObject(
3636
"type" -> JsString("basic_predicate_chunk"),
3737
"predicate" -> JsString(id.toString),
@@ -40,7 +40,7 @@ object SymbExLogReportWriter {
4040
"perm" -> TermWriter.toJSON(perm)
4141
)
4242

43-
case BasicChunk(FieldID, id, Seq(receiver), _, snap, perm, _) =>
43+
case BasicChunk(FieldID, id, Seq(receiver), _, snap, _, perm, _) =>
4444
JsObject(
4545
"type" -> JsString("basic_field_chunk"),
4646
"field" -> JsString(id.toString),

src/main/scala/reporting/Converter.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,10 @@ object Converter {
345345
def extractHeap(h: Iterable[Chunk], model: Model): ExtractedHeap = {
346346
var entries: Vector[HeapEntry] = Vector()
347347
h foreach {
348-
case c @ BasicChunk(FieldID, _, _, _, _, _, _) =>
348+
case c @ BasicChunk(FieldID, _, _, _, _, _, _, _) =>
349349
val entry = extractField(c, model)
350350
entries = entries :+ entry
351-
case c @ BasicChunk(PredicateID, _, _, _, _, _, _) =>
351+
case c @ BasicChunk(PredicateID, _, _, _, _, _, _, _) =>
352352
val entry = extractPredicate(c, model)
353353
entries = entries :+ entry
354354
case c: BasicChunk =>

src/main/scala/rules/Brancher.scala

-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
package viper.silicon.rules
88

9-
import viper.silicon.common.collections.immutable.InsertionOrderedSet
10-
119
import java.util.concurrent._
1210
import viper.silicon.common.concurrency._
1311
import viper.silicon.decider.PathConditionStack

src/main/scala/rules/Evaluator.scala

+18-13
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,10 @@ object evaluator extends EvaluationRules {
203203

204204
case fa: ast.FieldAccess if s.qpFields.contains(fa.field) =>
205205
eval(s, fa.rcv, pve, v)((s1, tRcvr, eRcvr, v1) => {
206-
val debugOldLabel = v1.getDebugOldLabel(s1)
206+
val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fa.pos)
207207
val newFa = Option.when(withExp)({
208208
if (s1.isEvalInOld) ast.FieldAccess(eRcvr.get, fa.field)(fa.pos, fa.info, fa.errT)
209-
else ast.DebugLabelledOld(ast.FieldAccess(eRcvr.get, fa.field)(), debugOldLabel)(fa.pos, fa.info, fa.errT)
209+
else ast.DebugLabelledOld(ast.FieldAccess(eRcvr.get, fa.field)(), debugLabel)(fa.pos, fa.info, fa.errT)
210210
})
211211
val (relevantChunks, _) =
212212
quantifiedChunkSupporter.splitHeap[QuantifiedFieldChunk](s1.h, BasicChunkIdentifier(fa.field.name))
@@ -230,7 +230,7 @@ object evaluator extends EvaluationRules {
230230
val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr)
231231
val fr1 = s1.functionRecorder.recordSnapshot(fa, v1.decider.pcs.branchConditions, fvfLookup)
232232
val s2 = s1.copy(functionRecorder = fr1)
233-
val s3 = if (Verifier.config.enableDebugging() && !s2.isEvalInOld) s2.copy(oldHeaps = s2.oldHeaps + (debugOldLabel -> magicWandSupporter.getEvalHeap(s2))) else s2
233+
val s3 = if (Verifier.config.enableDebugging() && !s2.isEvalInOld) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s2))) else s2
234234
Q(s3, fvfLookup, newFa, v1)
235235
} else {
236236
val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr))
@@ -245,7 +245,7 @@ object evaluator extends EvaluationRules {
245245
else
246246
s1.possibleTriggers
247247
val s2 = s1.copy(functionRecorder = fr1, possibleTriggers = possTriggers)
248-
val s3 = if (Verifier.config.enableDebugging() && !s2.isEvalInOld) s2.copy(oldHeaps = s2.oldHeaps + (debugOldLabel -> magicWandSupporter.getEvalHeap(s2))) else s2
248+
val s3 = if (Verifier.config.enableDebugging() && !s2.isEvalInOld) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s2))) else s2
249249
Q(s3, fvfLookup, newFa, v1)}
250250
}
251251
case _ =>
@@ -317,12 +317,12 @@ object evaluator extends EvaluationRules {
317317
chunkSupporter.lookup(s1, s1.h, resource, tArgs, eArgs, ve, v1)((s2, h2, tSnap, v2) => {
318318
val fr = s2.functionRecorder.recordSnapshot(fa, v2.decider.pcs.branchConditions, tSnap)
319319
val s3 = s2.copy(h = h2, functionRecorder = fr)
320-
val debugOldLabel = v2.getDebugOldLabel(s3)
320+
val (debugHeapName, debugLabel) = v2.getDebugOldLabel(s3, fa.pos)
321321
val newFa = Option.when(withExp)({
322322
if (s3.isEvalInOld) ast.FieldAccess(eArgs.get.head, fa.field)(e.pos, e.info, e.errT)
323-
else ast.DebugLabelledOld(ast.FieldAccess(eArgs.get.head, fa.field)(), debugOldLabel)(e.pos, e.info, e.errT)
323+
else ast.DebugLabelledOld(ast.FieldAccess(eArgs.get.head, fa.field)(), debugLabel)(e.pos, e.info, e.errT)
324324
})
325-
val s4 = if (Verifier.config.enableDebugging() && !s3.isEvalInOld) s3.copy(oldHeaps = s3.oldHeaps + (debugOldLabel -> magicWandSupporter.getEvalHeap(s3))) else s3
325+
val s4 = if (Verifier.config.enableDebugging() && !s3.isEvalInOld) s3.copy(oldHeaps = s3.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s3))) else s3
326326
Q(s4, tSnap, newFa, v1)
327327
})
328328
})
@@ -340,11 +340,15 @@ object evaluator extends EvaluationRules {
340340
Q(s1, t0, e0New.map(ast.Old(_)(e.pos, e.info, e.errT)), v1))
341341

342342
case old@ast.DebugLabelledOld(e0, lbl) =>
343-
s.oldHeaps.get(lbl) match {
343+
val heapName = if (lbl.contains("#"))
344+
lbl.substring(0, lbl.indexOf("#"))
345+
else
346+
lbl
347+
s.oldHeaps.get(heapName) match {
344348
case None =>
345-
createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, lbl)(old.pos, old.info, old.errT)), v, s, "labelled state reached")
349+
createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, heapName)(old.pos, old.info, old.errT)), v, s, "labelled state reached")
346350
case _ =>
347-
evalInOldState(s, lbl, e0, pve, v)((s1, t0, _, v1) =>
351+
evalInOldState(s, heapName, e0, pve, v)((s1, t0, _, v1) =>
348352
Q(s1, t0, Some(old), v1))
349353
}
350354

@@ -581,9 +585,11 @@ object evaluator extends EvaluationRules {
581585
}
582586
val currentPermAmount = PermLookup(field.name, pmDef.pm, args.head)
583587
v1.decider.prover.comment(s"perm($resacc) ~~> assume upper permission bound")
584-
val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resacc)(), v1.getDebugOldLabel(s2))(), ast.FullPerm()())())
588+
val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s2, resacc.pos, Some(h))
589+
val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resacc)(), debugLabel)(), ast.FullPerm()())())
585590
v1.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)))
586-
(s2, currentPermAmount)
591+
val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2
592+
(s3, currentPermAmount)
587593

588594
case predicate: ast.Predicate =>
589595
val (relevantChunks, _) =
@@ -1102,7 +1108,6 @@ object evaluator extends EvaluationRules {
11021108
val assertExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT))
11031109
v1.decider.assert(AtLeast(t1, IntLiteral(0))) {
11041110
case true =>
1105-
val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT))
11061111
val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT))
11071112
v1.decider.assert(Less(t1, SeqLength(t0))) {
11081113
case true =>

0 commit comments

Comments
 (0)