Skip to content

Commit

Permalink
basic structural inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
pshirshov committed Sep 14, 2023
1 parent 5b9caba commit 69ebea2
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/main/scala/io/septimalmind/baboon/BaboonModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 19 additions & 4 deletions src/main/scala/io/septimalmind/baboon/parser/defns/DefDto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import io.septimalmind.baboon.parser.model.{
RawDtoMember,
RawField,
RawFieldName,
RawTypeName,
RawTypeRef,
RawTypeName
ScopedRef
}
import izumi.fundamentals.collections.nonempty.NEList

Expand All @@ -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 {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ object RawTypeRef {
case class Constructor(name: RawTypeName, params: NEList[RawTypeRef])
extends RawTypeRef
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.septimalmind.baboon.parser.model

import izumi.fundamentals.collections.nonempty.NEList

case class ScopedRef(path: NEList[RawTypeName])
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
33 changes: 25 additions & 8 deletions src/main/scala/io/septimalmind/baboon/typer/BaboonTranslator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
77 changes: 67 additions & 10 deletions src/main/scala/io/septimalmind/baboon/typer/BaboonTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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] = {
Expand Down
77 changes: 70 additions & 7 deletions src/main/scala/io/septimalmind/baboon/typer/ScopeSupport.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
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}
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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 =>
Expand Down
Loading

0 comments on commit 69ebea2

Please sign in to comment.