This commit is contained in:
Dima 2021-07-14 16:09:10 +03:00 committed by GitHub
parent 83d5a7b2a3
commit f455716548
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 149 additions and 210 deletions

View File

@ -1,34 +0,0 @@
service OpH("op"):
puk(s: string) -> string
pek(s: string, -- trgtr
c: string) -> string
pyk: []u32 -> string
pok: []bool -> string
identity: -> ⊥
func a( -- ferkjn
b: string, -- fr
c: string, -- asdf
g: string
) -> string: -- rgtr
try:
f = "world"
OpH "planet"
OpH.pek("TRY THIS", -- gtrg
c)
catch err:
OpH.puk(err.msg)
<- f
-- hello
func z(t: ?string, b: []u32, c: *bool):
OpH.puk(t!)
OpH.pyk(b)
OpH.pok(c)
c <- OpH.identity(true)
c <- OpH.identity("string")
func k():
z(nil, nil, nil)

11
aqua-src/test.aqua Normal file
View File

@ -0,0 +1,11 @@
service OpH("op"):
get_str(s: string) -> string
get_arr() -> []string
identity: -> ⊥
func registerKeyPutValue(node_id: string) -> []string:
nodes <- OpH.get_arr()
for n <- nodes par:
on n:
OpH.get_str(node_id)
<- nodes

View File

@ -1,62 +0,0 @@
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class Test1 extends AnyFlatSpec with Matchers {
"test" should "run" in {
import cats.parse.{Parser0 P0, Parser => P, Numbers}
/*
def getTime(peer: PeerId, reply: i64 -> string) -> string =
on peer:
t <- Peer.timestamp()
reply(t)
Scope(Var("peer"), List(
Fetch(
Var("t"),
AbilityCall(
CustomType("Peer"),
FnName("timestamp"),
Nil
)
)
)),
Unscoped(
FuncCall("reply", List(Var("t"))
)
(seq
(call %init_peer_id% ("load" "peer") [] peer)
(seq
(call peer ("peer" "timestamp") [] t)
(call %init_peer_id% ("callback" "reply") [t]) ;; if used in fetch, then put return value
)
)
;; register peer value
;; register reply callback
code block:
free variables
free functions
new variables
scope?
THEN name, types -- maybe bind free to types
line variants:
func(...)
x <- func(...)
Ability.func(...)
x <- Ability.func(...)
par line
xor line
on peer:
indented lines*
*/
}
}

View File

