Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ object AST:

object Parents:
def empty[CV <: RiddlValue]: Parents = Seq.empty[Branch[?]]
def apply(contents: Branch[?]*) = Seq(contents: _*)
end Parents

/** A mutable stack of Branch[?] for keeping track of the parent hierarchy */
Expand All @@ -956,6 +957,7 @@ object AST:
object ParentStack:
/** @return an empty ParentStack */
def empty[CV <: RiddlValue]: ParentStack = mutable.Stack.empty[Branch[?]]
def apply(items: Branch[?]*): ParentStack = mutable.Stack(items: _*)
end ParentStack

type DefinitionStack = mutable.Stack[Definition] // TODO: Make this opaque some day
Expand All @@ -969,6 +971,7 @@ object AST:

object DefinitionStack:
def empty: DefinitionStack = mutable.Stack.empty[Definition]
def apply(items: Definition*): DefinitionStack = mutable.Stack(items: _*)
end DefinitionStack

/** The kind of thing that can be returned by PathId Resolution Pass optionally providing the
Expand Down Expand Up @@ -1953,7 +1956,19 @@ object AST:
}
}

@JSExportTopLevel("ZonedDatTime")
@JSExportTopLevel("ZonedDate")
case class ZonedDate(loc: At, zone: Option[LiteralString] = None) extends TimeType {

override def isAssignmentCompatible(other: TypeExpression): Boolean = {
super.isAssignmentCompatible(other) || other.isInstanceOf[ZonedDateTime] ||
other.isInstanceOf[DateTime] || other.isInstanceOf[Date] || other.isInstanceOf[String_] ||
other.isInstanceOf[Pattern]
}

override def format: String = s"ZonedDateTime(${zone.map(_.format).getOrElse("\"UTC\"")})"
}

@JSExportTopLevel("ZonedDateTime")
case class ZonedDateTime(loc: At, zone: Option[LiteralString] = None) extends TimeType {

override def isAssignmentCompatible(other: TypeExpression): Boolean = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
package com.ossuminc.riddl.language

import com.ossuminc.riddl.language.AST.*
import scala.collection.immutable.HashMap
import scala.collection.mutable

import scala.reflect.{ClassTag, classTag}
import scalajs.js.annotation._
import scalajs.js.annotation.*

/** The referent for finding things within a given [[com.ossuminc.riddl.language.AST.Container]] of
* [[com.ossuminc.riddl.language.AST.RiddlValue]] as found in the AST model. This provides the ability to find values
* in the model by traversing it and looking for the matching condition.
* [[com.ossuminc.riddl.language.AST.RiddlValue]] as found in the AST model. This provides the
* ability to find values in the model by traversing it and looking for the matching condition.
* @param root
* The container of RiddlValues to traverse for the sought condition
*/
Expand All @@ -32,8 +34,9 @@ case class Finder[CV <: RiddlValue](root: Container[CV]) {
*/
@JSExport
def find(select: CV => Boolean): Seq[CV] =
Folding.foldEachDefinition[Seq[CV], CV](root, Seq.empty[CV]) { case (state: Seq[CV], value: CV) =>
if select(value) then state :+ value else state
Folding.foldEachDefinition[Seq[CV], CV](root, Seq.empty[CV]) {
case (state: Seq[CV], value: CV) =>
if select(value) then state :+ value else state
}
end find

Expand All @@ -52,10 +55,11 @@ case class Finder[CV <: RiddlValue](root: Container[CV]) {
/** Find a matching set of [[AST.RiddlValue]] but return them with their parents
*
* @param select
* The boolean expression derived from a candidate [[AST.RiddlValue]] that selects it to the result set
* The boolean expression derived from a candidate [[AST.RiddlValue]] that selects it to the
* result set
* @return
* A [[Finder#DefWithParents]] that returns a [[scala.Seq]] of two-tuples with the [[AST.RiddlValue]] a a
* [[scala.Seq]] of the parents of that value.
* A [[Finder#DefWithParents]] that returns a [[scala.Seq]] of two-tuples with the
* [[AST.RiddlValue]] a a [[scala.Seq]] of the parents of that value.
*/
@JSExport
def findWithParents[T <: RiddlValue: ClassTag](
Expand All @@ -76,18 +80,43 @@ case class Finder[CV <: RiddlValue](root: Container[CV]) {
}
end findWithParents

/** Run a transformation function on the [[Finder]] contents. They type parameter specifies what kind of thing should
* be found, the `select` argument provides further refinement of which things of that type should be selected. The
* transformation function, `transformF` does the transformation, probably by using the Scala `.copy` method.
/** Find the Parents for a given node in the root */
@JSExport
def findParents(node: Definition): Parents = {
val result = findWithParents[Definition](_ == node)
result.headOption.map(_._2).getOrElse(Parents.empty)
}

/** Start from the root Container and for every definition it contains, compute the Parents (path
* to that definition).
* @returns
* A HashMap[Definition,Parents] that provides the path to every definition in a fast-access
* data structure
*/
@JSExport
def findAllPaths: HashMap[Definition, Parents] = {
val stack = ParentStack.empty[Branch[?]]
val result: mutable.HashMap[Definition, Parents] = mutable.HashMap.empty
Folding.foldLeftWithStack(result, root, stack) { case (map, definition: Definition, parents) =>
map.addOne((definition, parents))
map
}
result.toMap[Definition, Parents].asInstanceOf[HashMap[Definition, Parents]]
}

/** Run a transformation function on the [[Finder]] contents. They type parameter specifies what
* kind of thing should be found, the `select` argument provides further refinement of which
* things of that type should be selected. The transformation function, `transformF` does the
* transformation, probably by using the Scala `.copy` method.
*
* @tparam TT
* The transform type. This narrows the search to just the contents that have the base type TT.
* @param select
* The function to select which values should be operated on. It should return true if the transformation function
* should be executed on the element passed to it.
* The function to select which values should be operated on. It should return true if the
* transformation function should be executed on the element passed to it.
* @param transformF
* The transformation function to convert one value to another. The returned value will replace the passed value in
* the [[Finder]]'s container.
* The transformation function to convert one value to another. The returned value will replace
* the passed value in the [[Finder]]'s container.
*/
@JSExport
def transform[TT <: RiddlValue: ClassTag](select: TT => Boolean)(transformF: CV => CV): Unit =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2019 Ossum, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ossuminc.riddl.language

import com.ossuminc.riddl.language.AST.*

import scala.collection.immutable.HashMap
import scala.collection.mutable
// import scala.math.Ordered.orderingToOrdered

Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ private[parsing] trait AdaptorParser(using PlatformContext) { this: ProcessorPar
Index ~ Keywords.adaptor ~/ identifier ~
adaptorDirection ~ contextRef ~ is ~ open ~ adaptorBody ~
close ~ withMetaData ~ Index
)./ map { case (start, id, direction, cRef, contents, descriptives, end) =>
)./ map { (start, id, direction, cRef, contents, meta, end) =>
checkForDuplicateIncludes(contents)
Adaptor(at(start, end), id, direction, cRef, contents.toContents, descriptives.toContents)
Adaptor(at(start, end), id, direction, cRef, contents.toContents, meta.toContents)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ object PredefTypes {
PredefType.Time
).!
)

def zonedDateTypes[u:P]: P[String] = keywords(
StringIn(PredefType.ZonedDate, PredefType.ZonedDate).!
)

def otherTypes[u: P]: P[String] = keywords(
StringIn(
Expand Down Expand Up @@ -121,6 +125,7 @@ object PredefType {
final val UserId = "UserId"
final val UUID = "UUID"
final val Whole = "Whole"
final val ZonedDate = "ZonedDate"
final val ZonedDateTime = "ZonedDateTime"

// NOTE: Keep this list in synch with the one in TokenParser
Expand Down Expand Up @@ -156,6 +161,7 @@ object PredefType {
UserId,
UUID,
Whole,
ZonedDate,
ZonedDateTime
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ trait ProcessorParser
P(
Index ~ Keywords.relationship ~ identifier ~/ to ~ processorRef ~ as ~ relationshipCardinality ~
(Keywords.label ~ as ~ literalString).? ~ withMetaData ~ Index
).map { case (start, id, procRef, cardinality, label, descriptives, end) =>
).map { (start, id, procRef, cardinality, label, descriptives, end) =>
Relationship(at(start, end), id, procRef, cardinality, label, descriptives.toContents)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package com.ossuminc.riddl.language.parsing

import com.ossuminc.riddl.language.AST.*
import com.ossuminc.riddl.language.{AST, At}

import fastparse.*
import fastparse.MultiLineWhitespace.*

Expand Down Expand Up @@ -272,6 +273,26 @@ private[parsing] trait TypeParser {
}
}

private def zone[u: P]: P[Option[LiteralString]] = {
P(Index ~ StringIn("A-Z", "0-9", ":.+-").!.? ~ Index).map { case (start, str, end) =>
str.map(s => LiteralString(at(start, end), s))
}
}

private def zonedPredefTypes[u: P]: P[TypeExpression] = {
P(
Index ~ PredefTypes.zonedDateTypes ~
Punctuation.roundOpen ~ zone ~ Punctuation.roundClose ~
Index
).map { case (start, dateType, zone, end) =>
val loc = at(start, end)
dateType match
case PredefType.ZonedDate => ZonedDate(loc, zone)
case PredefType.ZonedDateTime => ZonedDateTime(loc, zone)
end match
}
}

private def otherPredefTypes[u: P]: P[TypeExpression] = {
P(
Index ~ PredefTypes.otherTypes ~ Index
Expand All @@ -295,7 +316,7 @@ private[parsing] trait TypeParser {
private def predefinedTypes[u: P]: P[TypeExpression] = {
P(
stringType | currencyType | urlType | integerPredefTypes | realPredefTypes | timePredefTypes |
decimalType | otherPredefTypes
zonedPredefTypes | decimalType | otherPredefTypes
)./
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2019 Ossum, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ossuminc.riddl.language

import com.ossuminc.riddl.utils.pc
import com.ossuminc.riddl.language.AST.{Root, Parents, RootContents}
import com.ossuminc.riddl.language.parsing.{
AbstractParsingTest,
RiddlParserInput,
TestParser,
TopLevelParser
}
import com.ossuminc.riddl.utils.{
AbstractTestingBasis,
AbstractTestingBasisWithTestData,
PlatformContext
}
import org.scalatest.TestData

class SharedFinderTest extends AbstractTestingBasis {

val content =
"""module A {
| domain B {
| context C {
| entity D {
| handler E { ??? }
| }
| }
| }
|}
|""".stripMargin
val input = RiddlParserInput(content, "FinderTest")
val root: Root =
TopLevelParser.parseInput(input, true) match
case Left(messages) =>
fail(messages.justErrors.format)
case Right(root: Root) =>
root
end match
val finder: Finder[RootContents] = Finder(root)

"Finder" must {
"find a node" in {
val a = root.modules.head
val b = a.domains.head
val c = b.contexts.head
val d = c.entities.head
val e = d.handlers.head
finder.findParents(d) match {
case s: Parents if s.isEmpty => fail("Path not found")
case s: Parents =>
s must be(Parents(c, b, a, root))
}
}
"build path map correctly" in {
val a = root.modules.head
val b = a.domains.head
val c = b.contexts.head
val d = c.entities.head
val e = d.handlers.head
val pf = finder.findAllPaths
val a_par = pf.getOrElse(a, fail("no path for a"))
val b_par = pf.getOrElse(b, fail("no path for b"))
val c_par = pf.getOrElse(c, fail("no path for c"))
val d_par = pf.getOrElse(d, fail("no path for d"))
val e_par = pf.getOrElse(e, fail("no path for e"))
a_par must be(Parents(root))
b_par must be(Parents(a, root))
c_par must be(Parents(b, a, root))
d_par must be(Parents(c, b, a, root))
e_par must be(Parents(d, c, b, a, root))
}
}
}