mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
feat(compiler): Add equality ops [fixes LNG-217] (#820)
* Add eq ops * Fixed if * Fix return * Fix tests * Add parser tests * Fix types, add semantics tests * Add integration tests * Add integration test * Remove comment
This commit is contained in:
parent
ef4b0143ac
commit
a5e9354aeb
@ -33,7 +33,10 @@ class AquaParser[F[_], E, I, S[_]: Comonad](
|
||||
sources.sources.map(
|
||||
_.leftMap(_.map[Err](SourcesErr(_))).andThen(_.map { case (i, s) =>
|
||||
parser(i)(s)
|
||||
.bimap(_.map[Err](ParserErr(_)), ast => Chain.one(i -> ast))
|
||||
.bimap(
|
||||
_.map[Err](ParserErr(_)),
|
||||
ast => Chain.one(i -> ast)
|
||||
)
|
||||
}.foldA)
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
aqua Bool
|
||||
|
||||
export main, Effector
|
||||
export main, compareStreams, compareStructs, Effector
|
||||
|
||||
service Effector("effector"):
|
||||
effect(name: string) -> bool
|
||||
@ -13,6 +13,27 @@ func bar(x: i8) -> i8:
|
||||
y = x - 1
|
||||
<- y
|
||||
|
||||
func compareStreams(peer: string) -> bool:
|
||||
s1: *i8
|
||||
s2: *i8
|
||||
|
||||
on peer:
|
||||
s1 <<- bar(43)
|
||||
s2 <<- bar(43)
|
||||
|
||||
<- s1 == s2
|
||||
|
||||
data Struct:
|
||||
field: i8
|
||||
str: string
|
||||
|
||||
func compareStructs(peer: string, str: string) -> bool:
|
||||
on peer:
|
||||
st1 = Struct(field = 42, str = str)
|
||||
st2 = Struct(field = 24, str = str)
|
||||
|
||||
<- st1 == st2
|
||||
|
||||
func main(peer: string) -> []bool:
|
||||
res: *bool
|
||||
|
||||
@ -26,6 +47,8 @@ func main(peer: string) -> []bool:
|
||||
res <<- foo(4) && bar(2) < 2 -- false
|
||||
res <<- !foo(10) && !!true -- true
|
||||
res <<- !(bar(2) < 1) || !!(a < 2) -- true
|
||||
res <<- bar(42) == bar(40 + 2) && foo(10) -- false
|
||||
res <<- bar(2) < 5 || bar(2) != 1 -- true
|
||||
|
||||
-- Effector is only registered on init_peer
|
||||
res <<- true || Effector.effect("impossible") -- true
|
||||
@ -36,5 +59,7 @@ func main(peer: string) -> []bool:
|
||||
res <<- Effector.effect("true") && false -- false
|
||||
res <<- !foo(10) || Effector.effect("impossible") -- true
|
||||
res <<- !(1 < 2) && !Effector.effect("impossible") -- false
|
||||
res <<- !(bar(5) == 5) || Effector.effect("impossible") -- true
|
||||
res <<- bar(5) != 4 && Effector.effect("impossible") -- false
|
||||
|
||||
<- res
|
@ -32,7 +32,7 @@ import { registerHandlers, returnNull, returnOptionalCall, useOptionalCall } fro
|
||||
import { viaArrCall, viaOptCall, viaOptNullCall, viaStreamCall } from '../examples/viaCall.js';
|
||||
import { nestedFuncsCall } from '../examples/nestedFuncsCall.js';
|
||||
import { assignmentCall } from '../examples/assignment.js';
|
||||
import { boolAlgebraCall } from '../examples/boolAlgebra.js';
|
||||
import { boolAlgebraCall, compareStreamsCall, compareStructsCall } from '../examples/boolAlgebra.js';
|
||||
import { tryCatchCall } from '../examples/tryCatchCall.js';
|
||||
import { tryOtherwiseCall } from '../examples/tryOtherwiseCall.js';
|
||||
import { coCall } from '../examples/coCall.js';
|
||||
@ -405,6 +405,10 @@ describe('Testing examples', () => {
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
@ -416,6 +420,16 @@ describe('Testing examples', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('boolAlgebra.aqua compareStreams', async () => {
|
||||
let result = await compareStreamsCall(relayPeerId1);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('boolAlgebra.aqua compareStructs', async () => {
|
||||
let result = await compareStructsCall(relayPeerId1, 'struct');
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('join.aqua local', async () => {
|
||||
let joinLocalCallResult = await joinIdxLocalCall(relayPeerId1);
|
||||
expect(joinLocalCallResult.length).toBeGreaterThanOrEqual(2);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { main, registerEffector } from '../compiled/examples/boolAlgebra.js';
|
||||
import { main, compareStreams, compareStructs, registerEffector } from '../compiled/examples/boolAlgebra.js';
|
||||
|
||||
export async function boolAlgebraCall(relay: string): Promise<boolean[]> {
|
||||
registerEffector({
|
||||
@ -10,3 +10,11 @@ export async function boolAlgebraCall(relay: string): Promise<boolean[]> {
|
||||
|
||||
return await main(relay);
|
||||
}
|
||||
|
||||
export async function compareStreamsCall(relay: string): Promise<boolean> {
|
||||
return await compareStreams(relay);
|
||||
}
|
||||
|
||||
export async function compareStructsCall(relay: string, str: string): Promise<boolean> {
|
||||
return await compareStructs(relay, str);
|
||||
}
|
||||
|
@ -24,6 +24,12 @@ private[inline] case class Inline(
|
||||
mergeMode: MergeMode = ParMode
|
||||
) {
|
||||
|
||||
def append(tree: Option[OpModel.Tree]): Inline =
|
||||
tree match {
|
||||
case None => this
|
||||
case Some(tree) => copy(predo = predo :+ tree)
|
||||
}
|
||||
|
||||
def desugar: Inline = {
|
||||
val desugaredPredo = predo match {
|
||||
case Chain.nil | _ ==: Chain.nil => predo
|
||||
|
@ -32,8 +32,8 @@ object MakeStructRawInliner extends RawInliner[MakeStructRaw] {
|
||||
result: VarModel
|
||||
): State[S, OpModel.Tree] = {
|
||||
fields.toSortedMap.toList.flatMap { case (name, value) =>
|
||||
LiteralModel.fromRaw(LiteralRaw.quote(name)) :: value :: Nil
|
||||
}.map(TagInliner.canonicalizeIfStream(_, None)).sequence.map { argsWithOps =>
|
||||
LiteralModel.quote(name) :: value :: Nil
|
||||
}.traverse(TagInliner.canonicalizeIfStream(_)).map { argsWithOps =>
|
||||
val (args, ops) = argsWithOps.unzip
|
||||
val createOp =
|
||||
CallServiceModel(
|
||||
@ -42,8 +42,7 @@ object MakeStructRawInliner extends RawInliner[MakeStructRaw] {
|
||||
args,
|
||||
result
|
||||
).leaf
|
||||
SeqModel.wrap((ops.flatten :+ createOp): _*)
|
||||
|
||||
SeqModel.wrap(ops.flatten :+ createOp)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,12 @@ import aqua.model.inline.state.{Arrows, Exports, Mangler}
|
||||
import aqua.model.*
|
||||
import aqua.model.inline.RawValueInliner.collectionToModel
|
||||
import aqua.model.inline.raw.CallArrowRawInliner
|
||||
import aqua.raw.value.ApplyBinaryOpRaw.Op as BinOp
|
||||
import aqua.raw.ops.*
|
||||
import aqua.raw.value.*
|
||||
import aqua.types.{BoxType, CanonStreamType, StreamType}
|
||||
import aqua.model.inline.Inline.parDesugarPrefixOpt
|
||||
|
||||
import cats.syntax.traverse.*
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.flatMap.*
|
||||
@ -18,7 +21,6 @@ import cats.data.{Chain, State, StateT}
|
||||
import cats.syntax.show.*
|
||||
import cats.syntax.bifunctor.*
|
||||
import scribe.{log, Logging}
|
||||
import aqua.model.inline.Inline.parDesugarPrefixOpt
|
||||
|
||||
/**
|
||||
* [[TagInliner]] prepares a [[RawTag]] for futher processing by converting [[ValueRaw]]s into [[ValueModel]]s.
|
||||
@ -106,15 +108,21 @@ object TagInliner extends Logging {
|
||||
|
||||
def canonicalizeIfStream[S: Mangler](
|
||||
vm: ValueModel,
|
||||
ops: Option[OpModel.Tree]
|
||||
ops: Option[OpModel.Tree] = None
|
||||
): State[S, (ValueModel, Option[OpModel.Tree])] = {
|
||||
vm match {
|
||||
case VarModel(name, StreamType(el), l) =>
|
||||
val canonName = name + "_canon"
|
||||
Mangler[S].findAndForbidName(canonName).map { n =>
|
||||
val canon = VarModel(n, CanonStreamType(el), l)
|
||||
val canonModel = CanonicalizeModel(vm, CallModel.Export(canon.name, canon.`type`)).leaf
|
||||
canon -> combineOpsWithSeq(ops, Option(canonModel))
|
||||
val canonModel = CanonicalizeModel(
|
||||
operand = vm,
|
||||
exportTo = CallModel.Export(
|
||||
canon.name,
|
||||
canon.`type`
|
||||
)
|
||||
).leaf
|
||||
canon -> combineOpsWithSeq(ops, canonModel.some)
|
||||
}
|
||||
case _ => State.pure(vm -> ops)
|
||||
}
|
||||
@ -157,7 +165,7 @@ object TagInliner extends Logging {
|
||||
val apOp = FlattenModel(canonV, apN).leaf
|
||||
(
|
||||
apV,
|
||||
combineOpsWithSeq(op, Option(apOp))
|
||||
combineOpsWithSeq(op, apOp.some)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -202,12 +210,37 @@ object TagInliner extends Logging {
|
||||
prefix = parDesugarPrefix(viaF.prependedAll(pif))
|
||||
)
|
||||
|
||||
case IfTag(leftRaw, rightRaw, shouldMatch) =>
|
||||
case IfTag(valueRaw) =>
|
||||
(valueRaw match {
|
||||
// Optimize in case last operation is equality check
|
||||
case ApplyBinaryOpRaw(op @ (BinOp.Eq | BinOp.Neq), left, right) =>
|
||||
(
|
||||
valueToModel(leftRaw) >>= canonicalizeIfStream.tupled,
|
||||
valueToModel(rightRaw) >>= canonicalizeIfStream.tupled
|
||||
).mapN { case ((leftModel, leftPrefix), (rightModel, rightPrefix)) =>
|
||||
val prefix = parDesugarPrefixOpt(leftPrefix, rightPrefix)
|
||||
valueToModel(left) >>= canonicalizeIfStream,
|
||||
valueToModel(right) >>= canonicalizeIfStream
|
||||
).mapN { case ((lmodel, lprefix), (rmodel, rprefix)) =>
|
||||
val prefix = parDesugarPrefixOpt(lprefix, rprefix)
|
||||
val matchModel = MatchMismatchModel(
|
||||
left = lmodel,
|
||||
right = rmodel,
|
||||
shouldMatch = op match {
|
||||
case BinOp.Eq => true
|
||||
case BinOp.Neq => false
|
||||
}
|
||||
)
|
||||
|
||||
(prefix, matchModel)
|
||||
}
|
||||
case _ =>
|
||||
valueToModel(valueRaw).map { case (valueModel, prefix) =>
|
||||
val matchModel = MatchMismatchModel(
|
||||
left = valueModel,
|
||||
right = LiteralModel.bool(true),
|
||||
shouldMatch = true
|
||||
)
|
||||
|
||||
(prefix, matchModel)
|
||||
}
|
||||
}).map { case (prefix, matchModel) =>
|
||||
val toModel = (children: Chain[OpModel.Tree]) =>
|
||||
XorModel.wrap(
|
||||
children.uncons.map { case (ifBody, elseBody) =>
|
||||
@ -227,11 +260,7 @@ object TagInliner extends Logging {
|
||||
)
|
||||
else elseBodyFiltered
|
||||
|
||||
MatchMismatchModel(
|
||||
leftModel,
|
||||
rightModel,
|
||||
shouldMatch
|
||||
).wrap(ifBody) +: elseBodyAugmented
|
||||
matchModel.wrap(ifBody) +: elseBodyAugmented
|
||||
}.getOrElse(children)
|
||||
)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package aqua.model.inline.raw
|
||||
|
||||
import aqua.model.*
|
||||
import aqua.model.inline.raw.RawInliner
|
||||
import aqua.model.inline.TagInliner
|
||||
import aqua.model.inline.state.{Arrows, Exports, Mangler}
|
||||
import aqua.raw.value.{AbilityRaw, LiteralRaw, MakeStructRaw}
|
||||
import cats.data.{NonEmptyList, NonEmptyMap, State}
|
||||
@ -10,6 +11,7 @@ import aqua.model.inline.RawValueInliner.{unfold, valueToModel}
|
||||
import aqua.types.{ArrowType, ScalarType}
|
||||
import aqua.raw.value.ApplyBinaryOpRaw
|
||||
import aqua.raw.value.ApplyBinaryOpRaw.Op.*
|
||||
import aqua.model.inline.Inline.MergeMode
|
||||
|
||||
import cats.data.Chain
|
||||
import cats.syntax.traverse.*
|
||||
@ -22,41 +24,130 @@ import cats.syntax.applicative.*
|
||||
|
||||
object ApplyBinaryOpRawInliner extends RawInliner[ApplyBinaryOpRaw] {
|
||||
|
||||
private type BoolOp = And.type | Or.type
|
||||
private type EqOp = Eq.type | Neq.type
|
||||
|
||||
override def apply[S: Mangler: Exports: Arrows](
|
||||
raw: ApplyBinaryOpRaw,
|
||||
propertiesAllowed: Boolean
|
||||
): State[S, (ValueModel, Inline)] = for {
|
||||
left <- unfold(raw.left)
|
||||
left <- unfold(raw.left, propertiesAllowed)
|
||||
(lmodel, linline) = left
|
||||
right <- unfold(raw.right)
|
||||
right <- unfold(raw.right, propertiesAllowed)
|
||||
(rmodel, rinline) = right
|
||||
|
||||
result <- (lmodel, rmodel) match {
|
||||
result <- raw.op match {
|
||||
case op @ (And | Or) => inlineBoolOp(lmodel, rmodel, linline, rinline, op)
|
||||
case op @ (Eq | Neq) =>
|
||||
for {
|
||||
// Canonicalize stream operands before comparison
|
||||
leftStream <- TagInliner.canonicalizeIfStream(lmodel)
|
||||
(lmodelStream, linlineStream) = leftStream.map(linline.append)
|
||||
rightStream <- TagInliner.canonicalizeIfStream(rmodel)
|
||||
(rmodelStream, rinlineStream) = rightStream.map(rinline.append)
|
||||
result <- inlineEqOp(lmodelStream, rmodelStream, linlineStream, rinlineStream, op)
|
||||
} yield result
|
||||
}
|
||||
} yield result
|
||||
|
||||
private def inlineEqOp[S: Mangler: Exports: Arrows](
|
||||
lmodel: ValueModel,
|
||||
rmodel: ValueModel,
|
||||
linline: Inline,
|
||||
rinline: Inline,
|
||||
op: EqOp
|
||||
): State[S, (ValueModel, Inline)] = (lmodel, rmodel) match {
|
||||
// Optimize in case compared values are literals
|
||||
// Semantics should check that types are comparable
|
||||
case (LiteralModel(lvalue, _), LiteralModel(rvalue, _)) =>
|
||||
(
|
||||
LiteralModel.bool {
|
||||
op match {
|
||||
case Eq => lvalue == rvalue
|
||||
case Neq => lvalue != rvalue
|
||||
}
|
||||
},
|
||||
linline.mergeWith(rinline, MergeMode.ParMode)
|
||||
).pure[State[S, *]]
|
||||
case _ => fullInlineEqOp(lmodel, rmodel, linline, rinline, op)
|
||||
}
|
||||
|
||||
private def fullInlineEqOp[S: Mangler: Exports: Arrows](
|
||||
lmodel: ValueModel,
|
||||
rmodel: ValueModel,
|
||||
linline: Inline,
|
||||
rinline: Inline,
|
||||
op: EqOp
|
||||
): State[S, (ValueModel, Inline)] = {
|
||||
val (name, shouldMatch) = op match {
|
||||
case Eq => ("eq", true)
|
||||
case Neq => ("neq", false)
|
||||
}
|
||||
|
||||
/**
|
||||
* (seq
|
||||
* <left-inline>
|
||||
* (seq
|
||||
* <right-inline>
|
||||
* (xor
|
||||
* (match/mismatch <left-res> <right-res>
|
||||
* (ap true <res-name>)
|
||||
* )
|
||||
* (ap false <res-name>)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
val predo = (resName: String) =>
|
||||
SeqModel.wrap(
|
||||
linline.predo ++ rinline.predo :+ XorModel.wrap(
|
||||
MatchMismatchModel(lmodel, rmodel, shouldMatch).wrap(
|
||||
FlattenModel(
|
||||
LiteralModel.bool(true),
|
||||
resName
|
||||
).leaf
|
||||
),
|
||||
FlattenModel(
|
||||
LiteralModel.bool(false),
|
||||
resName
|
||||
).leaf
|
||||
)
|
||||
)
|
||||
|
||||
result(name, predo)
|
||||
}
|
||||
|
||||
private def inlineBoolOp[S: Mangler: Exports: Arrows](
|
||||
lmodel: ValueModel,
|
||||
rmodel: ValueModel,
|
||||
linline: Inline,
|
||||
rinline: Inline,
|
||||
op: BoolOp
|
||||
): State[S, (ValueModel, Inline)] = (lmodel, rmodel) match {
|
||||
// Optimize in case of left value is known at compile time
|
||||
case (LiteralModel.Bool(lvalue), _) =>
|
||||
(raw.op match {
|
||||
(op match {
|
||||
case And if !lvalue => (LiteralModel.bool(false), linline)
|
||||
case Or if lvalue => (LiteralModel.bool(true), linline)
|
||||
case _ => (rmodel, Inline(linline.predo ++ rinline.predo))
|
||||
}).pure[State[S, *]]
|
||||
// Optimize in case of right value is known at compile time and it has no computation
|
||||
case (_, LiteralModel.Bool(rvalue)) if rinline.predo.isEmpty =>
|
||||
(raw.op match {
|
||||
(op match {
|
||||
case And if !rvalue => (LiteralModel.bool(false), linline)
|
||||
case Or if rvalue => (LiteralModel.bool(true), linline)
|
||||
case _ => (lmodel, linline)
|
||||
}).pure[State[S, *]]
|
||||
// Produce unoptimized inline
|
||||
case _ => fullInline(lmodel, rmodel, linline, rinline, raw.op)
|
||||
case _ => fullInlineBoolOp(lmodel, rmodel, linline, rinline, op)
|
||||
}
|
||||
} yield result
|
||||
|
||||
private def fullInline[S: Mangler: Exports: Arrows](
|
||||
private def fullInlineBoolOp[S: Mangler: Exports: Arrows](
|
||||
lmodel: ValueModel,
|
||||
rmodel: ValueModel,
|
||||
linline: Inline,
|
||||
rinline: Inline,
|
||||
op: ApplyBinaryOpRaw.Op
|
||||
op: BoolOp
|
||||
): State[S, (ValueModel, Inline)] = {
|
||||
val (name, compareWith) = op match {
|
||||
case And => ("and", false)
|
||||
@ -99,6 +190,13 @@ object ApplyBinaryOpRawInliner extends RawInliner[ApplyBinaryOpRaw] {
|
||||
)
|
||||
)
|
||||
|
||||
result(name, predo)
|
||||
}
|
||||
|
||||
private def result[S: Mangler](
|
||||
name: String,
|
||||
predo: String => OpModel.Tree
|
||||
): State[S, (ValueModel, Inline)] =
|
||||
Mangler[S]
|
||||
.findAndForbidName(name)
|
||||
.map(resName =>
|
||||
@ -107,5 +205,4 @@ object ApplyBinaryOpRawInliner extends RawInliner[ApplyBinaryOpRaw] {
|
||||
Inline(Chain.one(predo(resName)))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,8 @@ object ApplyIntoCopyRawInliner extends Logging {
|
||||
result: VarModel
|
||||
): State[S, OpModel.Tree] = {
|
||||
fields.toSortedMap.toList.flatMap { case (name, value) =>
|
||||
LiteralModel.fromRaw(LiteralRaw.quote(name)) :: value :: Nil
|
||||
}.map(TagInliner.canonicalizeIfStream(_, None)).sequence.map { argsWithOps =>
|
||||
LiteralModel.quote(name) :: value :: Nil
|
||||
}.traverse(TagInliner.canonicalizeIfStream(_)).map { argsWithOps =>
|
||||
val (args, ops) = argsWithOps.unzip
|
||||
val copyOp = CallServiceModel(
|
||||
"json",
|
||||
@ -43,7 +43,7 @@ object ApplyIntoCopyRawInliner extends Logging {
|
||||
value +: args,
|
||||
result
|
||||
).leaf
|
||||
SeqModel.wrap((ops.flatten :+ copyOp): _*)
|
||||
SeqModel.wrap(ops.flatten :+ copyOp)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -81,9 +81,9 @@ case object ParTag extends ParGroupTag {
|
||||
case object Par extends GroupTag
|
||||
}
|
||||
|
||||
case class IfTag(left: ValueRaw, right: ValueRaw, equal: Boolean) extends GroupTag {
|
||||
case class IfTag(value: ValueRaw) extends GroupTag {
|
||||
override def mapValues(f: ValueRaw => ValueRaw): RawTag =
|
||||
IfTag(left.map(f), right.map(_.map(f)), equal)
|
||||
IfTag(value.map(f))
|
||||
}
|
||||
|
||||
object IfTag {
|
||||
|
@ -202,6 +202,9 @@ object ApplyBinaryOpRaw {
|
||||
enum Op {
|
||||
case And
|
||||
case Or
|
||||
|
||||
case Eq
|
||||
case Neq
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import aqua.model.transform.ModelBuilder
|
||||
import aqua.model.{CallModel, OnModel, SeqModel}
|
||||
import aqua.model.transform.cursor.ChainZipper
|
||||
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
|
||||
import aqua.raw.ops.{Call, FuncOp, OnTag, ReturnTag}
|
||||
import aqua.raw.ops.{Call, FuncOp, OnTag}
|
||||
import aqua.raw.value.{ValueRaw, VarRaw}
|
||||
import aqua.types.{ArrayType, ScalarType}
|
||||
import cats.data.{Chain, NonEmptyList}
|
||||
|
@ -3,7 +3,7 @@ package aqua.parser.expr.func
|
||||
import aqua.parser.Expr
|
||||
import aqua.parser.expr.func.{ForExpr, IfExpr}
|
||||
import aqua.parser.lexer.Token.*
|
||||
import aqua.parser.lexer.{EqOp, LiteralToken, ValueToken}
|
||||
import aqua.parser.lexer.{LiteralToken, ValueToken}
|
||||
import aqua.parser.lift.LiftParser
|
||||
import aqua.types.LiteralType
|
||||
import cats.parse.Parser as P
|
||||
@ -11,11 +11,10 @@ import cats.{~>, Comonad}
|
||||
import aqua.parser.lift.Span
|
||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||
|
||||
case class IfExpr[F[_]](left: ValueToken[F], eqOp: EqOp[F], right: ValueToken[F])
|
||||
extends Expr[F](IfExpr, eqOp) {
|
||||
case class IfExpr[F[_]](value: ValueToken[F]) extends Expr[F](IfExpr, value) {
|
||||
|
||||
override def mapK[K[_]: Comonad](fk: F ~> K): IfExpr[K] =
|
||||
copy(left.mapK(fk), eqOp.mapK(fk), right.mapK(fk))
|
||||
copy(value.mapK(fk))
|
||||
}
|
||||
|
||||
object IfExpr extends Expr.AndIndented {
|
||||
@ -24,10 +23,5 @@ object IfExpr extends Expr.AndIndented {
|
||||
override def validChildren: List[Expr.Lexem] = ForExpr.validChildren
|
||||
|
||||
override val p: P[IfExpr[Span.S]] =
|
||||
(`if` *> ` ` *> ValueToken.`value` ~ (` ` *> EqOp.p ~ (` ` *> ValueToken.`value`)).?).map {
|
||||
case (left, Some((e, right))) =>
|
||||
IfExpr(left, e, right)
|
||||
case (left, None) =>
|
||||
IfExpr(left, EqOp(left.as(true)), LiteralToken(left.as("true"), LiteralType.bool))
|
||||
}
|
||||
(`if` *> ` ` *> ValueToken.`value`).map(IfExpr(_))
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
package aqua.parser.lexer
|
||||
|
||||
import aqua.parser.lift.LiftParser
|
||||
import cats.Comonad
|
||||
import cats.syntax.functor.*
|
||||
import Token.*
|
||||
import cats.parse.Parser as P
|
||||
import LiftParser.*
|
||||
import cats.syntax.comonad.*
|
||||
import cats.~>
|
||||
import aqua.parser.lift.Span
|
||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||
|
||||
case class EqOp[F[_]: Comonad](eq: F[Boolean]) extends Token[F] {
|
||||
override def as[T](v: T): F[T] = eq.as(v)
|
||||
|
||||
override def mapK[K[_]: Comonad](fk: F ~> K): EqOp[K] =
|
||||
copy(fk(eq))
|
||||
|
||||
def value: Boolean = eq.extract
|
||||
}
|
||||
|
||||
object EqOp {
|
||||
|
||||
val p: P[EqOp[Span.S]] =
|
||||
(`eqs`.as(true).lift | `neq`.as(false).lift).map(EqOp(_))
|
||||
}
|
@ -176,6 +176,10 @@ object InfixToken {
|
||||
case Lt extends CmpOp("<")
|
||||
case Lte extends CmpOp("<=")
|
||||
|
||||
enum EqOp(val symbol: String):
|
||||
case Eq extends EqOp("==")
|
||||
case Neq extends EqOp("!=")
|
||||
|
||||
enum Op(val symbol: String):
|
||||
/**
|
||||
* Scala3 does not support nested enums with fields
|
||||
@ -183,6 +187,7 @@ object InfixToken {
|
||||
*/
|
||||
case Math(mathOp: MathOp) extends Op(mathOp.symbol)
|
||||
case Cmp(cmpOp: CmpOp) extends Op(cmpOp.symbol)
|
||||
case Eq(eqOp: EqOp) extends Op(eqOp.symbol)
|
||||
case Bool(boolOp: BoolOp) extends Op(boolOp.symbol)
|
||||
|
||||
def p: P[Unit] = P.string(symbol)
|
||||
@ -204,6 +209,11 @@ object InfixToken {
|
||||
|
||||
val cmp = CmpOp.values.map(Cmp(_)).toList
|
||||
|
||||
val Equ = Eq(EqOp.Eq)
|
||||
val Neq = Eq(EqOp.Neq)
|
||||
|
||||
val eq = EqOp.values.map(Eq(_)).toList
|
||||
|
||||
val And = Bool(BoolOp.And)
|
||||
val Or = Bool(BoolOp.Or)
|
||||
|
||||
@ -260,8 +270,11 @@ object InfixToken {
|
||||
Op.Gte :: Op.Lte :: Op.Gt :: Op.Lt :: Nil
|
||||
)
|
||||
|
||||
private val eq: P[ValueToken[Span.S]] =
|
||||
infixParserLeft(compare, Op.Equ :: Op.Neq :: Nil)
|
||||
|
||||
private val and: P[ValueToken[Span.S]] =
|
||||
infixParserLeft(compare, Op.And :: Nil)
|
||||
infixParserLeft(eq, Op.And :: Nil)
|
||||
|
||||
private val or: P[ValueToken[Span.S]] =
|
||||
infixParserLeft(and, Op.Or :: Nil)
|
||||
@ -322,7 +335,11 @@ object InfixToken {
|
||||
*
|
||||
* -- Logical AND is left associative.
|
||||
* andExpr
|
||||
* -> cmpExpr AND_OP cmpExpr
|
||||
* -> eqExpr AND_OP eqExpr
|
||||
*
|
||||
* -- Equality is left associative.
|
||||
* eqExpr
|
||||
* -> cmpExpr EQ_OP cmpExpr
|
||||
*
|
||||
* -- Comparison isn't an associative operation so it's not a recursive definition.
|
||||
* cmpExpr
|
||||
|
@ -2,19 +2,11 @@ package aqua
|
||||
|
||||
import aqua.AquaSpec.spanToId
|
||||
import aqua.parser.expr.*
|
||||
import aqua.parser.expr.func.{
|
||||
AbilityIdExpr,
|
||||
ArrowExpr,
|
||||
AssignmentExpr,
|
||||
CallArrowExpr,
|
||||
ClosureExpr,
|
||||
ElseOtherwiseExpr,
|
||||
ForExpr,
|
||||
IfExpr,
|
||||
OnExpr,
|
||||
PushToStreamExpr,
|
||||
ReturnExpr
|
||||
}
|
||||
import aqua.parser.expr.func.*
|
||||
import aqua.parser.lexer.InfixToken.Op as InfixOp
|
||||
import aqua.parser.lexer.PrefixToken.Op as PrefixOp
|
||||
import aqua.parser.lexer.InfixToken.Op.*
|
||||
import aqua.parser.lexer.PrefixToken.Op.*
|
||||
import aqua.parser.head.FromExpr.NameOrAbAs
|
||||
import aqua.parser.head.{FromExpr, UseFromExpr}
|
||||
import aqua.parser.lexer.*
|
||||
@ -170,6 +162,57 @@ trait AquaSpec extends EitherValues {
|
||||
def closureExpr(str: String): ClosureExpr[Id] = ClosureExpr.p.parseAll(str).value.mapK(spanToId)
|
||||
def arrowExpr(str: String): ArrowExpr[Id] = ArrowExpr.p.parseAll(str).value.mapK(spanToId)
|
||||
|
||||
def prefixToken(value: ValueToken[Id], op: PrefixOp) =
|
||||
PrefixToken[Id](value, op)
|
||||
|
||||
def infixToken(left: ValueToken[Id], right: ValueToken[Id], op: InfixOp) =
|
||||
InfixToken[Id](left, right, op)
|
||||
|
||||
def mul(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Mul)
|
||||
|
||||
def sub(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Sub)
|
||||
|
||||
def div(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Div)
|
||||
|
||||
def rem(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Rem)
|
||||
|
||||
def add(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Add)
|
||||
|
||||
def pow(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Pow)
|
||||
|
||||
def gt(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Gt)
|
||||
|
||||
def gte(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Gte)
|
||||
|
||||
def lt(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Lt)
|
||||
|
||||
def lte(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Lte)
|
||||
|
||||
def or(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Or)
|
||||
|
||||
def and(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, And)
|
||||
|
||||
def not(value: ValueToken[Id]): ValueToken[Id] =
|
||||
prefixToken(value, Not)
|
||||
|
||||
def equ(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Equ)
|
||||
|
||||
def neq(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Neq)
|
||||
|
||||
val nat = new (Span.S ~> Id) {
|
||||
|
||||
override def apply[A](span: Span.S[A]): A = {
|
||||
|
@ -7,7 +7,6 @@ import aqua.parser.lexer.{
|
||||
ArrowTypeToken,
|
||||
BasicTypeToken,
|
||||
CallArrowToken,
|
||||
EqOp,
|
||||
LiteralToken,
|
||||
Token,
|
||||
VarToken
|
||||
@ -106,7 +105,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Inside with Inspectors
|
||||
val ifBody =
|
||||
checkHeadGetTail(
|
||||
arrowExpr.head,
|
||||
IfExpr(toVarLambda("peer", List("id")), EqOp[Id](true), toVar("other")),
|
||||
IfExpr(equ(toVarLambda("peer", List("id")), toVar("other"))),
|
||||
3
|
||||
).toList
|
||||
|
||||
@ -304,7 +303,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Inside with Inspectors
|
||||
.cata[Int]((expr, results) =>
|
||||
// Count `if`s inside the tree
|
||||
Eval.later(results.sumAll + (expr match {
|
||||
case IfExpr(_, _, _) => 1
|
||||
case IfExpr(_) => 1
|
||||
case _ => 0
|
||||
}))
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ package aqua.parser
|
||||
import aqua.AquaSpec
|
||||
import aqua.parser.expr.func.IfExpr
|
||||
import aqua.parser.lexer.InfixToken.Op.{Add, Sub}
|
||||
import aqua.parser.lexer.{CallArrowToken, CollectionToken, EqOp, InfixToken}
|
||||
import aqua.parser.lexer.{CallArrowToken, CollectionToken, InfixToken}
|
||||
import aqua.parser.lexer.CollectionToken.Mode.OptionMode
|
||||
import cats.Id
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
@ -11,75 +11,79 @@ import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class IfExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
|
||||
import AquaSpec._
|
||||
import AquaSpec.*
|
||||
|
||||
"if" should "be parsed" in {
|
||||
parseIf("if a") should be(
|
||||
IfExpr[Id](toVarLambda("a", Nil), EqOp[Id](true), toBool(true))
|
||||
IfExpr[Id](toVarLambda("a", Nil))
|
||||
)
|
||||
|
||||
parseIf("if a == b") should be(
|
||||
IfExpr[Id](toVarLambda("a", Nil), EqOp[Id](true), toVar("b"))
|
||||
IfExpr[Id](equ(toVarLambda("a", Nil), toVar("b")))
|
||||
)
|
||||
|
||||
parseIf("if 1 != false") should be(
|
||||
IfExpr[Id](toNumber(1), EqOp[Id](false), toBool(false))
|
||||
IfExpr[Id](neq(toNumber(1), toBool(false)))
|
||||
)
|
||||
|
||||
parseIf("if a[1] != \"ds\"") should be(
|
||||
IfExpr[Id](toVarIndex("a", 1), EqOp[Id](false), toStr("ds"))
|
||||
IfExpr[Id](neq(toVarIndex("a", 1), toStr("ds")))
|
||||
)
|
||||
|
||||
parseIf("if a[1] == 43") should be(
|
||||
IfExpr[Id](toVarIndex("a", 1), EqOp[Id](true), toNumber(43))
|
||||
IfExpr[Id](equ(toVarIndex("a", 1), toNumber(43)))
|
||||
)
|
||||
|
||||
parseIf("if a!5 == b[3]") should be(
|
||||
IfExpr[Id](toVarIndex("a", 5), EqOp[Id](true), toVarIndex("b", 3))
|
||||
IfExpr[Id](equ(toVarIndex("a", 5), toVarIndex("b", 3)))
|
||||
)
|
||||
|
||||
parseIf("if Op.identity(\"str\") == \"a\"") should be(
|
||||
IfExpr[Id](
|
||||
equ(
|
||||
CallArrowToken[Id](Some(toNamedType("Op")), toName("identity"), toStr("str") :: Nil),
|
||||
EqOp[Id](true),
|
||||
toStr("a")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
parseIf("if Op.identity(\"str\") != Op.identity(\"str\")") should be(
|
||||
IfExpr[Id](
|
||||
neq(
|
||||
CallArrowToken[Id](Some(toNamedType("Op")), toName("identity"), toStr("str") :: Nil),
|
||||
EqOp[Id](false),
|
||||
CallArrowToken[Id](Some(toNamedType("Op")), toName("identity"), toStr("str") :: Nil)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
parseIf("if 2 - 3 != Op.identity(4) + 5") should be(
|
||||
IfExpr[Id](
|
||||
InfixToken[Id](toNumber(2), toNumber(3), Sub),
|
||||
EqOp[Id](false),
|
||||
InfixToken[Id](
|
||||
neq(
|
||||
sub(toNumber(2), toNumber(3)),
|
||||
add(
|
||||
CallArrowToken[Id](Some(toNamedType("Op")), toName("identity"), toNumber(4) :: Nil),
|
||||
toNumber(5),
|
||||
Add
|
||||
toNumber(5)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
parseIf("if funcCall(3) == funcCall2(4)") should be(
|
||||
IfExpr[Id](
|
||||
equ(
|
||||
CallArrowToken[Id](None, toName("funcCall"), toNumber(3) :: Nil),
|
||||
EqOp[Id](true),
|
||||
CallArrowToken[Id](None, toName("funcCall2"), toNumber(4) :: Nil)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
parseIf("if ?[\"a\"] == ?[\"a\"]") should be(
|
||||
IfExpr[Id](
|
||||
equ(
|
||||
CollectionToken[Id](OptionMode, toStr("a") :: Nil),
|
||||
EqOp[Id](true),
|
||||
CollectionToken[Id](OptionMode, toStr("a") :: Nil)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
}
|
||||
}
|
||||
|
||||
import AquaSpec._
|
||||
import AquaSpec.*
|
||||
|
||||
private def variable(name: String): ValueToken[Id] =
|
||||
VarToken(Name(name), Nil)
|
||||
@ -38,43 +38,9 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
|
||||
private def literalBool(b: Boolean): ValueToken[Id] = toBool(b)
|
||||
|
||||
private def prefixToken(value: ValueToken[Id], op: PrefixOp) =
|
||||
PrefixToken[Id](value, op)
|
||||
private def literalString(s: String): ValueToken[Id] = toStr(s)
|
||||
|
||||
private def infixToken(left: ValueToken[Id], right: ValueToken[Id], op: InfixOp) =
|
||||
InfixToken[Id](left, right, op)
|
||||
|
||||
private def mul(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Mul)
|
||||
|
||||
private def sub(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Sub)
|
||||
|
||||
private def div(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Div)
|
||||
|
||||
private def rem(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Rem)
|
||||
|
||||
private def add(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Add)
|
||||
|
||||
private def pow(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Pow)
|
||||
|
||||
private def gt(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Gt)
|
||||
|
||||
private def gte(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Gte)
|
||||
|
||||
private def lt(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Lt)
|
||||
|
||||
private def lte(left: ValueToken[Id], right: ValueToken[Id]): ValueToken[Id] =
|
||||
infixToken(left, right, Lte)
|
||||
|
||||
"primitive math expression" should "be parsed" in {
|
||||
"ValueToken" should "parse primitive math expression" in {
|
||||
|
||||
val vt = ValueToken.`value`.parseAll("3").right.get.mapK(spanToId)
|
||||
val vt1 = ValueToken.`value`.parseAll("2 - 3").right.get.mapK(spanToId)
|
||||
@ -119,7 +85,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
vt11 shouldBe rem(2, 4)
|
||||
}
|
||||
|
||||
"primitive math expression with multiplication" should "be parsed" in {
|
||||
it should "parse primitive math expression with multiplication" in {
|
||||
|
||||
val res = ValueToken.`value`.parseAll("(3 - 2) * 4").right.get.mapK(spanToId)
|
||||
val res2 = ValueToken.`value`.parseAll("3 - 2 * 4").right.get.mapK(spanToId)
|
||||
@ -176,7 +142,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
|
||||
}
|
||||
|
||||
"math expression" should "be parsed" in {
|
||||
it should "parse math expression" in {
|
||||
|
||||
val vt = ValueToken.`value`.parseAll("3 - 2 + 5").right.get.mapK(spanToId)
|
||||
|
||||
@ -185,7 +151,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
|
||||
}
|
||||
|
||||
"complex math expression" should "be parsed" in {
|
||||
it should "parse complex math expression" in {
|
||||
|
||||
val res = ValueToken.`value`.parseAll("(3 - 2 + 5) + 5 + (4 - 7)").right.get.mapK(spanToId)
|
||||
|
||||
@ -201,7 +167,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
)
|
||||
}
|
||||
|
||||
"complex math expression with multiplication" should "be parsed" in {
|
||||
it should "parse complex math expression with multiplication" in {
|
||||
|
||||
val vt = ValueToken.`value`.parseAll("(3 - 2) * 2 + (4 - 7) * 3").right.get.mapK(spanToId)
|
||||
|
||||
@ -221,7 +187,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
)
|
||||
}
|
||||
|
||||
"simple math expression with exp" should "be parsed" in {
|
||||
it should "parse simple math expression with exp" in {
|
||||
// Correct (1 ** (2 ** 3))
|
||||
val vt = ValueToken.`value`.parseAll("1**2**3").right.get.mapK(spanToId)
|
||||
|
||||
@ -230,7 +196,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
|
||||
}
|
||||
|
||||
"complex math expression with exp" should "be parsed" in {
|
||||
it should "parse complex math expression with exp" in {
|
||||
// Correct ((1 ** 2) + (((3 ** 4) * (5 ** (6 ** 7))) * 9))
|
||||
val vt = ValueToken.`value`.parseAll("1 ** 2 + 3**4* 5**6 ** 7*9").right.get.mapK(spanToId)
|
||||
|
||||
@ -259,7 +225,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
|
||||
}
|
||||
|
||||
"simple cmp math expression " should "be parsed" in {
|
||||
it should "parse simple cmp math expression" in {
|
||||
val vt = ValueToken.`value`.parseAll("1 > 3").right.get.mapK(spanToId)
|
||||
val vt1 = ValueToken.`value`.parseAll("1 < 3").right.get.mapK(spanToId)
|
||||
val vt2 = ValueToken.`value`.parseAll("1 >= 3").right.get.mapK(spanToId)
|
||||
@ -271,7 +237,7 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
vt3 shouldBe lte(literal(1), literal(3))
|
||||
}
|
||||
|
||||
"complex cmp math expression " should "be parsed" in {
|
||||
it should "parse complex cmp math expression" in {
|
||||
val test = (op: InfixOp) => {
|
||||
val vt = ValueToken.`value`
|
||||
.parseAll(
|
||||
@ -289,12 +255,12 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
List(Gt, Lt, Gte, Lte).foreach(test)
|
||||
}
|
||||
|
||||
"simple cmp math expression in brackets " should "be parsed" in {
|
||||
it should "parse simple cmp math expression in brackets" in {
|
||||
val vt = ValueToken.`value`.parseAll("(1 > 3)").right.get.mapK(spanToId)
|
||||
vt shouldBe InfixToken(literal(1), literal(3), Gt)
|
||||
}
|
||||
|
||||
"simple logical expression" should "be parsed" in {
|
||||
it should "parse simple logical expression" in {
|
||||
val vtAnd = ValueToken.`value`.parseAll("true && false").map(_.mapK(spanToId))
|
||||
|
||||
inside(vtAnd) { case Right(vt) =>
|
||||
@ -342,7 +308,21 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
}
|
||||
}
|
||||
|
||||
"logical expression with brackets" should "be parsed" in {
|
||||
it should "parse simple equality expression" in {
|
||||
val ltEqLt = ValueToken.`value`.parseAll("\"abc\" == \"cba\"").map(_.mapK(spanToId))
|
||||
|
||||
inside(ltEqLt) { case Right(vt) =>
|
||||
vt shouldBe equ(literalString("abc"), literalString("cba"))
|
||||
}
|
||||
|
||||
val vtNeqLt = ValueToken.`value`.parseAll("a != \"cba\"").map(_.mapK(spanToId))
|
||||
|
||||
inside(vtNeqLt) { case Right(vt) =>
|
||||
vt shouldBe neq(variable("a"), literalString("cba"))
|
||||
}
|
||||
}
|
||||
|
||||
it should "parse logical expression with brackets" in {
|
||||
val vtAndOr = ValueToken.`value`.parseAll("false && (true || false)").map(_.mapK(spanToId))
|
||||
|
||||
inside(vtAndOr) { case Right(vt) =>
|
||||
@ -387,124 +367,191 @@ class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with A
|
||||
}
|
||||
}
|
||||
|
||||
"logical expression with math expressions" should "be parsed" in {
|
||||
it should "parse logical expression with math expressions" in {
|
||||
val vt1 = ValueToken.`value`.parseAll("1 < 2 + 3 || 3 % 2 > 1").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt1) { case Right(vt) =>
|
||||
vt shouldBe infixToken(
|
||||
infixToken(
|
||||
vt shouldBe or(
|
||||
lt(
|
||||
literal(1),
|
||||
infixToken(literal(2), literal(3), Add),
|
||||
Lt
|
||||
add(literal(2), literal(3))
|
||||
),
|
||||
infixToken(
|
||||
infixToken(literal(3), literal(2), Rem),
|
||||
literal(1),
|
||||
Gt
|
||||
),
|
||||
Or
|
||||
gt(
|
||||
rem(literal(3), literal(2)),
|
||||
literal(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vt2 = ValueToken.`value`.parseAll("1 - 2 > 3 && 3 ** 2 <= 1").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt2) { case Right(vt) =>
|
||||
vt shouldBe infixToken(
|
||||
infixToken(
|
||||
infixToken(literal(1), literal(2), Sub),
|
||||
literal(3),
|
||||
Gt
|
||||
vt shouldBe and(
|
||||
gt(
|
||||
sub(literal(1), literal(2)),
|
||||
literal(3)
|
||||
),
|
||||
infixToken(
|
||||
infixToken(literal(3), literal(2), Pow),
|
||||
literal(1),
|
||||
Lte
|
||||
),
|
||||
And
|
||||
lte(
|
||||
pow(literal(3), literal(2)),
|
||||
literal(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vt3 = ValueToken.`value`.parseAll("!(1 - 2 > 3) && 3 ** 2 <= 1").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt3) { case Right(vt) =>
|
||||
vt shouldBe infixToken(
|
||||
prefixToken(
|
||||
infixToken(
|
||||
infixToken(literal(1), literal(2), Sub),
|
||||
literal(3),
|
||||
Gt
|
||||
vt shouldBe and(
|
||||
not(
|
||||
gt(
|
||||
sub(literal(1), literal(2)),
|
||||
literal(3)
|
||||
)
|
||||
),
|
||||
Not
|
||||
),
|
||||
infixToken(
|
||||
infixToken(literal(3), literal(2), Pow),
|
||||
literal(1),
|
||||
Lte
|
||||
),
|
||||
And
|
||||
lte(
|
||||
pow(literal(3), literal(2)),
|
||||
literal(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"logical expression with function calls and variables" should "be parsed" in {
|
||||
it should "parse logical expression with math and equality" in {
|
||||
val vt1 = ValueToken.`value`.parseAll("1 == 2 + 3 || 3 % 2 != 1").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt1) { case Right(vt) =>
|
||||
vt shouldBe or(
|
||||
equ(
|
||||
literal(1),
|
||||
add(literal(2), literal(3))
|
||||
),
|
||||
neq(
|
||||
rem(literal(3), literal(2)),
|
||||
literal(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vt2 = ValueToken.`value`.parseAll("1 - 2 != 3 && 3 ** 2 == 1").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt2) { case Right(vt) =>
|
||||
vt shouldBe and(
|
||||
neq(
|
||||
sub(literal(1), literal(2)),
|
||||
literal(3)
|
||||
),
|
||||
equ(
|
||||
pow(literal(3), literal(2)),
|
||||
literal(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vt3 =
|
||||
ValueToken.`value`.parseAll("!(true == 2 > 3) && false == 2 <= 1").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt3) { case Right(vt) =>
|
||||
vt shouldBe and(
|
||||
not(
|
||||
equ(
|
||||
literalBool(true),
|
||||
gt(literal(2), literal(3))
|
||||
)
|
||||
),
|
||||
equ(
|
||||
literalBool(false),
|
||||
lte(literal(2), literal(1))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "parse logical expression with function calls and variables" in {
|
||||
val vt1 = ValueToken.`value`.parseAll("foo() || a + 1 < 2 && b").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt1) { case Right(vt) =>
|
||||
vt shouldBe infixToken(
|
||||
vt shouldBe or(
|
||||
func("foo", Nil),
|
||||
infixToken(
|
||||
infixToken(
|
||||
infixToken(
|
||||
and(
|
||||
lt(
|
||||
add(
|
||||
variable("a"),
|
||||
literal(1),
|
||||
Add
|
||||
literal(1)
|
||||
),
|
||||
literal(2),
|
||||
Lt
|
||||
literal(2)
|
||||
),
|
||||
variable("b"),
|
||||
And
|
||||
),
|
||||
Or
|
||||
variable("b")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vt2 = ValueToken.`value`.parseAll("bar(a) < 2 && (b > 5 || c)").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt2) { case Right(vt) =>
|
||||
vt shouldBe infixToken(
|
||||
infixToken(func("bar", List(variable("a"))), literal(2), Lt),
|
||||
infixToken(
|
||||
infixToken(
|
||||
vt shouldBe and(
|
||||
lt(func("bar", List(variable("a"))), literal(2)),
|
||||
or(
|
||||
gt(
|
||||
variable("b"),
|
||||
literal(5),
|
||||
Gt
|
||||
literal(5)
|
||||
),
|
||||
variable("c"),
|
||||
Or
|
||||
),
|
||||
And
|
||||
variable("c")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vt3 = ValueToken.`value`.parseAll("!baz(a) && (!(b > 4) || !c)").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt3) { case Right(vt) =>
|
||||
vt shouldBe infixToken(
|
||||
prefixToken(func("baz", List(variable("a"))), Not),
|
||||
infixToken(
|
||||
prefixToken(
|
||||
infixToken(
|
||||
vt shouldBe and(
|
||||
not(func("baz", List(variable("a")))),
|
||||
or(
|
||||
not(
|
||||
gt(
|
||||
variable("b"),
|
||||
literal(4),
|
||||
Gt
|
||||
literal(4)
|
||||
)
|
||||
),
|
||||
Not
|
||||
prefixToken(variable("c"), Not)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vt4 = ValueToken.`value`.parseAll("a == foo(b) && !(baz(c) != d)").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt4) { case Right(vt) =>
|
||||
vt shouldBe and(
|
||||
equ(
|
||||
variable("a"),
|
||||
func("foo", List(variable("b")))
|
||||
),
|
||||
prefixToken(variable("c"), Not),
|
||||
Or
|
||||
not(
|
||||
neq(
|
||||
func("baz", List(variable("c"))),
|
||||
variable("d")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vt5 =
|
||||
ValueToken.`value`.parseAll("!(a == foo(b)) || baz(c) == (d && e)").map(_.mapK(spanToId))
|
||||
|
||||
inside(vt5) { case Right(vt) =>
|
||||
vt shouldBe or(
|
||||
not(
|
||||
equ(
|
||||
variable("a"),
|
||||
func("foo", List(variable("b")))
|
||||
)
|
||||
),
|
||||
And
|
||||
equ(
|
||||
func("baz", List(variable("c"))),
|
||||
and(
|
||||
variable("d"),
|
||||
variable("e")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,7 @@ import aqua.parser.lexer.{Arg, DataTypeToken}
|
||||
import aqua.raw.Raw
|
||||
import aqua.raw.arrow.ArrowRaw
|
||||
import aqua.raw.ops.{SeqTag, *}
|
||||
import aqua.raw.value.{
|
||||
ApplyGateRaw,
|
||||
ApplyPropertyRaw,
|
||||
CallArrowRaw,
|
||||
CollectionRaw,
|
||||
ValueRaw,
|
||||
VarRaw
|
||||
}
|
||||
import aqua.raw.value.*
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.ValuesAlgebra
|
||||
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||
@ -147,10 +140,12 @@ class ArrowSem[S[_]](val expr: ArrowExpr[S]) extends AnyVal {
|
||||
idx + 1
|
||||
)
|
||||
// assign and change return value for all `Apply*Raw`
|
||||
case (v: ApplyGateRaw, _) => assignRaw(v, idx, bodyAcc, returnAcc)
|
||||
case (v: ApplyPropertyRaw, _) => assignRaw(v, idx, bodyAcc, returnAcc)
|
||||
case (v: CallArrowRaw, _) => assignRaw(v, idx, bodyAcc, returnAcc)
|
||||
case (v: CollectionRaw, _) => assignRaw(v, idx, bodyAcc, returnAcc)
|
||||
case (
|
||||
v: (ApplyGateRaw | ApplyPropertyRaw | CallArrowRaw | CollectionRaw |
|
||||
ApplyBinaryOpRaw | ApplyUnaryOpRaw),
|
||||
_
|
||||
) =>
|
||||
assignRaw(v, idx, bodyAcc, returnAcc)
|
||||
|
||||
case (v, _) => (bodyAcc, returnAcc :+ v, idx)
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ import cats.syntax.applicative.*
|
||||
import cats.syntax.flatMap.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.traverse.*
|
||||
import aqua.types.ScalarType
|
||||
|
||||
class IfSem[S[_]](val expr: IfExpr[S]) extends AnyVal {
|
||||
|
||||
@ -29,30 +31,26 @@ class IfSem[S[_]](val expr: IfExpr[S]) extends AnyVal {
|
||||
): Prog[Alg, Raw] =
|
||||
Prog
|
||||
.around(
|
||||
(V.valueToRaw(expr.left), V.valueToRaw(expr.right)).flatMapN {
|
||||
case (Some(lt), Some(rt)) =>
|
||||
T.ensureValuesComparable(
|
||||
token = expr.token,
|
||||
left = lt.`type`,
|
||||
right = rt.`type`
|
||||
).map(Option.when(_)(lt -> rt))
|
||||
case _ => None.pure
|
||||
},
|
||||
(values: Option[(ValueRaw, ValueRaw)], ops: Raw) =>
|
||||
values
|
||||
V.valueToRaw(expr.value)
|
||||
.flatMap(
|
||||
_.flatTraverse(raw =>
|
||||
T.ensureTypeMatches(
|
||||
token = expr.value,
|
||||
expected = ScalarType.bool,
|
||||
givenType = raw.`type`
|
||||
).map(Option.when(_)(raw))
|
||||
)
|
||||
),
|
||||
(value: Option[ValueRaw], ops: Raw) =>
|
||||
value
|
||||
.fold(
|
||||
Raw.error("`if` expression errored in matching types")
|
||||
) { case (lt, rt) =>
|
||||
)(raw =>
|
||||
ops match {
|
||||
case FuncOp(op) =>
|
||||
IfTag(
|
||||
left = lt,
|
||||
right = rt,
|
||||
equal = expr.eqOp.value
|
||||
).wrap(op).toFuncOp
|
||||
case FuncOp(op) => IfTag(raw).wrap(op).toFuncOp
|
||||
case _ => Raw.error("Wrong body of the `if` expression")
|
||||
}
|
||||
}
|
||||
)
|
||||
.pure
|
||||
)
|
||||
.abilitiesScope[S](expr.token)
|
||||
|
@ -21,8 +21,7 @@ class ReturnSem[S[_]](val expr: ReturnExpr[S]) extends AnyVal {
|
||||
): Prog[Alg, Raw] =
|
||||
expr.values
|
||||
.traverse(v => V.valueToRaw(v).map(_.map(v -> _)))
|
||||
.map(_.toList.flatten)
|
||||
.map(NonEmptyList.fromList)
|
||||
.map(_.sequence)
|
||||
.flatMap {
|
||||
case Some(vals) =>
|
||||
T.checkArrowReturn(vals).map[Raw] {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.semantics.rules
|
||||
|
||||
import aqua.parser.lexer.*
|
||||
import aqua.parser.lexer.InfixToken.{BoolOp, CmpOp, MathOp, Op as InfOp}
|
||||
import aqua.parser.lexer.InfixToken.{BoolOp, CmpOp, EqOp, MathOp, Op as InfOp}
|
||||
import aqua.parser.lexer.PrefixToken.Op as PrefOp
|
||||
import aqua.raw.value.*
|
||||
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||
@ -219,6 +219,23 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
|
||||
right = rightRaw
|
||||
)
|
||||
)
|
||||
case InfOp.Eq(eop) =>
|
||||
T.ensureValuesComparable(
|
||||
token = it,
|
||||
left = lType,
|
||||
right = rType
|
||||
).map(
|
||||
Option.when(_)(
|
||||
ApplyBinaryOpRaw(
|
||||
op = eop match {
|
||||
case EqOp.Eq => ApplyBinaryOpRaw.Op.Eq
|
||||
case EqOp.Neq => ApplyBinaryOpRaw.Op.Neq
|
||||
},
|
||||
left = leftRaw,
|
||||
right = rightRaw
|
||||
)
|
||||
)
|
||||
)
|
||||
case op @ (InfOp.Math(_) | InfOp.Cmp(_)) =>
|
||||
// Some type acrobatics to make
|
||||
// compiler check exhaustive pattern matching
|
||||
@ -308,7 +325,9 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
|
||||
): Alg[Option[CallArrowRaw]] =
|
||||
abType.arrows.get(ca.funcName.value) match {
|
||||
case Some(arrowType) =>
|
||||
Option(CallArrowRaw(None, AbilityType.fullName(ab, ca.funcName.value), Nil, arrowType, None)).pure[Alg]
|
||||
Option(
|
||||
CallArrowRaw(None, AbilityType.fullName(ab, ca.funcName.value), Nil, arrowType, None)
|
||||
).pure[Alg]
|
||||
case None => None.pure[Alg]
|
||||
}
|
||||
|
||||
@ -331,7 +350,7 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
|
||||
)(ab =>
|
||||
// Check that we have variable as ability
|
||||
N.read(ab.asName, false).flatMap {
|
||||
case Some(at@AbilityType(_, _)) =>
|
||||
case Some(at @ AbilityType(_, _)) =>
|
||||
callAbType(ab.value, at, ca)
|
||||
case _ =>
|
||||
// Check that we have registered ability type.
|
||||
|
@ -13,20 +13,7 @@ import aqua.raw.value.{
|
||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||
import aqua.semantics.rules.StackInterpreter
|
||||
import aqua.semantics.rules.errors.ReportErrors
|
||||
import aqua.types.{
|
||||
ArrayType,
|
||||
ArrowType,
|
||||
BoxType,
|
||||
LiteralType,
|
||||
NamedType,
|
||||
OptionType,
|
||||
ProductType,
|
||||
ScalarType,
|
||||
AbilityType,
|
||||
StreamType,
|
||||
StructType,
|
||||
Type
|
||||
}
|
||||
import aqua.types.*
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.data.{Chain, NonEmptyList, NonEmptyMap, State}
|
||||
import cats.instances.list.*
|
||||
@ -226,17 +213,35 @@ class TypesInterpreter[S[_], X](implicit
|
||||
left: Type,
|
||||
right: Type
|
||||
): State[X, Boolean] = {
|
||||
val isComparable = (left, right) match {
|
||||
// TODO: This needs more comprehensive logic
|
||||
def isComparable(lt: Type, rt: Type): Boolean =
|
||||
(lt, rt) match {
|
||||
// All numbers are comparable
|
||||
case (lst: ScalarType, rst: ScalarType)
|
||||
if ScalarType.number(lst) && ScalarType.number(rst) =>
|
||||
true
|
||||
// Hack: u64 `U` LiteralType.signed = TopType,
|
||||
// but they should be comparable
|
||||
case (lst: ScalarType, LiteralType.signed) if ScalarType.number(lst) =>
|
||||
true
|
||||
case (LiteralType.signed, rst: ScalarType) if ScalarType.number(rst) =>
|
||||
true
|
||||
case (lbt: BoxType, rbt: BoxType) =>
|
||||
isComparable(lbt.element, rbt.element)
|
||||
// Prohibit comparing abilities
|
||||
case (_: AbilityType, _: AbilityType) =>
|
||||
false
|
||||
// Prohibit comparing arrows
|
||||
case (_: ArrowType, _: ArrowType) =>
|
||||
false
|
||||
case (LiteralType(xs, _), LiteralType(ys, _)) =>
|
||||
xs.intersect(ys).nonEmpty
|
||||
case _ =>
|
||||
left.acceptsValueOf(right)
|
||||
lt.uniteTop(rt) != TopType
|
||||
}
|
||||
|
||||
if (isComparable) State.pure(true)
|
||||
else
|
||||
report(token, s"Cannot compare '$left' with '$right''")
|
||||
.as(false)
|
||||
if (isComparable(left, right)) State.pure(true)
|
||||
else report(token, s"Cannot compare '$left' with '$right''").as(false)
|
||||
}
|
||||
|
||||
private def extractToken(token: Token[S]) =
|
||||
|
@ -5,7 +5,7 @@ import aqua.parser.Ast
|
||||
import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, OnTag, ParTag, RawTag, SeqGroupTag, SeqTag}
|
||||
import aqua.parser.Parser
|
||||
import aqua.parser.lift.{LiftParser, Span}
|
||||
import aqua.raw.value.{LiteralRaw, ValueRaw}
|
||||
import aqua.raw.value.{ApplyBinaryOpRaw, LiteralRaw, ValueRaw}
|
||||
import aqua.types.*
|
||||
import aqua.raw.ops.*
|
||||
|
||||
@ -66,6 +66,12 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
)
|
||||
.leaf
|
||||
|
||||
def equ(left: ValueRaw, right: ValueRaw): ApplyBinaryOpRaw =
|
||||
ApplyBinaryOpRaw(ApplyBinaryOpRaw.Op.Eq, left, right)
|
||||
|
||||
def neq(left: ValueRaw, right: ValueRaw): ApplyBinaryOpRaw =
|
||||
ApplyBinaryOpRaw(ApplyBinaryOpRaw.Op.Neq, left, right)
|
||||
|
||||
// use it to fix https://github.com/fluencelabs/aqua/issues/90
|
||||
"semantics" should "create right model" in {
|
||||
val script =
|
||||
@ -109,7 +115,7 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
|
||||
insideBody(script) { body =>
|
||||
val expected =
|
||||
IfTag(LiteralRaw.number(1), LiteralRaw.number(2), true).wrap(
|
||||
IfTag(equ(LiteralRaw.number(1), LiteralRaw.number(2))).wrap(
|
||||
testServiceCallStr("if"),
|
||||
testServiceCallStr("else")
|
||||
)
|
||||
@ -203,7 +209,7 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
|
||||
insideBody(script) { body =>
|
||||
val expected =
|
||||
IfTag(LiteralRaw.number(1), LiteralRaw.number(2), false).wrap(
|
||||
IfTag(neq(LiteralRaw.number(1), LiteralRaw.number(2))).wrap(
|
||||
testServiceCallStr("if")
|
||||
)
|
||||
|
||||
@ -283,7 +289,7 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
|
||||
insideBody(script) { body =>
|
||||
val expected = TryTag.wrap(
|
||||
IfTag(LiteralRaw.quote("a"), LiteralRaw.quote("b"), false).wrap(
|
||||
IfTag(neq(LiteralRaw.quote("a"), LiteralRaw.quote("b"))).wrap(
|
||||
testServiceCallStr("if")
|
||||
),
|
||||
testServiceCallStr("otherwise")
|
||||
@ -300,7 +306,7 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
| if "a" != "b":
|
||||
| Test.testCallStr("if")
|
||||
|""".stripMargin ->
|
||||
IfTag(LiteralRaw.quote("a"), LiteralRaw.quote("b"), false).wrap(
|
||||
IfTag(neq(LiteralRaw.quote("a"), LiteralRaw.quote("b"))).wrap(
|
||||
testServiceCallStr("if")
|
||||
),
|
||||
"""
|
||||
@ -430,7 +436,7 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
TryTag.wrap(
|
||||
ParTag.wrap(
|
||||
TryTag.wrap(
|
||||
IfTag(LiteralRaw.quote("a"), LiteralRaw.quote("b"), false).wrap(
|
||||
IfTag(neq(LiteralRaw.quote("a"), LiteralRaw.quote("b"))).wrap(
|
||||
testServiceCallStr("if")
|
||||
),
|
||||
testServiceCallStr("otherwise1")
|
||||
|
@ -16,8 +16,10 @@ import aqua.semantics.rules.locations.LocationsAlgebra
|
||||
import aqua.semantics.rules.locations.DummyLocationsInterpreter
|
||||
import aqua.raw.value.{ApplyBinaryOpRaw, LiteralRaw}
|
||||
import aqua.raw.RawContext
|
||||
import aqua.types.{LiteralType, ScalarType, TopType, Type}
|
||||
import aqua.types.*
|
||||
import aqua.parser.lexer.{InfixToken, LiteralToken, Name, PrefixToken, ValueToken, VarToken}
|
||||
import aqua.raw.value.ApplyUnaryOpRaw
|
||||
import aqua.parser.lexer.ValueToken.string
|
||||
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
@ -26,8 +28,9 @@ import cats.Id
|
||||
import cats.data.State
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.comonad.*
|
||||
import cats.data.NonEmptyMap
|
||||
import monocle.syntax.all.*
|
||||
import aqua.raw.value.ApplyUnaryOpRaw
|
||||
import scala.collection.immutable.SortedMap
|
||||
|
||||
class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
|
||||
@ -75,6 +78,25 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
)
|
||||
)
|
||||
|
||||
def valueOfType(t: Type)(
|
||||
varName: String,
|
||||
bool: String = "true",
|
||||
unsigned: String = "42",
|
||||
signed: String = "-42",
|
||||
string: String = "string"
|
||||
): ValueToken[Id] = t match {
|
||||
case t: LiteralType if t == LiteralType.bool =>
|
||||
literal(bool, t)
|
||||
case t: LiteralType if t == LiteralType.unsigned =>
|
||||
literal(unsigned, t)
|
||||
case t: LiteralType if t == LiteralType.signed =>
|
||||
literal(signed, t)
|
||||
case t: LiteralType if t == LiteralType.string =>
|
||||
literal(f"\"$string\"", t)
|
||||
case _ =>
|
||||
variable(varName)
|
||||
}
|
||||
|
||||
"valueToRaw" should "handle +, -, /, *, % on number literals" in {
|
||||
val types = List(
|
||||
LiteralType.signed,
|
||||
@ -249,6 +271,85 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
}
|
||||
}
|
||||
|
||||
it should "handle ==, != on values" in {
|
||||
val test = (lt: Type, rt: Type) => {
|
||||
InfixToken.EqOp.values.foreach { op =>
|
||||
val left = valueOfType(lt)(
|
||||
varName = "left",
|
||||
bool = "true",
|
||||
unsigned = "42",
|
||||
signed = "-42",
|
||||
string = "\"foo\""
|
||||
)
|
||||
val right = valueOfType(rt)(
|
||||
varName = "right",
|
||||
bool = "false",
|
||||
unsigned = "37",
|
||||
signed = "-37",
|
||||
string = "\"bar\""
|
||||
)
|
||||
|
||||
val alg = algebra()
|
||||
|
||||
val state = genState(
|
||||
vars = (
|
||||
List("left" -> lt).filter(_ =>
|
||||
lt match {
|
||||
case _: LiteralType => false
|
||||
case _ => true
|
||||
}
|
||||
) ++ List("right" -> rt).filter(_ =>
|
||||
rt match
|
||||
case _: LiteralType => false
|
||||
case _ => true
|
||||
)
|
||||
).toMap
|
||||
)
|
||||
|
||||
val token = InfixToken[Id](left, right, InfixToken.Op.Eq(op))
|
||||
|
||||
val (st, res) = alg
|
||||
.valueToRaw(token)
|
||||
.run(state)
|
||||
.value
|
||||
|
||||
inside(res) { case Some(ApplyBinaryOpRaw(bop, _, _)) =>
|
||||
bop shouldBe (op match {
|
||||
case InfixToken.EqOp.Eq => ApplyBinaryOpRaw.Op.Eq
|
||||
case InfixToken.EqOp.Neq => ApplyBinaryOpRaw.Op.Neq
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val numbers = ScalarType.integer.toList ++ List(
|
||||
LiteralType.signed,
|
||||
LiteralType.unsigned
|
||||
)
|
||||
|
||||
allPairs(numbers).foreach { case (lt, rt) =>
|
||||
test(lt, rt)
|
||||
}
|
||||
|
||||
val numberStreams = ScalarType.integer.toList.map(StreamType.apply)
|
||||
|
||||
allPairs(numberStreams).foreach { case (lt, rt) =>
|
||||
test(lt, rt)
|
||||
}
|
||||
|
||||
val structType = StructType(
|
||||
"Struct",
|
||||
NonEmptyMap(
|
||||
"foo" -> ScalarType.i64,
|
||||
SortedMap(
|
||||
"bar" -> ScalarType.bool
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
test(structType, structType)
|
||||
}
|
||||
|
||||
it should "handle ! on bool values" in {
|
||||
val types = List(LiteralType.bool, ScalarType.bool)
|
||||
|
||||
@ -325,6 +426,59 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
}
|
||||
}
|
||||
|
||||
it should "check type of (in)equality operands" in {
|
||||
val structType = StructType("Struct", NonEmptyMap.one("field", ScalarType.i8))
|
||||
|
||||
val types =
|
||||
List(
|
||||
LiteralType.bool,
|
||||
ScalarType.i32,
|
||||
structType,
|
||||
StreamType(ScalarType.i8),
|
||||
StreamType(structType),
|
||||
ArrowType(
|
||||
domain = ProductType(ScalarType.i64 :: Nil),
|
||||
codomain = ProductType(ScalarType.bool :: Nil)
|
||||
)
|
||||
)
|
||||
|
||||
allPairs(types).filterNot { case (lt, rt) => lt == rt }.foreach { case (lt, rt) =>
|
||||
InfixToken.EqOp.values.foreach { op =>
|
||||
val left = lt match {
|
||||
case lt: LiteralType =>
|
||||
literal("true", lt)
|
||||
case _ =>
|
||||
variable("left")
|
||||
}
|
||||
val right = rt match {
|
||||
case rt: LiteralType =>
|
||||
literal("false", rt)
|
||||
case _ =>
|
||||
variable("right")
|
||||
}
|
||||
|
||||
val alg = algebra()
|
||||
|
||||
val state = genState(
|
||||
vars = (
|
||||
List("left" -> lt).filter(_ => lt != LiteralType.bool) ++
|
||||
List("right" -> rt).filter(_ => rt != LiteralType.bool)
|
||||
).toMap
|
||||
)
|
||||
|
||||
val token = InfixToken[Id](left, right, InfixToken.Op.Eq(op))
|
||||
|
||||
val (st, res) = alg
|
||||
.valueToRaw(token)
|
||||
.run(state)
|
||||
.value
|
||||
|
||||
res shouldBe None
|
||||
st.errors.exists(_.isInstanceOf[RulesViolated[Id]]) shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "check type of logical operand (unary)" in {
|
||||
val types = ScalarType.integer.toList :+ LiteralType.unsigned
|
||||
|
||||
|
@ -126,9 +126,9 @@ object CompareTypes {
|
||||
// Literals and scalars
|
||||
case (x: ScalarType, y: ScalarType) => scalarOrder.partialCompare(x, y)
|
||||
case (LiteralType(xs, _), y: ScalarType) if xs == Set(y) => 0.0
|
||||
case (LiteralType(xs, _), y: ScalarType) if xs(y) => -1.0
|
||||
case (LiteralType(xs, _), y: ScalarType) if xs.exists(y acceptsValueOf _) => -1.0
|
||||
case (x: ScalarType, LiteralType(ys, _)) if ys == Set(x) => 0.0
|
||||
case (x: ScalarType, LiteralType(ys, _)) if ys(x) => 1.0
|
||||
case (x: ScalarType, LiteralType(ys, _)) if ys.exists(x acceptsValueOf _) => 1.0
|
||||
case (LiteralType(xs, _), LiteralType(ys, _)) if xs == ys => 0.0
|
||||
case (LiteralType(xs, _), LiteralType(ys, _)) if xs subsetOf ys => 1.0
|
||||
case (LiteralType(xs, _), LiteralType(ys, _)) if ys subsetOf xs => -1.0
|
||||
|
Loading…
Reference in New Issue
Block a user