@ -32,8 +32,8 @@ object PathFinder extends LogSupport {
findPath( findPath(
fromOn, fromOn,
toOn, toOn,
Chain.fromOption(from.currentPeerId), from.currentPeerId,
Chain.fromOption(to.currentPeerId) to.currentPeerId
) )
} }
} }
@ -49,6 +49,10 @@ object PathFinder extends LogSupport {
case (acc, p) if acc.contains(p) => acc.takeWhile(_ != p) :+ p case (acc, p) if acc.contains(p) => acc.takeWhile(_ != p) :+ p
case (acc, p) => acc :+ p case (acc, p) => acc :+ p
} }
trace(s"PEER IDS: $optimized")
trace(s"PREFIX: $prefix")
trace(s"SUFFIX: $suffix")
trace(s"OPTIMIZED WITH PREFIX AND SUFFIX: $optimized")
val noPrefix = skipPrefix(optimized, prefix, optimized) val noPrefix = skipPrefix(optimized, prefix, optimized)
skipSuffix(noPrefix, suffix, noPrefix) skipSuffix(noPrefix, suffix, noPrefix)
} }
@ -56,19 +60,30 @@ object PathFinder extends LogSupport {
def findPath( def findPath(
fromOn: Chain[OnTag], fromOn: Chain[OnTag],
toOn: Chain[OnTag], toOn: Chain[OnTag],
fromPeer: Chain[ValueModel], fromPeer: Option[ValueModel],
toPeer: Chain[ValueModel] toPeer: Option[ValueModel]
): Chain[ValueModel] = { ): Chain[ValueModel] = {
trace(s"FROM ON: $fromOn")
trace(s"TO ON: $toOn")
val (from, to) = skipCommonPrefix(fromOn, toOn) val (from, to) = skipCommonPrefix(fromOn, toOn)
val fromFix = val fromFix =
if (from.isEmpty && fromPeer != toPeer) Chain.fromOption(fromOn.lastOption) else from if (from.isEmpty && fromPeer != toPeer) Chain.fromOption(fromOn.lastOption) else from
val toFix = if (to.isEmpty && fromPeer != toPeer) Chain.fromOption(toOn.lastOption) else to val toFix = if (to.isEmpty && fromPeer != toPeer) Chain.fromOption(toOn.lastOption) else to
val fromTo = fromFix.reverse.flatMap(_.via.reverse) ++ toFix.flatMap(_.via)
val optimized = optimizePath(fromPeer ++ fromTo ++ toPeer, fromPeer, toPeer)
trace("FIND PATH " + fromFix) trace("FIND PATH FROM | " + fromFix)
trace(" -> " + toFix) trace(" TO | " + toFix)
trace(s"$fromPeer $toPeer")
val fromTo = fromFix.reverse.flatMap(_.via.reverse) ++ toFix.flatMap(_.via)
trace(s"FROM TO: $fromTo")
val fromPeerCh = Chain.fromOption(fromPeer)
val toPeerCh = Chain.fromOption(toPeer)
val optimized = optimizePath(fromPeerCh ++ fromTo ++ toPeerCh, fromPeerCh, toPeerCh)
trace(
s"FROM PEER '${fromPeer.map(_.toString).getOrElse("None")}' TO PEER '${toPeer.map(_.toString).getOrElse("None")}'"
)
trace(" Optimized: " + optimized) trace(" Optimized: " + optimized)
optimized optimized
} }

View File

@ -127,27 +127,26 @@ case class RawCursor(tree: NonEmptyList[ChainZipper[FuncOp.Tree]])
} }
def cata[A](wrap: ChainZipper[Cofree[Chain, A]] => Chain[Cofree[Chain, A]])( def cata[A](wrap: ChainZipper[Cofree[Chain, A]] => Chain[Cofree[Chain, A]])(
f: RawCursor => OptionT[Eval, ChainZipper[Cofree[Chain, A]]] folder: RawCursor => OptionT[Eval, ChainZipper[Cofree[Chain, A]]]
): Eval[Chain[Cofree[Chain, A]]] = ): Eval[Chain[Cofree[Chain, A]]] =
f(this).map { case ChainZipper(prev, curr, next) => folder(this).map { case ChainZipper(prev, curr, next) =>
val inner = curr.copy(tail = val tail = Eval.later {
Eval Chain.fromSeq(
.later( toFirstChild
Chain.fromSeq( .map(folderCursor =>
toFirstChild LazyList
.map(fc => .unfold(folderCursor) { _.toNextSibling.map(cursor => cursor -> cursor) }
LazyList .prepended(folderCursor)
.unfold(fc) { _.toNextSibling.map(c => c -> c) }
.prepended(fc)
)
.getOrElse(LazyList.empty)
) )
) .getOrElse(LazyList.empty)
// TODO: this can be parallelized )
.flatMap(_.traverse(_.cata(wrap)(f))) }
.map(_.flatMap(identity)) // TODO: this can be parallelized
.flatMap(addition => curr.tail.map(_ ++ addition)) .flatMap(_.traverse(_.cata(wrap)(folder)))
) .map(_.flatMap(identity))
.flatMap(addition => curr.tail.map(_ ++ addition))
val inner = curr.copy(tail = tail)
wrap(ChainZipper(prev, inner, next)) wrap(ChainZipper(prev, inner, next))
}.getOrElse(Chain.empty).memoize }.getOrElse(Chain.empty).memoize

View File

@ -1,6 +1,7 @@
package aqua.model.func.resolved package aqua.model.func.resolved
import aqua.model.func.Call import aqua.model.func.Call
import aqua.model.topology.Topology.Res
import aqua.model.{LiteralModel, ValueModel} import aqua.model.{LiteralModel, ValueModel}
import cats.Eval import cats.Eval
import cats.data.Chain import cats.data.Chain
@ -8,24 +9,24 @@ import cats.free.Cofree
object MakeRes { object MakeRes {
val nilTail: Eval[Chain[Cofree[Chain, ResolvedOp]]] = Eval.now(Chain.empty) val nilTail: Eval[Chain[Cofree[Chain, ResolvedOp]]] = Eval.now(Chain.empty)
type Cof = Cofree[Chain, ResolvedOp]
def leaf(op: ResolvedOp): Cof = Cofree[Chain, ResolvedOp](op, nilTail)
def next(item: String): Cof = def leaf(op: ResolvedOp): Res = Cofree[Chain, ResolvedOp](op, nilTail)
def next(item: String): Res =
leaf(NextRes(item)) leaf(NextRes(item))
def seq(first: Cof, second: Cof, more: Cof*): Cof = def seq(first: Res, second: Res, more: Res*): Res =
Cofree[Chain, ResolvedOp](SeqRes, Eval.later(first +: second +: Chain.fromSeq(more))) Cofree[Chain, ResolvedOp](SeqRes, Eval.later(first +: second +: Chain.fromSeq(more)))
def par(first: Cof, second: Cof, more: Cof*): Cof = def par(first: Res, second: Res, more: Res*): Res =
Cofree[Chain, ResolvedOp](ParRes, Eval.later(first +: second +: Chain.fromSeq(more))) Cofree[Chain, ResolvedOp](ParRes, Eval.later(first +: second +: Chain.fromSeq(more)))
def xor(first: Cof, second: Cof): Cof = def xor(first: Res, second: Res): Res =
Cofree[Chain, ResolvedOp](XorRes, Eval.later(Chain(first, second))) Cofree[Chain, ResolvedOp](XorRes, Eval.later(Chain(first, second)))
def fold(item: String, iter: ValueModel, body: Cof): Cof = def fold(item: String, iter: ValueModel, body: Res): Res =
Cofree[Chain, ResolvedOp](FoldRes(item, iter), Eval.now(Chain.one(body))) Cofree[Chain, ResolvedOp](FoldRes(item, iter), Eval.now(Chain.one(body)))
def noop(onPeer: ValueModel): Cof = def noop(onPeer: ValueModel): Res =
leaf(CallServiceRes(LiteralModel.quote("op"), "noop", Call(Nil, None), onPeer)) leaf(CallServiceRes(LiteralModel.quote("op"), "noop", Call(Nil, None), onPeer))
} }

View File

@ -1,26 +1,27 @@
package aqua.model.topology package aqua.model.topology
import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.model.func.raw._
import cats.Eval
import cats.data.{Chain, NonEmptyChain, NonEmptyList, OptionT}
import cats.data.Chain.nil
import cats.free.Cofree
import aqua.model.cursor.ChainZipper import aqua.model.cursor.ChainZipper
import aqua.model.func.raw._
import aqua.model.func.resolved._ import aqua.model.func.resolved._
import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.types.{BoxType, ScalarType} import aqua.types.{BoxType, ScalarType}
import wvlet.log.LogSupport import cats.Eval
import cats.data.Chain.nil
import cats.data.{Chain, NonEmptyChain, NonEmptyList, OptionT}
import cats.free.Cofree
import cats.syntax.traverse._ import cats.syntax.traverse._
import wvlet.log.LogSupport
object Topology extends LogSupport { object Topology extends LogSupport {
type Tree = Cofree[Chain, RawTag] type Tree = Cofree[Chain, RawTag]
type Res = Cofree[Chain, ResolvedOp] type Res = Cofree[Chain, ResolvedOp]
def resolve(op: Tree): Res = def resolve(op: Tree): Res = {
val resolved = resolveOnMoves(op).value
Cofree Cofree
.cata[Chain, ResolvedOp, Res](resolveOnMoves(op).value) { .cata[Chain, ResolvedOp, Res](resolved) {
case (SeqRes, children) => case (SeqRes, children) =>
Eval.later( Eval.later {
children.uncons children.uncons
.filter(_._2.isEmpty) .filter(_._2.isEmpty)
.map(_._1) .map(_._1)
@ -33,10 +34,11 @@ object Topology extends LogSupport {
}) })
) )
) )
) }
case (head, children) => Eval.later(Cofree(head, Eval.now(children))) case (head, children) => Eval.later(Cofree(head, Eval.now(children)))
} }
.value .value
}
def wrap(cz: ChainZipper[Res]): Chain[Res] = def wrap(cz: ChainZipper[Res]): Chain[Res] =
Chain.one( Chain.one(
@ -44,60 +46,64 @@ object Topology extends LogSupport {
else cz.current else cz.current
) )
def resolveOnMoves(op: Tree): Eval[Res] = private def rawToResolved(
RawCursor(NonEmptyList.one(ChainZipper.one(op))) currentPeerId: Option[ValueModel]
): PartialFunction[RawTag, ResolvedOp] = {
case SeqTag => SeqRes
case _: OnTag => SeqRes
case MatchMismatchTag(a, b, s) => MatchMismatchRes(a, b, s)
case ForTag(item, iter) => FoldRes(item, iter)
case ParTag | ParTag.Detach => ParRes
case XorTag | XorTag.LeftBiased => XorRes
case NextTag(item) => NextRes(item)
case CallServiceTag(serviceId, funcName, call) =>
CallServiceRes(
serviceId,
funcName,
call,
currentPeerId
.getOrElse(LiteralModel.initPeerId)
)
}
def resolveOnMoves(op: Tree): Eval[Res] = {
val cursor = RawCursor(NonEmptyList.one(ChainZipper.one(op)))
val resolvedCofree = cursor
.cata(wrap) { rc => .cata(wrap) { rc =>
debug(s"<:> $rc") debug(s"<:> $rc")
OptionT[Eval, ChainZipper[Res]]( val resolved = rawToResolved(rc.currentPeerId).lift
({ .apply(rc.tag)
case SeqTag => SeqRes .map(MakeRes.leaf)
case _: OnTag => SeqRes val chainZipperEv = resolved.traverse(cofree =>
case MatchMismatchTag(a, b, s) => MatchMismatchRes(a, b, s) Eval.later {
case ForTag(item, iter) => FoldRes(item, iter) val cz = ChainZipper(
case ParTag | ParTag.Detach => ParRes through(rc.pathFromPrev),
case XorTag | XorTag.LeftBiased => XorRes cofree,
case NextTag(item) => NextRes(item) through(rc.pathToNext)
case CallServiceTag(serviceId, funcName, call) =>
CallServiceRes(
serviceId,
funcName,
call,
rc.currentPeerId
.getOrElse(LiteralModel.initPeerId)
)
}: PartialFunction[RawTag, ResolvedOp]).lift
.apply(rc.tag)
.map(MakeRes.leaf)
.traverse(c =>
Eval.later {
val cz = ChainZipper(
through(rc.pathFromPrev),
c,
through(rc.pathToNext)
)
if (cz.next.nonEmpty || cz.prev.nonEmpty) {
debug(s"Resolved $rc -> $c")
if (cz.prev.nonEmpty)
trace("From prev: " + cz.prev.map(_.head).toList.mkString(" -> "))
if (cz.next.nonEmpty)
trace("To next: " + cz.next.map(_.head).toList.mkString(" -> "))
} else debug(s"EMPTY $rc -> $c")
cz
}
) )
if (cz.next.nonEmpty || cz.prev.nonEmpty) {
debug(s"Resolved $rc -> $cofree")
if (cz.prev.nonEmpty)
trace("From prev: " + cz.prev.map(_.head).toList.mkString(" -> "))
if (cz.next.nonEmpty)
trace("To next: " + cz.next.map(_.head).toList.mkString(" -> "))
} else debug(s"EMPTY $rc -> $cofree")
cz
}
) )
OptionT[Eval, ChainZipper[Res]](chainZipperEv)
}
} resolvedCofree.map(NonEmptyChain.fromChain(_).map(_.uncons)).map {
.map(NonEmptyChain.fromChain(_).map(_.uncons)) case None =>
.map { error("Topology emitted nothing")
case None => Cofree(SeqRes, MakeRes.nilTail)
error("Topology emitted nothing") case Some((el, `nil`)) => el
Cofree(SeqRes, MakeRes.nilTail) case Some((el, tail)) =>
case Some((el, `nil`)) => el warn("Topology emitted many nodes, that's unusual")
case Some((el, tail)) => Cofree(SeqRes, Eval.now(el +: tail))
warn("Topology emitted many nodes, that's unusual") }
Cofree(SeqRes, Eval.now(el +: tail)) }
}
// Walks through peer IDs, doing a noop function on each // Walks through peer IDs, doing a noop function on each
// If same IDs are found in a row, does noop only once // If same IDs are found in a row, does noop only once

View File

@ -1,13 +1,11 @@
package aqua.model.transform package aqua.model.transform
import aqua.model.{ValueModel, VarModel}
import aqua.model.func.{ArgDef, ArgsCall, ArgsDef, Call, FuncCallable}
import aqua.model.func.raw.{FuncOp, FuncOps} import aqua.model.func.raw.{FuncOp, FuncOps}
import aqua.model.func._
import aqua.model.{ValueModel, VarModel}
import aqua.types.ArrowType import aqua.types.ArrowType
import cats.Eval import cats.Eval
import cats.syntax.apply._ import cats.syntax.apply._
import cats.syntax.functor._
import wvlet.log.Logger
case class ResolveFunc( case class ResolveFunc(
transform: FuncOp => FuncOp, transform: FuncOp => FuncOp,
@ -17,9 +15,6 @@ case class ResolveFunc(
arrowCallbackPrefix: String = "init_peer_callable_" arrowCallbackPrefix: String = "init_peer_callable_"
) { ) {
private val logger = Logger.of[ResolveFunc]
import logger._
def returnCallback(retModel: ValueModel): FuncOp = def returnCallback(retModel: ValueModel): FuncOp =
callback( callback(
respFuncName, respFuncName,

View File

@ -323,7 +323,6 @@ class TopologySpec extends AnyFlatSpec with Matchers {
} }
"topology resolver" should "get back to init peer after a long chain" in { "topology resolver" should "get back to init peer after a long chain" in {
val init = on( val init = on(
initPeer, initPeer,
relay :: Nil, relay :: Nil,

View File

@ -45,6 +45,7 @@ object Token {
val `co`: P[Unit] = P.string("co") val `co`: P[Unit] = P.string("co")
val `:` : P[Unit] = P.char(':') val `:` : P[Unit] = P.char(':')
val ` : ` : P[Unit] = P.char(':').surroundedBy(` `.?) val ` : ` : P[Unit] = P.char(':').surroundedBy(` `.?)
val `anum_*` : P[Unit] = P.charsWhile(anum_).void
val `name`: P[String] = (P.charIn(az) ~ P.charsWhile(anum_).?).string val `name`: P[String] = (P.charIn(az) ~ P.charsWhile(anum_).?).string
@ -55,7 +56,7 @@ object Token {
val `--` : P[Unit] = ` `.?.with1 *> P.string("--") <* ` `.? val `--` : P[Unit] = ` `.?.with1 *> P.string("--") <* ` `.?
val ` \n` : P[Unit] = val ` \n` : P[Unit] =
(` `.?.void *> (`--` *> P.charsWhile(_ != '\n')).?.void).with1 *> `\n` (` `.?.void *> (`--` *> P.charsWhile0(_ != '\n')).?.void).with1 *> `\n`
val ` \n+` : P[Unit] = P.repAs[Unit, Unit](` \n`.backtrack, 1)(Accumulator0.unitAccumulator0) val ` \n+` : P[Unit] = P.repAs[Unit, Unit](` \n`.backtrack, 1)(Accumulator0.unitAccumulator0)
val ` : \n+` : P[Unit] = ` `.?.with1 *> `:` *> ` \n+` val ` : \n+` : P[Unit] = ` `.?.with1 *> `:` *> ` \n+`

View File

@ -4,10 +4,10 @@ import aqua.parser.lexer.Token._
import aqua.parser.lift.LiftParser import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser._ import aqua.parser.lift.LiftParser._
import aqua.types.LiteralType import aqua.types.LiteralType
import cats.{Comonad, Functor}
import cats.parse.{Numbers, Parser => P} import cats.parse.{Numbers, Parser => P}
import cats.syntax.functor._
import cats.syntax.comonad._ import cats.syntax.comonad._
import cats.syntax.functor._
import cats.{Comonad, Functor}
sealed trait Value[F[_]] extends Token[F] sealed trait Value[F[_]] extends Token[F]
@ -32,7 +32,7 @@ object Value {
P.oneOf( P.oneOf(
("true" :: "false" :: Nil) ("true" :: "false" :: Nil)
.map(t P.string(t).lift.map(fu => Literal(fu.as(t), LiteralType.bool))) .map(t P.string(t).lift.map(fu => Literal(fu.as(t), LiteralType.bool)))
) ) <* P.not(`anum_*`)
def initPeerId[F[_]: LiftParser: Comonad]: P[Literal[F]] = def initPeerId[F[_]: LiftParser: Comonad]: P[Literal[F]] =
`%init_peer_id%`.string.lift.map(Literal(_, LiteralType.string)) `%init_peer_id%`.string.lift.map(Literal(_, LiteralType.string))
@ -57,7 +57,7 @@ object Value {
.map(Literal(_, LiteralType.string)) .map(Literal(_, LiteralType.string))
def literal[F[_]: LiftParser: Comonad]: P[Literal[F]] = def literal[F[_]: LiftParser: Comonad]: P[Literal[F]] =
P.oneOf(bool :: float.backtrack :: num :: string :: Nil) P.oneOf(bool.backtrack :: float.backtrack :: num.backtrack :: string :: Nil)
def `value`[F[_]: LiftParser: Comonad]: P[Value[F]] = def `value`[F[_]: LiftParser: Comonad]: P[Value[F]] =
P.oneOf(literal.backtrack :: initPeerId.backtrack :: varLambda :: Nil) P.oneOf(literal.backtrack :: initPeerId.backtrack :: varLambda :: Nil)

View File

@ -2,8 +2,7 @@ package aqua.parser
import aqua.AquaSpec import aqua.AquaSpec
import aqua.parser.expr.ArrowTypeExpr import aqua.parser.expr.ArrowTypeExpr
import aqua.types.ScalarType.string import aqua.types.ScalarType.{string, u32}
import aqua.types.ScalarType.u32
import cats.Id import cats.Id
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
@ -21,6 +20,10 @@ class ArrowTypeExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
ArrowTypeExpr[Id]("onIn", toArrowType(List("Custom"), Some("Custom2"))) ArrowTypeExpr[Id]("onIn", toArrowType(List("Custom"), Some("Custom2")))
) )
parseArrow("onIn(a: Custom, b: Custom2)") should be(
ArrowTypeExpr[Id]("onIn", toArrowType(List("Custom", "Custom2"), None))
)
parseArrow("onIn: Custom, string, u32, Custom3 -> Custom2") should be( parseArrow("onIn: Custom, string, u32, Custom3 -> Custom2") should be(
ArrowTypeExpr[Id]( ArrowTypeExpr[Id](
"onIn", "onIn",

View File

@ -1,12 +1,12 @@
package aqua.parser.lexer package aqua.parser.lexer
import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import aqua.parser.lift.LiftParser.Implicits.idLiftParser import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.types.ScalarType import aqua.types.ScalarType
import aqua.types.ScalarType.u32 import aqua.types.ScalarType.u32
import cats.Id import cats.Id
import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import scala.language.implicitConversions import scala.language.implicitConversions
@ -27,6 +27,11 @@ class TypeTokenSpec extends AnyFlatSpec with Matchers with EitherValues {
ArrowTypeToken.`arrowdef`.parseAll("A -> B").right.value should be( ArrowTypeToken.`arrowdef`.parseAll("A -> B").right.value should be(
ArrowTypeToken[Id]((), CustomTypeToken[Id]("A") :: Nil, Some(CustomTypeToken[Id]("B"))) ArrowTypeToken[Id]((), CustomTypeToken[Id]("A") :: Nil, Some(CustomTypeToken[Id]("B")))
) )
ArrowTypeToken.`arrowWithNames`.parseAll("(a: A) -> B").right.value should be(
ArrowTypeToken[Id]((), CustomTypeToken[Id]("A") :: Nil, Some(CustomTypeToken[Id]("B")))
)
ArrowTypeToken.`arrowdef`.parseAll("u32 -> Boo").right.value should be( ArrowTypeToken.`arrowdef`.parseAll("u32 -> Boo").right.value should be(
ArrowTypeToken[Id]((), (u32: BasicTypeToken[Id]) :: Nil, Some(CustomTypeToken[Id]("Boo"))) ArrowTypeToken[Id]((), (u32: BasicTypeToken[Id]) :: Nil, Some(CustomTypeToken[Id]("Boo")))
) )