Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
InversionSpaces committed Jul 24, 2023
1 parent f9a034c commit 3581d79
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 31 deletions.
8 changes: 7 additions & 1 deletion parser/src/main/scala/aqua/parser/lexer/ValueToken.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ object CallArrowToken {
Name.p
~ abilities().? ~ comma0(ValueToken.`value`.surroundedBy(`/s*`))
.between(` `.?.with1 *> `(` <* `/s*`, `/s*` *> `)`)
).map { case ((n, ab), args) =>
)
.map { case ((n, ab), args) =>
CallBraces(n, ab.map(_.toList).getOrElse(Nil), args)
}
.withContext(
Expand Down Expand Up @@ -171,6 +172,11 @@ object InfixToken {

def p: P[Unit] = P.string(symbol)

object Op {
val math: List[Op] = List(Pow, Mul, Div, Rem, Add, Sub)
val compare: List[Op] = List(Gt, Gte, Lt, Lte)
}

private def opsParser(ops: List[Op]): P[(Span, Op)] =
P.oneOf(ops.map(op => op.p.lift.map(s => s.as(op))))

Expand Down
28 changes: 28 additions & 0 deletions semantics/src/main/scala/aqua/semantics/CompilerState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import aqua.semantics.rules.definitions.DefinitionsState
import aqua.semantics.rules.locations.LocationsState
import aqua.semantics.rules.names.NamesState
import aqua.semantics.rules.types.TypesState
import aqua.semantics.rules.errors.ReportErrors

import cats.Semigroup
import cats.data.{Chain, State}
import cats.kernel.Monoid
import cats.syntax.monoid.*
import monocle.Lens
import monocle.macros.GenLens

case class CompilerState[S[_]](
errors: Chain[SemanticError[S]] = Chain.empty[SemanticError[S]],
Expand All @@ -32,6 +36,30 @@ object CompilerState {
types = TypesState.init[F](ctx)
)

given [S[_]]: Lens[CompilerState[S], NamesState[S]] =
GenLens[CompilerState[S]](_.names)

given [S[_]]: Lens[CompilerState[S], AbilitiesState[S]] =
GenLens[CompilerState[S]](_.abilities)

given [S[_]]: Lens[CompilerState[S], TypesState[S]] =
GenLens[CompilerState[S]](_.types)

given [S[_]]: Lens[CompilerState[S], DefinitionsState[S]] =
GenLens[CompilerState[S]](_.definitions)

given [S[_]]: ReportErrors[S, CompilerState[S]] =
new ReportErrors[S, CompilerState[S]] {
import monocle.syntax.all.*

override def apply(
st: CompilerState[S],
token: Token[S],
hints: List[String]
): CompilerState[S] =
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
}

implicit def compilerStateMonoid[S[_]]: Monoid[St[S]] = new Monoid[St[S]] {
override def empty: St[S] = State.pure(Raw.Empty("compiler state monoid empty"))

Expand Down
20 changes: 0 additions & 20 deletions semantics/src/main/scala/aqua/semantics/Semantics.scala
Original file line number Diff line number Diff line change
Expand Up @@ -309,26 +309,6 @@ object RawSemantics extends Logging {
def transpile[S[_]](
ast: Ast[S]
)(implicit locations: LocationsAlgebra[S, Interpreter[S, *]]): Interpreter[S, Raw] = {
import monocle.syntax.all.*

implicit val re: ReportErrors[S, CompilerState[S]] = new ReportErrors[S, CompilerState[S]] {
override def apply(
st: CompilerState[S],
token: Token[S],
hints: List[String]
): CompilerState[S] =
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
}

implicit val ns: Lens[CompilerState[S], NamesState[S]] = GenLens[CompilerState[S]](_.names)

implicit val as: Lens[CompilerState[S], AbilitiesState[S]] =
GenLens[CompilerState[S]](_.abilities)

implicit val ts: Lens[CompilerState[S], TypesState[S]] = GenLens[CompilerState[S]](_.types)

implicit val ds: Lens[CompilerState[S], DefinitionsState[S]] =
GenLens[CompilerState[S]](_.definitions)

implicit val typesInterpreter: TypesInterpreter[S, CompilerState[S]] =
new TypesInterpreter[S, CompilerState[S]]
Expand Down
25 changes: 21 additions & 4 deletions semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ import cats.syntax.traverse.*
import cats.syntax.option.*
import cats.instances.list.*
import cats.data.{NonEmptyList, NonEmptyMap}
import scribe.Logging

import scala.collection.immutable.SortedMap

class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
N: NamesAlgebra[S, Alg],
T: TypesAlgebra[S, Alg],
A: AbilitiesAlgebra[S, Alg]
) {
) extends Logging {

def ensureIsString(v: ValueToken[S]): Alg[Boolean] =
ensureTypeMatches(v, LiteralType.string)
Expand Down Expand Up @@ -198,16 +199,32 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
case Op.Lte => ("cmp", "lte")
}

/*
* If `uType == TopType`, it means that we don't
* have type big enough to hold the result of operation.
* e.g. We will use `i64` for result of `i32 * u64`
* TODO: Handle this more gracefully
* (use warning system when it is implemented)
*/
def uTypeBounded = if (uType == TopType) {
val bounded = ScalarType.i64
logger.warn(
s"Result type of ($lType ${it.op} $rType) is $TopType, " +
s"using $bounded instead"
)
bounded
} else uType

// Expected type sets of left and right operands, result type
val (leftExp, rightExp, resType) = it.op match {
case Op.Add | Op.Sub | Op.Div | Op.Rem =>
(ScalarType.integer, ScalarType.integer, uType)
(ScalarType.integer, ScalarType.integer, uTypeBounded)
case Op.Pow =>
(ScalarType.integer, ScalarType.unsigned, uType)
(ScalarType.integer, ScalarType.unsigned, uTypeBounded)
case Op.Mul if hasFloat =>
(ScalarType.float, ScalarType.float, ScalarType.i64)
case Op.Mul =>
(ScalarType.integer, ScalarType.integer, uType)
(ScalarType.integer, ScalarType.integer, uTypeBounded)
case Op.Gt | Op.Lt | Op.Gte | Op.Lte =>
(ScalarType.integer, ScalarType.integer, ScalarType.bool)
}
Expand Down
206 changes: 206 additions & 0 deletions semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package aqua.semantics

import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.names.NamesState
import aqua.semantics.rules.abilities.AbilitiesState
import aqua.semantics.rules.types.TypesState
import aqua.semantics.rules.types.TypesAlgebra
import aqua.semantics.rules.abilities.AbilitiesInterpreter
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.definitions.DefinitionsAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.names.NamesInterpreter
import aqua.semantics.rules.definitions.DefinitionsInterpreter
import aqua.semantics.rules.types.TypesInterpreter
import aqua.semantics.rules.locations.LocationsAlgebra
import aqua.semantics.rules.locations.DummyLocationsInterpreter
import aqua.raw.value.LiteralRaw
import aqua.raw.RawContext
import aqua.types.{LiteralType, ScalarType, TopType, Type}
import aqua.parser.lexer.{InfixToken, LiteralToken, Name, ValueToken, VarToken}

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.Inside
import cats.Id
import cats.data.State
import cats.syntax.functor.*
import cats.syntax.comonad.*
import monocle.syntax.all.*

class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {

type TestState = CompilerState[Id]

def algebra() = {
type Interpreter[A] = State[TestState, A]

given LocationsAlgebra[Id, Interpreter] =
new DummyLocationsInterpreter[Id, CompilerState[Id]]

given TypesAlgebra[Id, Interpreter] =
new TypesInterpreter[Id, CompilerState[Id]]
given AbilitiesAlgebra[Id, Interpreter] =
new AbilitiesInterpreter[Id, CompilerState[Id]]
given NamesAlgebra[Id, Interpreter] =
new NamesInterpreter[Id, CompilerState[Id]]
given DefinitionsAlgebra[Id, Interpreter] =
new DefinitionsInterpreter[Id, CompilerState[Id]]

new ValuesAlgebra[Id, Interpreter]
}

def literal(value: String, `type`: LiteralType) =
LiteralToken(Id(value), `type`)

def variable(name: String) =
VarToken(Name(Id(name)), Nil)

def allPairs[A](list: List[A]): List[(A, A)] = for {
a <- list
b <- list
} yield (a, b)

def genState(vars: Map[String, Type] = Map.empty) =
CompilerState
.init[Id](RawContext.blank)
.focus(_.names)
.modify(
_.focus(_.stack).modify(
NamesState.Frame(
token = Name(Id("test")), // Token just for test
names = vars
) :: _
)
)

"valueToRaw" should "handle +, -, /, *, % on number literals" in {
val types = List(
LiteralType.signed,
LiteralType.unsigned
)

allPairs(types).foreach { case (lt, rt) =>
val llit = literal("42", lt)
val rlit = literal("37", rt)

val alg = algebra()

InfixToken.Op.math
.filterNot(
// Can not use negative numbers with pow
_ == InfixToken.Op.Pow && rt != LiteralType.unsigned
)
.foreach { op =>
val token = InfixToken[Id](llit, rlit, op)

val (st, res) = alg
.valueToRaw(token)
.run(genState())
.value

val t = if (lt == rt) lt else LiteralType.signed

inside(res) { case Some(value) =>
value.`type` shouldBe t
}
}
}
}

it should "handle +, -, /, *, % on number vars" in {
allPairs(ScalarType.integer.toList).foreach { case (lt, rt) =>
val vl = variable("left")
val vr = variable("right")

val ut = lt.uniteTop(rt)

val state = genState(
vars = Map(
"left" -> lt,
"right" -> rt
)
)

val alg = algebra()

InfixToken.Op.math
.filterNot(
// Can not use negative numbers with pow
_ == InfixToken.Op.Pow && ScalarType.signed(rt)
)
.foreach { op =>
val token = InfixToken[Id](vl, vr, op)

val (st, res) = alg
.valueToRaw(token)
.run(state)
.value

inside(res) { case Some(value) =>
value.`type` shouldBe a[ScalarType]

if (ut != TopType) {
value.`type`.acceptsValueOf(lt) shouldBe true
value.`type`.acceptsValueOf(rt) shouldBe true
} else {
// This should happen only if
// of the types is 64 bit
List(lt, rt).exists(
List(ScalarType.u64, ScalarType.i64).contains
) shouldBe true

(value.`type`.acceptsValueOf(lt) ||
value.`type`.acceptsValueOf(rt)) shouldBe true
}

}
}
}
}

it should "handle * on float literals" in {
val llit = literal("42.1", LiteralType.float)
val rlit = literal("37.2", LiteralType.float)

val alg = algebra()

val token = InfixToken[Id](llit, rlit, InfixToken.Op.Mul)

val (st, res) = alg
.valueToRaw(token)
.run(genState())
.value

inside(res) { case Some(value) =>
value.`type` shouldBe ScalarType.i64
}
}

it should "handle * on float vars" in {
allPairs(ScalarType.float.toList).foreach { case (lt, rt) =>
val lvar = variable("left")
val rvar = variable("right")

val alg = algebra()

val state = genState(
vars = Map(
"left" -> lt,
"right" -> rt
)
)

val token = InfixToken[Id](lvar, rvar, InfixToken.Op.Mul)

val (st, res) = alg
.valueToRaw(token)
.run(state)
.value

inside(res) { case Some(value) =>
value.`type` shouldBe ScalarType.i64
}
}
}
}
3 changes: 1 addition & 2 deletions types/src/main/scala/aqua/types/CompareTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ object CompareTypes {
case _ if l == r => 0.0

case (TopType, _) | (_, BottomType) => 1.0
case (BottomType, _) | (_, TopType) =>
-1.0
case (BottomType, _) | (_, TopType) => -1.0

// Collections
case (x: ArrayType, y: ArrayType) => apply(x.element, y.element)
Expand Down
Loading

0 comments on commit 3581d79

Please sign in to comment.