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:
InversionSpaces 2023-08-09 17:38:24 +04:00 committed by GitHub
parent ef4b0143ac
commit a5e9354aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 760 additions and 323 deletions

View File

@ -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)
)

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
)

View File

@ -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)))
)
)
}
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -202,6 +202,9 @@ object ApplyBinaryOpRaw {
enum Op {
case And
case Or
case Eq
case Neq
}
}

View File

@ -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}

View File

@ -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(_))
}

View File

@ -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(_))
}

View File

@ -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

View File

@ -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 = {

View File

@ -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
}))
)

View File

@ -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)
)
)
)
}
}

View File

@ -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")
)
)
)
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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] {

View File

@ -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.

View File

@ -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]) =

View File

@ -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")

View File

@ -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

View File

@ -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