diff --git a/src/main/scala/esmeta/analyzer/Analyzer.scala b/src/main/scala/esmeta/analyzer/Analyzer.scala index 4dfb09ff5e..b8f443aaa1 100644 --- a/src/main/scala/esmeta/analyzer/Analyzer.scala +++ b/src/main/scala/esmeta/analyzer/Analyzer.scala @@ -39,15 +39,6 @@ abstract class Analyzer /** check reachability of return points */ def reachable(rp: ReturnPoint): Boolean - /** abstract states */ - type AbsState <: AbsStateLike - - /** abstract return values */ - type AbsRet <: AbsRetLike - - /** abstract values */ - type AbsValue <: AbsValueLike - /** lookup for node points */ def getResult(np: NodePoint[Node]): AbsState = npMap.getOrElse(np, AbsState.Bot) @@ -63,6 +54,7 @@ abstract class Analyzer detail: Boolean = false, ): String + /** logging mode */ val log: Boolean /** logging the current analysis result */ diff --git a/src/main/scala/esmeta/analyzer/Domain.scala b/src/main/scala/esmeta/analyzer/Domain.scala index 3577bedbd2..8adc0a5a6f 100644 --- a/src/main/scala/esmeta/analyzer/Domain.scala +++ b/src/main/scala/esmeta/analyzer/Domain.scala @@ -5,11 +5,8 @@ import esmeta.util.BaseUtils.* trait DomainDecl { self: Analyzer => - /** abstract domain */ - trait DomainLike[Elem] { - - /** top element */ - def Top: Elem + /** analysis domains */ + trait AnalysisDomain[Elem <: AnalysisElem[Elem]] { /** bottom element */ def Bot: Elem @@ -18,17 +15,22 @@ trait DomainDecl { self: Analyzer => given rule: Rule[Elem] } - trait DomainElemLike[Elem] { self: Elem => + /** analysis elements */ + trait AnalysisElem[Elem <: AnalysisElem[Elem]] { self: Elem => /** abstract domain */ - def domain: DomainLike[Elem] + def domain: AnalysisDomain[Elem] /** conversion to string */ override def toString: String = stringify(this)(using domain.rule) } + /** value domains */ + trait ValueDomain extends AnalysisDomain[AbsValue] + val AbsValue: ValueDomain + /** abstract values */ - trait AbsValueLike extends DomainElemLike[AbsValue] { self: AbsValue => + trait AbsValueElem extends AnalysisElem[AbsValue] { self: AbsValue => /** abstract domain */ def domain = AbsValue @@ -36,10 +38,14 @@ trait DomainDecl { self: Analyzer => /** get string of abstract value with an abstract state */ def getString(state: AbsState): String } - val AbsValue: DomainLike[AbsValue] + type AbsValue <: AbsValueElem + + /** state domains */ + trait StateDomain extends AnalysisDomain[AbsState] + val AbsState: StateDomain /** abstract states */ - trait AbsStateLike extends DomainElemLike[AbsState] { self: AbsState => + trait AbsStateElem extends AnalysisElem[AbsState] { self: AbsState => /** abstract domain */ def domain = AbsState @@ -47,10 +53,14 @@ trait DomainDecl { self: Analyzer => /** has imprecise elements */ def hasImprec: Boolean } - val AbsState: DomainLike[AbsState] + type AbsState <: AbsStateElem + + /** return value domains */ + trait RetDomain extends AnalysisDomain[AbsRet] + val AbsRet: RetDomain /** abstract return values */ - trait AbsRetLike extends DomainElemLike[AbsRet] { self: AbsRet => + trait AbsRetElem extends AnalysisElem[AbsRet] { self: AbsRet => /** abstract domain */ def domain = AbsRet @@ -58,5 +68,5 @@ trait DomainDecl { self: Analyzer => /** return value */ def value: AbsValue } - val AbsRet: DomainLike[AbsRet] + type AbsRet <: AbsRetElem } diff --git a/src/main/scala/esmeta/analyzer/es/AbsAddr.scala b/src/main/scala/esmeta/analyzer/es/AbsAddr.scala index 5464eb9049..8bbbac6490 100644 --- a/src/main/scala/esmeta/analyzer/es/AbsAddr.scala +++ b/src/main/scala/esmeta/analyzer/es/AbsAddr.scala @@ -9,7 +9,7 @@ import esmeta.util.domain.{*, given}, BSet.*, Flat.* /** abstract primitive values */ trait AbsAddrDecl { self: ESAnalyzer => - case class AbsAddr(set: BSet[Addr]) extends DomainElemLike[AbsAddr] { + case class AbsAddr(set: BSet[Addr]) extends AnalysisElem[AbsAddr] { /** abstract domain */ def domain = AbsAddr @@ -29,7 +29,7 @@ trait AbsAddrDecl { self: ESAnalyzer => /** meet operator */ def ⊓(that: AbsAddr): AbsAddr = ??? } - object AbsAddr extends DomainLike[AbsAddr] { + object AbsAddr extends AnalysisDomain[AbsAddr] { /** top element */ lazy val Top: AbsAddr = ??? diff --git a/src/main/scala/esmeta/analyzer/es/AbsClo.scala b/src/main/scala/esmeta/analyzer/es/AbsClo.scala index 385d4318e0..7d9af4197d 100644 --- a/src/main/scala/esmeta/analyzer/es/AbsClo.scala +++ b/src/main/scala/esmeta/analyzer/es/AbsClo.scala @@ -9,7 +9,7 @@ import esmeta.util.Appender.* trait AbsCloDecl { self: ESAnalyzer => // TODO more precise abstraction - case class AbsClo(exist: Boolean) extends DomainElemLike[AbsClo] { + case class AbsClo(exist: Boolean) extends AnalysisElem[AbsClo] { /** abstract domain */ def domain = AbsClo @@ -32,7 +32,7 @@ trait AbsCloDecl { self: ESAnalyzer => /** meet operator */ def ⊓(that: AbsClo): AbsClo = AbsClo(exist && that.exist) } - object AbsClo extends DomainLike[AbsClo] { + object AbsClo extends AnalysisDomain[AbsClo] { /** top element */ lazy val Top: AbsClo = AbsClo(true) diff --git a/src/main/scala/esmeta/analyzer/es/AbsCont.scala b/src/main/scala/esmeta/analyzer/es/AbsCont.scala index 7a7990c3a0..7262a7a9e1 100644 --- a/src/main/scala/esmeta/analyzer/es/AbsCont.scala +++ b/src/main/scala/esmeta/analyzer/es/AbsCont.scala @@ -9,7 +9,7 @@ import esmeta.util.Appender.* trait AbsContDecl { self: ESAnalyzer => // TODO more precise abstraction - case class AbsCont(exist: Boolean) extends DomainElemLike[AbsCont] { + case class AbsCont(exist: Boolean) extends AnalysisElem[AbsCont] { /** abstract domain */ def domain = AbsCont @@ -32,7 +32,7 @@ trait AbsContDecl { self: ESAnalyzer => /** meet operator */ def ⊓(that: AbsCont): AbsCont = AbsCont(exist && that.exist) } - object AbsCont extends DomainLike[AbsCont] { + object AbsCont extends AnalysisDomain[AbsCont] { /** top element */ lazy val Top: AbsCont = AbsCont(true) diff --git a/src/main/scala/esmeta/analyzer/es/AbsPrimValue.scala b/src/main/scala/esmeta/analyzer/es/AbsPrimValue.scala index 0244128702..ea1ee984e0 100644 --- a/src/main/scala/esmeta/analyzer/es/AbsPrimValue.scala +++ b/src/main/scala/esmeta/analyzer/es/AbsPrimValue.scala @@ -12,7 +12,7 @@ trait AbsPrimValueDecl { self: ESAnalyzer => // TODO more precise abstraction case class AbsPrimValue( set: BSet[PrimValue], - ) extends DomainElemLike[AbsPrimValue] { + ) extends AnalysisElem[AbsPrimValue] { /** abstract domain */ def domain = AbsPrimValue @@ -32,7 +32,7 @@ trait AbsPrimValueDecl { self: ESAnalyzer => /** meet operator */ def ⊓(that: AbsPrimValue): AbsPrimValue = ??? } - object AbsPrimValue extends DomainLike[AbsPrimValue] { + object AbsPrimValue extends AnalysisDomain[AbsPrimValue] { /** top element */ lazy val Top: AbsPrimValue = ??? diff --git a/src/main/scala/esmeta/analyzer/es/AbsRet.scala b/src/main/scala/esmeta/analyzer/es/AbsRet.scala index 653eb3b3f6..ea9d74da7c 100644 --- a/src/main/scala/esmeta/analyzer/es/AbsRet.scala +++ b/src/main/scala/esmeta/analyzer/es/AbsRet.scala @@ -5,7 +5,7 @@ import esmeta.util.Appender.* /** abstract return values */ trait AbsRetDecl { self: ESAnalyzer => - case class AbsRet() extends AbsRetLike { + case class AbsRet() extends AbsRetElem { import AbsRet.* /** return value */ @@ -26,7 +26,7 @@ trait AbsRetDecl { self: ESAnalyzer => /** meet operator */ def ⊓(that: AbsRet): AbsRet = ??? } - object AbsRet extends DomainLike[AbsRet] { + object AbsRet extends RetDomain { /** top element */ lazy val Top: AbsRet = ??? diff --git a/src/main/scala/esmeta/analyzer/es/AbsState.scala b/src/main/scala/esmeta/analyzer/es/AbsState.scala index 5cb5647613..9a5334025c 100644 --- a/src/main/scala/esmeta/analyzer/es/AbsState.scala +++ b/src/main/scala/esmeta/analyzer/es/AbsState.scala @@ -10,7 +10,7 @@ import esmeta.util.BaseUtils.* /** abstract states */ trait AbsStateDecl { self: ESAnalyzer => - case class AbsState() extends AbsStateLike { + case class AbsState() extends AbsStateElem { import AbsState.* given AbsState = this @@ -82,7 +82,7 @@ trait AbsStateDecl { self: ESAnalyzer => /** allocate a list object */ def allocList(vs: Iterable[AbsValue]): (AbsValue, AbsState) = ??? } - object AbsState extends DomainLike[AbsState] { + object AbsState extends StateDomain { /** bases */ private val globals: Map[Global, AbsValue] = diff --git a/src/main/scala/esmeta/analyzer/es/AbsValue.scala b/src/main/scala/esmeta/analyzer/es/AbsValue.scala index 6ab38c4352..471c4e3a7e 100644 --- a/src/main/scala/esmeta/analyzer/es/AbsValue.scala +++ b/src/main/scala/esmeta/analyzer/es/AbsValue.scala @@ -19,7 +19,7 @@ trait AbsValueDecl { self: ESAnalyzer => clo: AbsClo = AbsClo.Bot, cont: AbsCont = AbsCont.Bot, prim: AbsPrimValue = AbsPrimValue.Bot, - ) extends AbsValueLike { + ) extends AbsValueElem { import AbsValue.* /** bottom check */ @@ -113,7 +113,7 @@ trait AbsValueDecl { self: ESAnalyzer => def getString(state: AbsState): String = ??? } - object AbsValue extends DomainLike[AbsValue] { + object AbsValue extends ValueDomain { /** top element */ lazy val Top: AbsValue = ??? diff --git a/src/main/scala/esmeta/analyzer/tychecker/AbsRet.scala b/src/main/scala/esmeta/analyzer/tychecker/AbsRet.scala index 1b1a7fdfd1..c9f16d94c6 100644 --- a/src/main/scala/esmeta/analyzer/tychecker/AbsRet.scala +++ b/src/main/scala/esmeta/analyzer/tychecker/AbsRet.scala @@ -5,7 +5,7 @@ import esmeta.util.Appender.* /** abstract return values */ trait AbsRetDecl { self: TyChecker => - case class AbsRet(value: AbsValue) extends AbsRetLike { + case class AbsRet(value: AbsValue) extends AbsRetElem { import AbsRet.* /** bottom check */ @@ -25,7 +25,7 @@ trait AbsRetDecl { self: TyChecker => def ⊓(that: AbsRet)(using AbsState): AbsRet = AbsRet(this.value ⊓ that.value) } - object AbsRet extends DomainLike[AbsRet] { + object AbsRet extends RetDomain { /** top element */ lazy val Top: AbsRet = AbsRet(AbsValue.Top) diff --git a/src/main/scala/esmeta/analyzer/tychecker/AbsState.scala b/src/main/scala/esmeta/analyzer/tychecker/AbsState.scala index cb2f929a7e..2afb6069b0 100644 --- a/src/main/scala/esmeta/analyzer/tychecker/AbsState.scala +++ b/src/main/scala/esmeta/analyzer/tychecker/AbsState.scala @@ -18,7 +18,7 @@ trait AbsStateDecl { self: TyChecker => locals: Map[Local, AbsValue], symEnv: Map[Sym, ValueTy], pred: SymPred, - ) extends AbsStateLike { + ) extends AbsStateElem { import AbsState.* given AbsState = this @@ -155,7 +155,7 @@ trait AbsStateDecl { self: TyChecker => def get(base: AbsValue, field: AbsValue)(using AbsState): AbsValue = { import SymExpr.*, SymRef.*, SymTy.* val guard = lookupGuard(base.guard, field) - (base.symty, field.ty.getSingle) match + (base.symty, field.ty.toFlat) match case (SRef(ref), One(Str(f))) => AbsValue(SRef(SField(ref, STy(StrT(f)))), guard) case _ => @@ -184,7 +184,7 @@ trait AbsStateDecl { self: TyChecker => private def lookupAstIdxField( name: String, idx: Int, - )(field: ValueTy): ValueTy = field.math.getSingle match + )(field: ValueTy): ValueTy = field.math.toFlat match case Zero => BotT case One(k) => (for { @@ -197,7 +197,7 @@ trait AbsStateDecl { self: TyChecker => // lookup string fields of ASTs private def lookupAstStrField(field: ValueTy): ValueTy = val nameMap = cfg.grammar.nameMap - field.str.getSingle match + field.str.toFlat match case Zero => BotT case One(name) if nameMap contains name => AstT(name) case _ => AstT // TODO warning(s"invalid access: $name of $ast") @@ -243,7 +243,7 @@ trait AbsStateDecl { self: TyChecker => field: AbsValue, )(using AbsState): TypeGuard = { import RefinementKind.* - field.ty.str.getSingle match + field.ty.str.toFlat match case One("Value") => TypeGuard(guard.map.collect { case (RefinementKind(ty), map) if ty == NormalT(TrueT) => @@ -327,7 +327,7 @@ trait AbsStateDecl { self: TyChecker => ): (AbsValue, AbsState) = (AbsValue(ListT(vs.foldLeft(BotT)(_ || _.ty))), this) } - object AbsState extends DomainLike[AbsState] { + object AbsState extends StateDomain { /** top element */ lazy val Top: AbsState = exploded("top abstract state") diff --git a/src/main/scala/esmeta/analyzer/tychecker/AbsTransfer.scala b/src/main/scala/esmeta/analyzer/tychecker/AbsTransfer.scala index 63ba761344..ea08b2ab0f 100644 --- a/src/main/scala/esmeta/analyzer/tychecker/AbsTransfer.scala +++ b/src/main/scala/esmeta/analyzer/tychecker/AbsTransfer.scala @@ -752,7 +752,7 @@ trait AbsTransferDecl { analyzer: TyChecker => var math = lty.math val infinity = lty.infinity -- (if (!(isLt ^ pos)) InfinityTy.Pos else InfinityTy.Neg) - if (lty.math <= MathTy.Int) rty.getSingle match + if (lty.math <= MathTy.Int) rty.toFlat match case One(Math(0)) => math = (isLt, pos) match case (true, true) => /* x < 0 */ MathTy.NegInt @@ -960,7 +960,7 @@ trait AbsTransferDecl { analyzer: TyChecker => } yield { val lty = lv.ty val rty = rv.ty - def aux(positive: Boolean): ValueTy = rty.str.getSingle match + def aux(positive: Boolean): ValueTy = rty.str.toFlat match case One(tname) => val vty = ValueTy.fromTypeOf(tname) if (positive) lty && vty else lty -- vty @@ -1543,7 +1543,7 @@ trait AbsTransferDecl { analyzer: TyChecker => if (ty <= givenTy) None else Some(x -> givenTy) case SField(base, STy(x)) if x <= StrT && x.isSingle => val bty = st.getTy(base) - val field = x.str.getSingle match + val field = x.str.toFlat match case One(elem) => elem case _ => return None val refinedTy = ValueTy( @@ -1621,7 +1621,7 @@ trait AbsTransferDecl { analyzer: TyChecker => given AbsState <- get lty = lv.ty rty = rv.ty - refinedV = rty.str.getSingle match + refinedV = rty.str.toFlat match case One(tname) => val value = AbsValue(ValueTy.fromTypeOf(tname)) if (positive) lv ⊓ value else lv -- value @@ -1724,7 +1724,7 @@ trait AbsTransferDecl { analyzer: TyChecker => }, "RequireInternalSlot" -> { (func, vs, retTy, st) => given AbsState = st - val refined = vs(1).ty.str.getSingle match + val refined = vs(1).ty.str.toFlat match case One(f) => ValueTy( record = ObjectT.record.update(f, Binding.Exist, refine = true), diff --git a/src/main/scala/esmeta/analyzer/tychecker/AbsValue.scala b/src/main/scala/esmeta/analyzer/tychecker/AbsValue.scala index 0c4ca8824b..b0867159c7 100644 --- a/src/main/scala/esmeta/analyzer/tychecker/AbsValue.scala +++ b/src/main/scala/esmeta/analyzer/tychecker/AbsValue.scala @@ -19,7 +19,7 @@ trait AbsValueDecl { self: TyChecker => case class AbsValue( symty: SymTy, guard: TypeGuard = TypeGuard.Empty, - ) extends AbsValueLike { + ) extends AbsValueElem { import AbsValue.* /** bottom check */ @@ -406,7 +406,7 @@ trait AbsValueDecl { self: TyChecker => s"$this (${ty})" } - object AbsValue extends DomainLike[AbsValue] { + object AbsValue extends ValueDomain { def apply(ty: ValueTy): AbsValue = AbsValue(STy(ty), TypeGuard.Empty) diff --git a/src/main/scala/esmeta/analyzer/tychecker/SymTy.scala b/src/main/scala/esmeta/analyzer/tychecker/SymTy.scala index 370a9383b5..e036170eb2 100644 --- a/src/main/scala/esmeta/analyzer/tychecker/SymTy.scala +++ b/src/main/scala/esmeta/analyzer/tychecker/SymTy.scala @@ -13,17 +13,19 @@ import esmeta.util.domain.{*, given}, BSet.*, Flat.* trait SymTyDecl { self: TyChecker => import tyStringifier.given - enum SymTy { + enum SymTy extends AnalysisElem[SymTy] { case STy(ty: ValueTy) case SRef(ref: SymRef) case SNormal(symty: SymTy) + def domain = SymTy + def isBottom: Boolean = this match case STy(ty) => ty.isBottom case SRef(ref) => false case SNormal(symty) => symty.isBottom - def isSingle(using st: AbsState): Boolean = this.ty.getSingle match + def isSingle(using st: AbsState): Boolean = this.ty.toFlat match case One(_) => true case _ => false @@ -96,11 +98,9 @@ trait SymTyDecl { self: TyChecker => def getString = s"${this}" } - object SymTy extends DomainLike[SymTy] { - override def Top: SymTy = STy(ValueTy.Top) - - override def Bot: SymTy = STy(ValueTy.Bot) - + object SymTy extends AnalysisDomain[SymTy] { + lazy val Top: SymTy = STy(ValueTy.Top) + lazy val Bot: SymTy = STy(ValueTy.Bot) given rule: Rule[SymTy] = (app, elem) => elem match { case STy(ty) => app >> ty diff --git a/src/main/scala/esmeta/analyzer/tychecker/TypeGuard.scala b/src/main/scala/esmeta/analyzer/tychecker/TypeGuard.scala index 44ddc766ec..566c53dd6d 100644 --- a/src/main/scala/esmeta/analyzer/tychecker/TypeGuard.scala +++ b/src/main/scala/esmeta/analyzer/tychecker/TypeGuard.scala @@ -328,7 +328,7 @@ trait TypeGuardDecl { self: TyChecker => ref match case SBase(x) => app >> x case SField(base, STy(x)) if !x.isBottom => - x.getSingle match + x.toFlat match case One(Str(f)) => app >> base >> "." >> f case _ => app >> base >> "[" >> x >> "]" case SField(base, field) => app >> base >> "[" >> field >> "]" diff --git a/src/main/scala/esmeta/ty/BoolTy.scala b/src/main/scala/esmeta/ty/BoolTy.scala index 824dd4df2c..ca3102c1a7 100644 --- a/src/main/scala/esmeta/ty/BoolTy.scala +++ b/src/main/scala/esmeta/ty/BoolTy.scala @@ -29,8 +29,8 @@ case class BoolTy(set: Set[Boolean] = Set()) extends TyElem { /** inclusion check */ def contains(b: Boolean): Boolean = set contains b - /** get single value */ - def getSingle: Flat[Boolean] = Flat(set) + /** flatten */ + def toFlat: Flat[Boolean] = Flat(set) } object BoolTy extends Parser.From(Parser.boolTy) { def apply(b: Boolean): BoolTy = BoolTy(Set(b)) diff --git a/src/main/scala/esmeta/ty/CloTy.scala b/src/main/scala/esmeta/ty/CloTy.scala index 3877a1f01b..b0e9ad7cca 100644 --- a/src/main/scala/esmeta/ty/CloTy.scala +++ b/src/main/scala/esmeta/ty/CloTy.scala @@ -54,8 +54,8 @@ sealed trait CloTy extends TyElem { case CloSetTy(names) => names contains fname case _ => true - /** get single value */ - def getSingle: Flat[Clo] = if (isBottom) Zero else Many + /** flatten */ + def toFlat: Flat[Clo] = if (isBottom) Zero else Many /** to list of atomic closure types */ def toAtomicTys: List[CloTy] = if (isBottom) Nil else List(this) diff --git a/src/main/scala/esmeta/ty/InfinityTy.scala b/src/main/scala/esmeta/ty/InfinityTy.scala index c462b7dd98..01375be4bb 100644 --- a/src/main/scala/esmeta/ty/InfinityTy.scala +++ b/src/main/scala/esmeta/ty/InfinityTy.scala @@ -31,8 +31,8 @@ case class InfinityTy(pos: Set[Boolean] = Set()) extends TyElem { /** inclusion check */ def contains(b: Boolean): Boolean = pos contains b - /** get single value */ - def getSingle: Flat[Boolean] = Flat(pos) + /** flatten */ + def toFlat: Flat[Boolean] = Flat(pos) } object InfinityTy extends Parser.From(Parser.infTy) { lazy val Top: InfinityTy = InfinityTy(Set(true, false)) diff --git a/src/main/scala/esmeta/ty/IntTy.scala b/src/main/scala/esmeta/ty/IntTy.scala index 964d868c47..db27fccd8c 100644 --- a/src/main/scala/esmeta/ty/IntTy.scala +++ b/src/main/scala/esmeta/ty/IntTy.scala @@ -162,7 +162,7 @@ sealed trait IntTy extends TyElem { case IntSignTy(sign) => sign.isZero case IntSetTy(set) => true - def getSingle: Flat[BigInt] = this.canon match + def toFlat: Flat[BigInt] = this.canon match case IntSetTy(set) => Flat(set) case IntSignTy(sign) => if sign.isBottom then Flat.Zero @@ -208,7 +208,7 @@ object IntTy { * a singleton set if the result is a singleton, otherwise Top */ def single(l: IntTy, r: IntTy, f: (BigInt, BigInt) => BigInt) = - (l.getSingle, r.getSingle) match + (l.toFlat, r.toFlat) match case (Flat.One(lv), Flat.One(rv)) => IntSetTy(Set(f(lv, rv))) case _ => Top } diff --git a/src/main/scala/esmeta/ty/MapTy.scala b/src/main/scala/esmeta/ty/MapTy.scala index 7f50f387c9..4fc8540b96 100644 --- a/src/main/scala/esmeta/ty/MapTy.scala +++ b/src/main/scala/esmeta/ty/MapTy.scala @@ -54,8 +54,8 @@ enum MapTy extends TyElem { case Bot => BotT case Elem(k, _) => k - /** get single value */ - def getSingle: Flat[Value] = if (this.isBottom) Zero else Many + /** flatten */ + def toFlat: Flat[Value] = if (this.isBottom) Zero else Many /** map containment check */ def contains(m: MapObj, heap: Heap): Boolean = this match diff --git a/src/main/scala/esmeta/ty/MathTy.scala b/src/main/scala/esmeta/ty/MathTy.scala index 6c9c2512d1..b21756e747 100644 --- a/src/main/scala/esmeta/ty/MathTy.scala +++ b/src/main/scala/esmeta/ty/MathTy.scala @@ -184,8 +184,8 @@ sealed trait MathTy extends TyElem { case MathIntTy(int) => int.contains(bint.bigInt) case MathSetTy(set) => set.exists(_.decimal == bint.bigInt) - /** get single value */ - def getSingle: Flat[Math] = this.canon match + /** flatten */ + def toFlat: Flat[Math] = this.canon match case MathSignTy(sign) => if sign.isZero then Flat(Math(0)) else Many case MathIntTy(int) => int.toMathSet.fold(Flat.Zero)(Flat(_)) case MathSetTy(set) => Flat(set) diff --git a/src/main/scala/esmeta/ty/NumberTy.scala b/src/main/scala/esmeta/ty/NumberTy.scala index bf58b14f39..2cce85f554 100644 --- a/src/main/scala/esmeta/ty/NumberTy.scala +++ b/src/main/scala/esmeta/ty/NumberTy.scala @@ -129,13 +129,13 @@ sealed trait NumberTy extends TyElem { else number.double.isWhole && int.contains(number.double.toInt) case NumberSetTy(set) => set contains number - /** get single value */ - def getSingle: Flat[Number] = this.canon match + /** flatten */ + def toFlat: Flat[Number] = this.canon match case s if s.isBottom => Flat.Zero case NumberSetTy(set) => Flat(set) case NumberIntTy(int, nan) => if nan && int.isBottom then Flat(Number(Double.NaN)) - else int.getSingle.map(x => Number(x.toDouble)) + else int.toFlat.map(x => Number(x.toDouble)) case NumberSignTy(sign, nan) => if nan && sign.isBottom then Flat(Number(Double.NaN)) else if sign.isZero then Flat(Number(0)) diff --git a/src/main/scala/esmeta/ty/ValueTy.scala b/src/main/scala/esmeta/ty/ValueTy.scala index 959338bbcc..c2b1cdaf9d 100644 --- a/src/main/scala/esmeta/ty/ValueTy.scala +++ b/src/main/scala/esmeta/ty/ValueTy.scala @@ -253,28 +253,28 @@ sealed trait ValueTy extends Ty { ) ValueTy.Top else this - /** get single value */ - def getSingle: Flat[Value] = - clo.getSingle || + /** flatten */ + def toFlat: Flat[Value] = + clo.toFlat || (if (this.cont.isBottom) Zero else Many) || (if (this.record.isBottom) Zero else Many) || (if (this.map.isBottom) Zero else Many) || (if (this.list.isBottom) Zero else Many) || (if (this.ast.isBottom) Zero else Many) || - grammarSymbol.getSingle || + grammarSymbol.toFlat || (if (this.codeUnit.isBottom) Zero else Many) || - (enumv.getSingle.map(Enum(_): Value)) || - math.getSingle || - (infinity.getSingle.map(Infinity(_): Value)) || - number.getSingle || + (enumv.toFlat.map(Enum(_): Value)) || + math.toFlat || + (infinity.toFlat.map(Infinity(_): Value)) || + number.toFlat || (if (this.bigInt.isBottom) Zero else Many) || - (str.getSingle.map(Str(_): Value)) || - (bool.getSingle.map(Bool(_): Value)) || + (str.toFlat.map(Str(_): Value)) || + (bool.toFlat.map(Bool(_): Value)) || (if (this.undef.isBottom) Zero else One(Undef)) || (if (this.nullv.isBottom) Zero else One(Null)) /** single value check */ - def isSingle: Boolean = getSingle match + def isSingle: Boolean = toFlat match case One(_) => true case _ => false diff --git a/src/main/scala/esmeta/util/domain/BSet.scala b/src/main/scala/esmeta/util/domain/BSet.scala new file mode 100644 index 0000000000..c67350802c --- /dev/null +++ b/src/main/scala/esmeta/util/domain/BSet.scala @@ -0,0 +1,57 @@ +package esmeta.util.domain + +import esmeta.util.BaseUtils.* +import scala.annotation.unchecked.uncheckedVariance +import Flat.*, BSet.* + +enum BSet[+T] { + case Inf extends BSet[Nothing] + case Fin(set: Set[T @uncheckedVariance]) extends BSet[T] + + /** map function */ + def map[U](f: T => U): BSet[U] = this match + case Inf => Inf + case Fin(set) => Fin(set map f) + + /** prune operator */ + def --[U >: T](that: BSet[U]): BSet[U] = (this, that) match + case (_, Inf) => Fin(Set()) + case (Inf, _) => this + case (Fin(l), Fin(r)) => Fin(l.toSet -- r.toSet) + + /** unsound conversion to list */ + def unsoundList: List[T] = this match + case Fin(set) => set.toList + case Inf => warn(s"unsound iteration on infinite set"); Nil + + def isTop: Boolean = this == Inf + def isBottom: Boolean = this == Fin(Set()) + inline def ⊑(that: BSet[T @uncheckedVariance]): Boolean = this ⊑ that + def <=[U >: T](that: BSet[U]): Boolean = (this, that) match + case (_, Inf) => true + case (Inf, _) => false + case (Fin(lset), Fin(rset)) => lset.toSet subsetOf rset.toSet + inline def ⊔(that: BSet[T @uncheckedVariance]): BSet[T] = this ⊔ that + def ||[U >: T](that: BSet[U]): BSet[U] = (this, that) match + case (Inf, _) | (_, Inf) => Inf + case (Fin(lset), Fin(rset)) => Fin(lset ++ rset) + inline def ⊓(that: BSet[T @uncheckedVariance]): BSet[T] = this ⊓ that + def &&[U >: T](that: BSet[U]): BSet[U] = (this, that) match + case (Inf, _) => that + case (_, Inf) => this + case (Fin(lset), Fin(rset)) => Fin(lset.toSet intersect rset.toSet) + def contains(value: T @uncheckedVariance): Boolean = this match + case Inf => true + case Fin(set) => set.toSet contains value + def toBSet: BSet[T] = this + def toFlat: Flat[T] = this match + case Inf => Many + case Fin(set) if set.size == 1 => One(set.head) + case Fin(_) => Zero +} +object BSet { + val Top = Inf + val Bot = BSet() + inline def apply[A](elems: A*): BSet[A] = apply(elems) + inline def apply[A](elems: Iterable[A]): BSet[A] = Fin(elems.toSet) +} diff --git a/src/main/scala/esmeta/util/domain/BSetDomain.scala b/src/main/scala/esmeta/util/domain/BSetDomain.scala deleted file mode 100644 index 5cfcb05ecb..0000000000 --- a/src/main/scala/esmeta/util/domain/BSetDomain.scala +++ /dev/null @@ -1,61 +0,0 @@ -package esmeta.util.domain - -import esmeta.util.BaseUtils.* -import scala.annotation.unchecked.uncheckedVariance -import Flat.*, BSet.* - -/** set abstract domains */ -trait BSetDomain[A] extends Domain[A, BSet[A]]: - val Top = Inf - val Bot = Fin(Set()) - def alpha(elems: Iterable[A]): BSet[A] = BSet(elems) - -enum BSet[+A]: - case Inf extends BSet[Nothing] - case Fin(set: Set[A @uncheckedVariance]) extends BSet[A] - -object BSet: - val Top = Inf - val Bot = BSet() - inline def apply[A](elems: A*): BSet[A] = apply(elems) - inline def apply[A](elems: Iterable[A]): BSet[A] = Fin(elems.toSet) - -given bsetOps[A]: Ops[A, BSet[A]] with - extension (elem: BSet[A]) - def isTop: Boolean = elem == Inf - def isBottom: Boolean = elem == Fin(Set()) - def ⊑(that: BSet[A]): Boolean = (elem, that) match - case (_, Inf) => true - case (Inf, _) => false - case (Fin(lset), Fin(rset)) => lset subsetOf rset - def ⊔(that: BSet[A]): BSet[A] = (elem, that) match - case (Inf, _) | (_, Inf) => Inf - case (Fin(lset), Fin(rset)) => Fin(lset ++ rset) - def ⊓(that: BSet[A]): BSet[A] = (elem, that) match - case (Inf, _) => that - case (_, Inf) => elem - case (Fin(lset), Fin(rset)) => Fin(lset intersect rset) - def contains(value: A): Boolean = elem match - case Inf => true - case Fin(set) => set contains value - def gamma: BSet[A] = elem - def getSingle: Flat[A] = elem match - case Inf => Many - case Fin(set) if set.size == 1 => One(set.head) - case Fin(_) => Zero - - /** map function */ - def map[B](f: A => B): BSet[B] = elem match - case Inf => Inf - case Fin(set) => Fin(set map f) - - /** prune operator */ - def --(that: BSet[A]): BSet[A] = (elem, that) match - case (_, Inf) => Fin(Set()) - case (Inf, _) => elem - case (Fin(l), Fin(r)) => Fin(l -- r) - - /** unsound conversion to list */ - def unsoundList: List[A] = elem match - case Fin(set) => set.toList - case Inf => warn(s"unsound iteration on infinite set"); Nil diff --git a/src/main/scala/esmeta/util/domain/Domain.scala b/src/main/scala/esmeta/util/domain/Domain.scala index f00845b6fa..763cbf26cb 100644 --- a/src/main/scala/esmeta/util/domain/Domain.scala +++ b/src/main/scala/esmeta/util/domain/Domain.scala @@ -3,11 +3,16 @@ package esmeta.util.domain import esmeta.util.* import esmeta.util.Appender.* import esmeta.util.BaseUtils.* +import Flat.*, BSet.* -// ----------------------------------------------------------------------------- -// abstract domain -// ----------------------------------------------------------------------------- -trait Domain[A, Elem: ElemOps[A]]: +/** abstract domain */ +trait Domain { + + /** concrete element type */ + type Conc + + /** abstract element type */ + type Elem /** top element */ def Top: Elem @@ -16,13 +21,44 @@ trait Domain[A, Elem: ElemOps[A]]: def Bot: Elem /** abstraction */ - def alpha(elems: Iterable[A]): Elem + def alpha(elems: Iterable[Conc]): Elem /** abstraction */ - def alpha(elems: A*): Elem = alpha(elems) + inline def alpha(elems: Conc*): Elem = alpha(elems) /** abstraction */ - def apply(elems: Iterable[A]): Elem = alpha(elems) + inline def apply(elems: Iterable[Conc]): Elem = alpha(elems) /** abstraction */ - def apply(elems: A*): Elem = alpha(elems) + inline def apply(elems: Conc*): Elem = alpha(elems) + + extension (elem: Elem) { + + /** top element check */ + def isTop: Boolean + + /** bottom element check */ + def isBottom: Boolean + + /** partial order */ + def ⊑(that: Elem): Boolean + + /** not partial order */ + inline def !⊑(that: Elem): Boolean = !(elem ⊑ that) + + /** join operator */ + def ⊔(that: Elem): Elem + + /** meet operator */ + def ⊓(that: Elem): Elem + + /** contains operator */ + def contains(value: Conc): Boolean + + /** concretization to set domain */ + def toBSet: BSet[Conc] + + /** concretization to flat domain */ + def toFlat: Flat[Conc] + } +} diff --git a/src/main/scala/esmeta/util/domain/Flat.scala b/src/main/scala/esmeta/util/domain/Flat.scala new file mode 100644 index 0000000000..5ea3d50368 --- /dev/null +++ b/src/main/scala/esmeta/util/domain/Flat.scala @@ -0,0 +1,63 @@ +package esmeta.util.domain + +import esmeta.util.BaseUtils.* +import scala.annotation.unchecked.uncheckedVariance +import Flat.*, BSet.* + +/** flat abstraction */ +enum Flat[+T] { + case Many extends Flat[Nothing] + case One(value: T) extends Flat[T] + case Zero extends Flat[Nothing] + + /** map function */ + def map[U](f: T => U): Flat[U] = this match + case Many => Many + case One(value) => One(f(value)) + case Zero => Zero + + /** prune operator */ + def --(that: Flat[T @uncheckedVariance]): Flat[T] = (this, that) match + case (Many, One(_)) => Many + case (_, Zero) => this + case (One(l), One(r)) => if (l == r) Zero else this + case (Zero, _) | (_, Many) => Zero + + def isTop: Boolean = this == Many + def isBottom: Boolean = this == Zero + inline def ⊑(that: Flat[T @uncheckedVariance]): Boolean = this ⊑ that + def <=[U >: T](that: Flat[U]): Boolean = (this, that) match + case (Zero, _) | (_, Many) => true + case (Many, _) | (_, Zero) => false + case (One(l), One(r)) => l == r + inline def ⊔(that: Flat[T @uncheckedVariance]): Flat[T] = this || that + def ||[U >: T](that: Flat[U]): Flat[U] = (this, that) match + case (Many, _) | (_, Many) => Many + case (Zero, _) => that + case (_, Zero) => this + case (One(l), One(r)) => if (l == r) this else Many + inline def ⊓(that: Flat[T @uncheckedVariance]): Flat[T] = this && that + def &&[U >: T](that: Flat[U]): Flat[U] = (this, that) match + case (Zero, _) | (_, Zero) => Zero + case (Many, _) => that + case (_, Many) => this + case (One(l), One(r)) => if (l == r) this else Zero + def contains(value: T @uncheckedVariance): Boolean = this match + case Many => true + case One(v) => v == value + case Zero => false + def toBSet: BSet[T] = this match + case Many => Inf + case One(value) => Fin(Set(value)) + case Zero => Fin(Set()) + def toFlat: Flat[T] = this +} +object Flat { + val Top = Many + val Bot = Zero + inline def apply[A](elems: A*): Flat[A] = apply(elems) + def apply[A](elems: Iterable[A]): Flat[A] = + if (elems.isEmpty) Zero + else if (elems.size == 1) One(elems.head) + else Many +} diff --git a/src/main/scala/esmeta/util/domain/FlatDomain.scala b/src/main/scala/esmeta/util/domain/FlatDomain.scala deleted file mode 100644 index 7b093a7ab8..0000000000 --- a/src/main/scala/esmeta/util/domain/FlatDomain.scala +++ /dev/null @@ -1,70 +0,0 @@ -package esmeta.util.domain - -import esmeta.util.BaseUtils.* -import Flat.*, BSet.* - -/** flat abstract domain */ -trait FlatDomain[A] extends Domain[A, Flat[A]]: - val Top = Many - val Bot = Zero - def alpha(elems: Iterable[A]): Flat[A] = Flat(elems) - -enum Flat[+A]: - case Many extends Flat[Nothing] - case One(value: A) extends Flat[A] - case Zero extends Flat[Nothing] - -object Flat: - val Top = Many - val Bot = Zero - inline def apply[A](elems: A*): Flat[A] = apply(elems) - def apply[A](elems: Iterable[A]): Flat[A] = - if (elems.isEmpty) Zero - else if (elems.size == 1) One(elems.head) - else Many - -given flatOps[A]: Ops[A, Flat[A]] with - extension (elem: Flat[A]) - def isTop: Boolean = elem == Many - def isBottom: Boolean = elem == Zero - def ⊑(that: Flat[A]): Boolean = (elem, that) match - case (Zero, _) | (_, Many) => true - case (Many, _) | (_, Zero) => false - case (One(l), One(r)) => l == r - def ⊔(that: Flat[A]): Flat[A] = (elem, that) match - case (Many, _) | (_, Many) => Many - case (Zero, _) => that - case (_, Zero) => elem - case (One(l), One(r)) => if (l == r) elem else Many - def ⊓(that: Flat[A]): Flat[A] = (elem, that) match - case (Zero, _) | (_, Zero) => Zero - case (Many, _) => that - case (_, Many) => elem - case (One(l), One(r)) => if (l == r) elem else Zero - def --(that: Flat[A]): Flat[A] = (elem, that) match - case (Zero, _) | (_, Many) => Zero - case (_, Zero) => elem - case (Many, One(_)) => Many - case (One(l), One(r)) => if (l == r) Zero else elem - def contains(value: A): Boolean = elem match - case Many => true - case One(v) => v == value - case Zero => false - def gamma: BSet[A] = elem match - case Many => Inf - case One(value) => Fin(Set(value)) - case Zero => Fin(Set()) - def getSingle: Flat[A] = elem - - /** map function */ - def map[B](f: A => B): Flat[B] = elem match - case Many => Many - case One(value) => One(f(value)) - case Zero => Zero - - /** prune operator */ - def --(that: => Flat[A]): Flat[A] = (elem, that) match - case (Many, One(_)) => Many - case (_, Zero) => elem - case (One(l), One(r)) => if (l == r) Zero else elem - case (Zero, _) | (_, Many) => Zero diff --git a/src/main/scala/esmeta/util/domain/Ops.scala b/src/main/scala/esmeta/util/domain/Ops.scala deleted file mode 100644 index 4a9a20f50b..0000000000 --- a/src/main/scala/esmeta/util/domain/Ops.scala +++ /dev/null @@ -1,35 +0,0 @@ -package esmeta.util.domain - -/** operations on elements of an abstract domain */ -trait Ops[A, Elem]: - extension (elem: Elem) - - /** top element check */ - def isTop: Boolean - - /** bottom element check */ - def isBottom: Boolean - - /** partial order */ - def ⊑(that: Elem): Boolean - inline def <=(that: Elem): Boolean = elem ⊑ that - - /** join operator */ - def ⊔(that: Elem): Elem - inline def ||(that: Elem): Elem = elem ⊔ that - - /** meet operator */ - def ⊓(that: Elem): Elem - inline def &&(that: Elem): Elem = elem ⊓ that - - /** contains operator */ - def contains(value: A): Boolean - - /** concretization to set domain */ - def gamma: BSet[A] - - /** concretization to flat domain */ - def getSingle: Flat[A] - -/** type alias to use ElemOps as context bound */ -type ElemOps[A] = [T] =>> Ops[A, T] diff --git a/src/main/scala/esmeta/util/domain/SimpleDomain.scala b/src/main/scala/esmeta/util/domain/SimpleDomain.scala deleted file mode 100644 index 8998383b81..0000000000 --- a/src/main/scala/esmeta/util/domain/SimpleDomain.scala +++ /dev/null @@ -1,20 +0,0 @@ -package esmeta.util.domain - -import Flat.*, BSet.* - -/** simple abstract domains */ -trait SimpleDomain[A] extends Domain[A, Boolean]: - val Top = true - val Bot = false - def alpha(elems: Iterable[A]): Boolean = elems.nonEmpty - -given simpleOps[A]: Ops[A, Boolean] with - extension (elem: Boolean) - def isTop: Boolean = elem - def isBottom: Boolean = !elem - def ⊑(that: Boolean): Boolean = !elem || that - def ⊔(that: Boolean): Boolean = elem || that - def ⊓(that: Boolean): Boolean = elem && that - def contains(value: A): Boolean = elem - def gamma: BSet[A] = if (elem) Inf else Fin(Set()) - def getSingle: Flat[A] = if (elem) Many else Zero diff --git a/src/main/scala/esmeta/util/domain/package.scala b/src/main/scala/esmeta/util/domain/package.scala new file mode 100644 index 0000000000..b54d33501f --- /dev/null +++ b/src/main/scala/esmeta/util/domain/package.scala @@ -0,0 +1,63 @@ +package esmeta.util.domain + +import esmeta.util.* +import esmeta.util.Appender.* +import esmeta.util.BaseUtils.* +import Flat.*, BSet.* + +/** set abstract domains */ +class BSetDomain[T] extends Domain { + type Conc = T + type Elem = BSet[Conc] + val Top = Inf + val Bot = Fin(Set()) + def alpha(elems: Iterable[T]): BSet[T] = BSet(elems) + extension (elem: BSet[T]) { + def isTop: Boolean = elem.isTop + def isBottom: Boolean = elem.isBottom + def ⊑(that: BSet[T]): Boolean = elem <= that + def ⊔(that: BSet[T]): BSet[T] = elem || that + def ⊓(that: BSet[T]): BSet[T] = elem && that + def contains(value: T): Boolean = elem contains value + def toBSet: BSet[T] = elem.toBSet + def toFlat: Flat[T] = elem.toFlat + } +} + +/** flat abstract domain */ +trait FlatDomain[T] extends Domain { + type Conc = T + type Elem = Flat[T] + val Top = Many + val Bot = Zero + def alpha(elems: Iterable[T]): Flat[T] = Flat(elems) + extension (elem: Flat[T]) { + def isTop: Boolean = elem.isTop + def isBottom: Boolean = elem.isBottom + def ⊑(that: Flat[T]): Boolean = elem <= that + def ⊔(that: Flat[T]): Flat[T] = elem || that + def ⊓(that: Flat[T]): Flat[T] = elem && that + def contains(value: T): Boolean = elem contains value + def toBSet: BSet[T] = elem.toBSet + def toFlat: Flat[T] = elem.toFlat + } +} + +/** simple abstract domains */ +trait SimpleDomain[T] extends Domain { + type Conc = T + type Elem = Boolean + val Top = true + val Bot = false + def alpha(elems: Iterable[T]): Boolean = elems.nonEmpty + extension (elem: Boolean) { + def isTop: Boolean = elem + def isBottom: Boolean = !elem + def ⊑(that: Boolean): Boolean = !elem || that + def ⊔(that: Boolean): Boolean = elem || that + def ⊓(that: Boolean): Boolean = elem && that + def contains(value: T): Boolean = elem + def toBSet: BSet[T] = if (elem) Inf else Fin(Set()) + def toFlat: Flat[T] = if (elem) Many else Zero + } +}