From 69ebea292c4255956f30fdfa4b0e7a626789b6de Mon Sep 17 00:00:00 2001 From: Pavel Shirshov Date: Thu, 14 Sep 2023 18:02:26 +0100 Subject: [PATCH] basic structural inheritance --- .../io/septimalmind/baboon/BaboonModule.scala | 2 +- .../baboon/parser/defns/DefDto.scala | 23 +++++- .../baboon/parser/model/RawDtoMember.scala | 9 ++- .../baboon/parser/model/RawTypeRef.scala | 2 + .../baboon/parser/model/ScopedRef.scala | 5 ++ .../parser/model/issues/BaboonIssue.scala | 28 ++++++- .../baboon/typer/BaboonTranslator.scala | 33 ++++++-- .../baboon/typer/BaboonTyper.scala | 77 ++++++++++++++++--- .../baboon/typer/ScopeSupport.scala | 77 +++++++++++++++++-- src/test/resources/baboon/pkg0/pkg01.baboon | 13 ++++ 10 files changed, 237 insertions(+), 32 deletions(-) create mode 100644 src/main/scala/io/septimalmind/baboon/parser/model/ScopedRef.scala diff --git a/src/main/scala/io/septimalmind/baboon/BaboonModule.scala b/src/main/scala/io/septimalmind/baboon/BaboonModule.scala index 995dffd..b6c1e0e 100644 --- a/src/main/scala/io/septimalmind/baboon/BaboonModule.scala +++ b/src/main/scala/io/septimalmind/baboon/BaboonModule.scala @@ -46,7 +46,7 @@ class BaboonModule(options: CompilerOptions) extends ModuleDef { }.running { (translator: BaboonTranslator) => translator }) - .external(DIKey[Pkg], DIKey[NEList[Scope[FullRawDefn]]]) + .external(DIKey[Pkg], DIKey[NEList[Scope[FullRawDefn]]], DIKey[Map[TypeId, DomainMember]]) make[LocalContext[Identity, IndividualConversionHandler]] .fromLocalContext(new ModuleDef { diff --git a/src/main/scala/io/septimalmind/baboon/parser/defns/DefDto.scala b/src/main/scala/io/septimalmind/baboon/parser/defns/DefDto.scala index cc2d13e..e44a40d 100644 --- a/src/main/scala/io/septimalmind/baboon/parser/defns/DefDto.scala +++ b/src/main/scala/io/septimalmind/baboon/parser/defns/DefDto.scala @@ -8,8 +8,9 @@ import io.septimalmind.baboon.parser.model.{ RawDtoMember, RawField, RawFieldName, + RawTypeName, RawTypeRef, - RawTypeName + ScopedRef } import izumi.fundamentals.collections.nonempty.NEList @@ -20,6 +21,12 @@ class DefDto(context: ParserContext, meta: DefMeta) { .map(p => NEList.unsafeFrom(p.toList)) } + def scopedRef[$: P]: P[ScopedRef] = { + idt.symbolSeq.map( + s => ScopedRef(NEList.unsafeFrom(s.map(p => RawTypeName(p)).toList)) + ) + } + def typeRef[$: P]: P[RawTypeRef] = { import fastparse.SingleLineWhitespace.whitespace (idt.symbol ~ typeParams.?).map { @@ -41,11 +48,19 @@ class DefDto(context: ParserContext, meta: DefMeta) { (fieldName ~ ":" ~ typeRef).map { case (n, t) => model.RawField(n, t) } } + def parentDef[$: P]: P[ScopedRef] = { + import fastparse.ScalaWhitespace.whitespace + ("+" ~ scopedRef) + } + def dtoMember[$: P]: P[RawDtoMember] = - P(meta.withMeta(fieldDef)).map { + (P(meta.withMeta(fieldDef)).map { case (meta, field) => - model.RawDtoMember(field, meta) - } + model.RawDtoMember.FieldDef(field, meta) + } | P(meta.withMeta(parentDef)).map { + case (meta, parent) => + model.RawDtoMember.ParentDef(parent, meta) + }) def dto[$: P]: P[Seq[RawDtoMember]] = { import fastparse.ScalaWhitespace.whitespace diff --git a/src/main/scala/io/septimalmind/baboon/parser/model/RawDtoMember.scala b/src/main/scala/io/septimalmind/baboon/parser/model/RawDtoMember.scala index ce89472..910bb11 100644 --- a/src/main/scala/io/septimalmind/baboon/parser/model/RawDtoMember.scala +++ b/src/main/scala/io/septimalmind/baboon/parser/model/RawDtoMember.scala @@ -1,3 +1,10 @@ package io.septimalmind.baboon.parser.model -case class RawDtoMember(field: RawField, meta: RawNodeMeta) +sealed trait RawDtoMember + +object RawDtoMember { + case class FieldDef(field: RawField, meta: RawNodeMeta) extends RawDtoMember + + case class ParentDef(parent: ScopedRef, meta: RawNodeMeta) + extends RawDtoMember +} diff --git a/src/main/scala/io/septimalmind/baboon/parser/model/RawTypeRef.scala b/src/main/scala/io/septimalmind/baboon/parser/model/RawTypeRef.scala index ab44268..a025a90 100644 --- a/src/main/scala/io/septimalmind/baboon/parser/model/RawTypeRef.scala +++ b/src/main/scala/io/septimalmind/baboon/parser/model/RawTypeRef.scala @@ -9,3 +9,5 @@ object RawTypeRef { case class Constructor(name: RawTypeName, params: NEList[RawTypeRef]) extends RawTypeRef } + + diff --git a/src/main/scala/io/septimalmind/baboon/parser/model/ScopedRef.scala b/src/main/scala/io/septimalmind/baboon/parser/model/ScopedRef.scala new file mode 100644 index 0000000..562dfae --- /dev/null +++ b/src/main/scala/io/septimalmind/baboon/parser/model/ScopedRef.scala @@ -0,0 +1,5 @@ +package io.septimalmind.baboon.parser.model + +import izumi.fundamentals.collections.nonempty.NEList + +case class ScopedRef(path: NEList[RawTypeName]) diff --git a/src/main/scala/io/septimalmind/baboon/parser/model/issues/BaboonIssue.scala b/src/main/scala/io/septimalmind/baboon/parser/model/issues/BaboonIssue.scala index 3352447..5ae05a2 100644 --- a/src/main/scala/io/septimalmind/baboon/parser/model/issues/BaboonIssue.scala +++ b/src/main/scala/io/septimalmind/baboon/parser/model/issues/BaboonIssue.scala @@ -2,7 +2,14 @@ package io.septimalmind.baboon.parser.model.issues import fastparse.Parsed import io.septimalmind.baboon.parser.BaboonParser -import io.septimalmind.baboon.parser.model.{RawDefn, RawDomain, RawHeader} +import io.septimalmind.baboon.parser.model.{ + RawDefn, + RawDomain, + RawHeader, + RawTypeName, + ScopedRef +} +import io.septimalmind.baboon.typer.BaboonTyper import io.septimalmind.baboon.typer.BaboonTyper.FullRawDefn import io.septimalmind.baboon.typer.model.* import izumi.fundamentals.collections.nonempty.NEList @@ -95,6 +102,25 @@ object BaboonIssue { case class BadFieldName(name: String) extends TyperIssue case class BadTypeName(name: String) extends TyperIssue + + case class BadInheritance( + bad: Map[TypeId.User, List[(Set[TypeId.User], BaboonTyper.ScopedDefn)]] + ) extends TyperIssue + with BaboonBug + + case class CircularInheritance(e: ToposortError[TypeId.User]) + extends TyperIssue + + case class NameNotFound(pkg: Pkg, name: ScopedRef) extends TyperIssue + + case class UnexpectedScopeLookup(b: Scope[FullRawDefn]) extends TyperIssue + + case class NamSeqeNotFound(names: Seq[RawTypeName]) extends TyperIssue + + case class DuplicatedTypes(dupes: Set[TypeId]) extends TyperIssue + + case class WrongParent(id: TypeId.User, id1: TypeId) extends TyperIssue + // sealed trait EvolutionIssue extends BaboonIssue diff --git a/src/main/scala/io/septimalmind/baboon/typer/BaboonTranslator.scala b/src/main/scala/io/septimalmind/baboon/typer/BaboonTranslator.scala index 71f6b61..a567f64 100644 --- a/src/main/scala/io/septimalmind/baboon/typer/BaboonTranslator.scala +++ b/src/main/scala/io/septimalmind/baboon/typer/BaboonTranslator.scala @@ -11,6 +11,7 @@ import izumi.fundamentals.collections.nonempty.NEList class BaboonTranslator(pkg: Pkg, path: NEList[Scope[FullRawDefn]], + defined: Map[TypeId, DomainMember], scopeSupport: ScopeSupport) { def translate( defn: ScopedDefn @@ -69,14 +70,30 @@ class BaboonTranslator(pkg: Pkg, dto: RawDto ): Either[NEList[BaboonIssue.TyperIssue], DomainMember.User] = { for { - converted <- dto.members.biTraverse { raw => - for { - name <- Right(FieldName(raw.field.name.name)) - _ <- SymbolNames.validFieldName(name) - tpe <- convertTpe(raw.field.tpe) - } yield { - Field(name, tpe) - } + converted <- dto.members.biFlatTraverse { + case f: RawDtoMember.FieldDef => + for { + name <- Right(FieldName(f.field.name.name)) + _ <- SymbolNames.validFieldName(name) + tpe <- convertTpe(f.field.tpe) + } yield { + Seq(Field(name, tpe)) + } + case p: RawDtoMember.ParentDef => + for { + id <- scopeSupport.resolveScopedRef(p.parent, path, pkg) + parentDef = defined(id) + out <- parentDef match { + case DomainMember.User(_, defn: Typedef.Dto) => + Right(defn.fields) + case o => + Left(NEList(BaboonIssue.WrongParent(id, o.id))) + } + + } yield { + out + } + } _ <- converted .map(m => (m.name.name.toLowerCase, m)) diff --git a/src/main/scala/io/septimalmind/baboon/typer/BaboonTyper.scala b/src/main/scala/io/septimalmind/baboon/typer/BaboonTyper.scala index dac49ba..f2a2e08 100644 --- a/src/main/scala/io/septimalmind/baboon/typer/BaboonTyper.scala +++ b/src/main/scala/io/septimalmind/baboon/typer/BaboonTyper.scala @@ -27,7 +27,8 @@ object BaboonTyper { path: NEList[Scope[FullRawDefn]]) class BaboonTyperImpl(enquiries: BaboonEnquiries, - translator: LocalContext[Identity, BaboonTranslator]) + translator: LocalContext[Identity, BaboonTranslator], + scopeSupport: ScopeSupport) extends BaboonTyper { override def process( model: RawDomain @@ -171,27 +172,83 @@ object BaboonTyper { scopes <- buildScopes(pkg, members) flattened = flattenScopes(scopes) + ordered <- order(pkg, flattened) - // we don't support inheritance, so order doesn't matter here - out <- flattened - .map( - defn => - translator + out <- ordered.biFoldLeft(Map.empty[TypeId, DomainMember]) { + case (acc, defn) => + for { + next <- translator .provide(pkg) .provide(defn.path) + .provide(acc) .produce() .use(_.translate(defn)) - ) - .biFlatten + mapped = next.map(m => (m.id, m)) + dupes = acc.keySet.intersect(mapped.map(_._1).toSet) + _ <- Either.ifThenFail(dupes.nonEmpty)( + NEList(BaboonIssue.DuplicatedTypes(dupes)) + ) + } yield { + acc ++ mapped + } + } - indexed <- (initial ++ out) - .map(m => (m.id, m)) + indexed <- (initial.map(m => (m.id, m)) ++ out.toSeq) .toUniqueMap(e => NEList(BaboonIssue.NonUniqueTypedefs(e))) } yield { indexed.values.toList } } + def order( + pkg: Pkg, + flattened: List[ScopedDefn] + ): Either[NEList[BaboonIssue.TyperIssue], List[ScopedDefn]] = { + for { + depmap <- flattened.map(d => deps(pkg, d)).biSequence + asMap <- depmap.toUniqueMap( + bad => NEList(BaboonIssue.BadInheritance(bad)) + ) + + predMatrix = IncidenceMatrix(asMap.view.mapValues(_._1).toMap) + + sorted <- Toposort + .cycleBreaking(predMatrix, ToposortLoopBreaker.dontBreak) + .left + .map(e => NEList(BaboonIssue.CircularInheritance(e))) + + } yield { + sorted.map(id => asMap(id)._2).toList + } + } + + private def deps(pkg: Pkg, defn: ScopedDefn) = { + val d = defn.thisScope.defn.defn match { + case d: RawDto => + d.members + .collect { + case d: RawDtoMember.ParentDef => + Seq(d.parent) + case _ => + Seq.empty + } + .flatten + .toSet + case _ => + Set.empty + } + + for { + rawDefn <- Right(defn.thisScope.defn) + id <- scopeSupport.resolveUserTypeId(rawDefn.defn.name, defn.path, pkg) + mappedDeps <- d + .map(v => scopeSupport.resolveScopedRef(v, defn.path, pkg)) + .biSequence + } yield { + (id, (mappedDeps, defn)) + } + } + private def flattenScopes( root: RootScope[FullRawDefn] ): List[ScopedDefn] = { diff --git a/src/main/scala/io/septimalmind/baboon/typer/ScopeSupport.scala b/src/main/scala/io/septimalmind/baboon/typer/ScopeSupport.scala index 25f73d2..35500e2 100644 --- a/src/main/scala/io/septimalmind/baboon/typer/ScopeSupport.scala +++ b/src/main/scala/io/septimalmind/baboon/typer/ScopeSupport.scala @@ -1,7 +1,7 @@ package io.septimalmind.baboon.typer import io.septimalmind.baboon.parser.model.issues.BaboonIssue -import io.septimalmind.baboon.parser.model.{RawAdt, RawTypeName} +import io.septimalmind.baboon.parser.model.{RawAdt, RawTypeName, ScopedRef} import io.septimalmind.baboon.typer.BaboonTyper.FullRawDefn import io.septimalmind.baboon.typer.model.* import io.septimalmind.baboon.typer.model.Scope.{LeafScope, RootScope, ScopeName, SubScope} @@ -9,6 +9,12 @@ import izumi.functional.IzEither.* import izumi.fundamentals.collections.nonempty.NEList trait ScopeSupport { + def resolveScopedRef( + name: ScopedRef, + path: NEList[Scope[FullRawDefn]], + pkg: Pkg + ): Either[NEList[BaboonIssue.TyperIssue], TypeId.User] + def resolveTypeId(name: RawTypeName, path: NEList[Scope[FullRawDefn]], pkg: Pkg, @@ -20,12 +26,69 @@ trait ScopeSupport { ): Either[NEList[BaboonIssue.TyperIssue], TypeId.User] } - - object ScopeSupport { - case class FoundDefn(defn: FullRawDefn, path: List[Scope[FullRawDefn]]) + case class FoundDefn(defn: FullRawDefn, + path: List[Scope[FullRawDefn]], + scope: Scope[FullRawDefn]) + + case class LookupResult(suffix: List[Scope[FullRawDefn]], + scope: LeafScope[FullRawDefn]) class ScopeSupportImpl extends ScopeSupport { + def resolveScopedRef( + name: ScopedRef, + path: NEList[Scope[FullRawDefn]], + pkg: Pkg + ): Either[NEList[BaboonIssue.TyperIssue], TypeId.User] = { + + findDefn(ScopeName(name.path.head.name), path.reverse.toList) match { + case Some(found) => + for { + scope <- lookupName(name.path.tail, found.scope, List.empty) + fullPath = (found.path :+ found.scope) ++ scope.suffix + resolved <- resolveUserTypeId( + name.path.last, + NEList.unsafeFrom(fullPath.init), + pkg + ) + } yield { + resolved + } + + case None => + Left(NEList(BaboonIssue.NameNotFound(pkg, name))) + } + } + + def lookupName( + names: Seq[RawTypeName], + in: Scope[FullRawDefn], + suffix: List[Scope[FullRawDefn]] + ): Either[NEList[BaboonIssue.TyperIssue], LookupResult] = { + names.headOption match { + case Some(value) => + in match { + case s: SubScope[FullRawDefn] => + s.nested.toMap.get(ScopeName(value.name)) match { + case Some(value) => + lookupName(names.tail, value, suffix :+ value) + case None => + Left(NEList(BaboonIssue.NamSeqeNotFound(names))) + } + case _ => + Left(NEList(BaboonIssue.UnexpectedScoping(List(in)))) + } + case None => + in match { + case s: LeafScope[FullRawDefn] => + Right(LookupResult(suffix, s)) + case b => + Left(NEList(BaboonIssue.UnexpectedScopeLookup(b))) + } + } + + } + def resolveUserTypeId(name: RawTypeName, path: NEList[Scope[FullRawDefn]], pkg: Pkg, @@ -86,19 +149,19 @@ object ScopeSupport { case Some(s: RootScope[FullRawDefn]) => s.nested .get(needle) - .map(n => FoundDefn(n.defn, reversePath)) + .map(n => FoundDefn(n.defn, reversePath, n)) case Some(s: LeafScope[FullRawDefn]) => Some(s) .filter(_.name == needle) - .map(n => FoundDefn(n.defn, reversePath.reverse)) + .map(n => FoundDefn(n.defn, reversePath.reverse, n)) .orElse(findDefn(needle, reversePath.tail)) case Some(s: SubScope[FullRawDefn]) => s.nested.toMap .get(needle) .orElse(Some(s).filter(_.name == needle)) - .map(n => FoundDefn(n.defn, reversePath.reverse)) + .map(n => FoundDefn(n.defn, reversePath.reverse, n)) .orElse(findDefn(needle, reversePath.tail)) case None => diff --git a/src/test/resources/baboon/pkg0/pkg01.baboon b/src/test/resources/baboon/pkg0/pkg01.baboon index 4ea0f10..f746044 100644 --- a/src/test/resources/baboon/pkg0/pkg01.baboon +++ b/src/test/resources/baboon/pkg0/pkg01.baboon @@ -144,4 +144,17 @@ root data T9_test { f3: opt[lst[opt[str]]] f4: opt[lst[opt[T8_Removed_Fields]]] f5: map[str, lst[opt[T8_Removed_Fields]]] +} + +// + +root data T10_D1 { + + T10_A1.B1 + f2: i08 +} + +adt T10_A1 { + data B1 { + f1: T3_D1 + } } \ No newline at end of file