fix(compiler): Fix math ops for u64 [fixes LNG-204] (#811)

This commit is contained in:
InversionSpaces 2023-07-25 11:53:50 +02:00 committed by GitHub
parent cb539f1332
commit 50ba194b86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 612 additions and 171 deletions

View File

@ -103,7 +103,7 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
val const = ctx.allValues.get("X")
const.nonEmpty should be(true)
const.get should be(LiteralModel("5", LiteralType.number))
const.get should be(LiteralModel.number(5))
}

View File

@ -1,3 +1,6 @@
aqua Math
export test1, test2, testI16, testI32, testI64, testU64
func test1() -> u64:
res = 1 + 2 - 3 * 5 - 2 * 3 / 2 + 5
@ -5,4 +8,72 @@ func test1() -> u64:
func test2() -> u64:
res = 2 ** 2 ** (2 * 2 - 2) + 2 - 3 * 5 - 2 * 3 / 2 + 5 + (4 % 2 - 2)
<- res
func getI8() -> i8:
<- -8
func getI16() -> i16:
<- -16
func getI32() -> i32:
<- -32
func getI64() -> i64:
<- -64
func getU8() -> u8:
<- 8
func getU16() -> u16:
<- 16
func getU32() -> u32:
<- 32
func getU64() -> u64:
<- 64
func testI16(peer: string) -> []i16:
res: *i16
on peer:
res <<- getI16() + getI16()
res <<- getI8() * getU8()
res <<- getI8() % getI16()
res <<- getI16() - getI8()
<- res
func testI32(peer: string) -> []i32:
res: *i32
on peer:
res <<- getI32() + getU16()
res <<- getI16() * getU16()
res <<- getI8() % getU16()
res <<- getI16() - getI32()
<- res
func testI64(peer: string) -> []i64:
res: *i64
on peer:
res <<- getI32() + getU32()
res <<- getI16() * getU32()
res <<- getI64() % getI64()
res <<- getU8() - getI64()
<- res
func testU64(peer: string) -> []u64:
res: *u64
on peer:
res <<- getU32() + getU64()
res <<- getU64() * getU64()
res <<- getU64() % getU16()
res <<- getU8() - getU64()
<- res

View File

@ -54,7 +54,7 @@ import {
} from '../examples/collectionSugarCall.js';
import {funcsCall} from '../examples/funcsCall.js';
import {nestedDataCall} from '../examples/nestedDataCall.js';
import {mathTest1Call, mathTest2Call} from '../examples/mathCall.js';
import {mathTest1Call, mathTest2Call, mathTestI16Call, mathTestI32Call, mathTestI64Call, mathTestU64Call} from '../examples/mathCall.js';
import {lng58Bug} from '../compiled/examples/closures.js';
import {config, isEphemeral} from '../config.js';
import {bugLng79Call} from "../examples/canonCall.js";
@ -275,6 +275,30 @@ describe('Testing examples', () => {
expect(res).toEqual(3);
});
it('math.aqua test I16', async () => {
let res = await mathTestI16Call(relay1.peerId);
expect(res).toEqual([-32, -64, -8, -8]);
});
it('math.aqua test I32', async () => {
let res = await mathTestI32Call(relay1.peerId);
expect(res).toEqual([-16, -256, -8, 16]);
});
it('math.aqua test I64', async () => {
let res = await mathTestI64Call(relay1.peerId);
expect(res).toEqual([0, -512, 0, 72]);
});
it('math.aqua test U64', async () => {
let res = await mathTestU64Call(relay1.peerId);
expect(res).toEqual([96, 4096, 0, -56]);
});
it('multiReturn.aqua', async () => {
let multiReturnResult = await multiReturnCall();
expect(multiReturnResult).toEqual([['some-str', 'random-str', 'some-str'], 5, 'some-str', [1, 2], null, 10]);

View File

@ -1,4 +1,4 @@
import {test1, test2} from '../compiled/examples/math.js';
import {test1, test2, testI16, testI32, testI64, testU64} from '../compiled/examples/math.js';
export async function mathTest1Call(): Promise<number> {
return await test1();
@ -6,4 +6,20 @@ export async function mathTest1Call(): Promise<number> {
export async function mathTest2Call(): Promise<number> {
return await test2();
}
export async function mathTestI16Call(peer: string): Promise<number[]> {
return await testI16(peer);
}
export async function mathTestI32Call(peer: string): Promise<number[]> {
return await testI32(peer);
}
export async function mathTestI64Call(peer: string): Promise<number[]> {
return await testI64(peer);
}
export async function mathTestU64Call(peer: string): Promise<number[]> {
return await testU64(peer);
}

View File

@ -121,9 +121,9 @@ case class LiteralRaw(value: String, baseType: Type) extends ValueRaw {
object LiteralRaw {
def quote(value: String): LiteralRaw = LiteralRaw("\"" + value + "\"", LiteralType.string)
def number(value: Int): LiteralRaw = LiteralRaw(value.toString, LiteralType.number)
def number(value: Int): LiteralRaw = LiteralRaw(value.toString, LiteralType.forInt(value))
val Zero: LiteralRaw = LiteralRaw("0", LiteralType.number)
val Zero: LiteralRaw = number(0)
val True: LiteralRaw = LiteralRaw("true", LiteralType.bool)
val False: LiteralRaw = LiteralRaw("false", LiteralType.bool)
@ -162,11 +162,14 @@ case class MakeStructRaw(fields: NonEmptyMap[String, ValueRaw], structType: Stru
copy(fields = fields.map(_.renameVars(map)))
}
case class AbilityRaw(fieldsAndArrows: NonEmptyMap[String, ValueRaw], abilityType: AbilityType) extends ValueRaw {
case class AbilityRaw(fieldsAndArrows: NonEmptyMap[String, ValueRaw], abilityType: AbilityType)
extends ValueRaw {
override def baseType: Type = abilityType
override def map(f: ValueRaw => ValueRaw): ValueRaw = f(copy(fieldsAndArrows = fieldsAndArrows.map(f)))
override def map(f: ValueRaw => ValueRaw): ValueRaw = f(
copy(fieldsAndArrows = fieldsAndArrows.map(f))
)
override def varNames: Set[String] = {
fieldsAndArrows.toSortedMap.values.flatMap(_.varNames).toSet

View File

@ -70,7 +70,7 @@ object LiteralModel {
def quote(str: String): LiteralModel = LiteralModel(s"\"$str\"", LiteralType.string)
def number(n: Int): LiteralModel = LiteralModel(n.toString, LiteralType.number)
def number(n: Int): LiteralModel = LiteralModel(n.toString, LiteralType.forInt(n))
}
sealed trait PropertyModel {

View File

@ -80,10 +80,7 @@ object ModelBuilder {
ValueModel.fromRaw(bc.errorHandlingSrvId),
bc.errorFuncName,
CallRes(
ValueModel.lastError :: LiteralModel(
i.toString,
LiteralType.number
) :: Nil,
ValueModel.lastError :: LiteralModel.number(i) :: Nil,
None
),
on

View File

@ -34,7 +34,7 @@ case class LiteralToken[F[_]: Comonad](valueToken: F[String], ts: LiteralType)
def value: String = valueToken.extract
override def toString: String = s"$value"
override def toString: String = s"$value:$ts"
}
case class CollectionToken[F[_]: Comonad](
@ -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(
@ -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))))
@ -351,7 +357,7 @@ object ValueToken {
(minus.?.with1 ~ Numbers.nonNegativeIntString).lift.map(fu =>
fu.extract match {
case (Some(_), n) LiteralToken(fu.as(s"-$n"), LiteralType.signed)
case (None, n) LiteralToken(fu.as(n), LiteralType.number)
case (None, n) LiteralToken(fu.as(n), LiteralType.unsigned)
}
)

View File

@ -20,7 +20,7 @@ import aqua.parser.head.{FromExpr, UseFromExpr}
import aqua.parser.lexer.*
import aqua.parser.lexer.Token.LiftToken
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.types.LiteralType.{bool, number, string}
import aqua.types.LiteralType.{bool, number, signed, string, unsigned}
import aqua.types.{LiteralType, ScalarType}
import cats.{~>, Id}
import org.scalatest.EitherValues
@ -60,7 +60,9 @@ object AquaSpec {
implicit def toVarIndex(name: String, idx: Int): VarToken[Id] =
VarToken[Id](toName(name), IntoIndex[Id](toNumber(idx).unit, Some(toNumber(idx))) :: Nil)
implicit def toLiteral(name: String, t: LiteralType): LiteralToken[Id] = LiteralToken[Id](name, t)
implicit def toNumber(n: Int): LiteralToken[Id] = LiteralToken[Id](n.toString, number)
implicit def toNumber(n: Int): LiteralToken[Id] =
LiteralToken[Id](n.toString, LiteralType.forInt(n))
implicit def toBool(n: Boolean): LiteralToken[Id] = LiteralToken[Id](n.toString, bool)
implicit def toStr(n: String): LiteralToken[Id] = LiteralToken[Id]("\"" + n + "\"", string)

View File

@ -21,7 +21,7 @@ class AbilityIdExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
)
parseAbId("Ab 1") should be(
AbilityIdExpr[Id](toNamedType("Ab"), LiteralToken[Id]("1", LiteralType.number))
AbilityIdExpr[Id](toNamedType("Ab"), toNumber(1))
)
parseAbId("Ab a.id") should be(

View File

@ -16,15 +16,13 @@ class AbilityValueExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec.*
private def parseAndCheckAbility(str: String) = {
val one = LiteralToken[Id]("1", LiteralType.number)
parseData(
str
) should be(
NamedValueToken(
NamedTypeToken[Id]("AbilityA"),
NonEmptyMap.of(
"v1" -> one,
"v1" -> toNumber(1),
"f1" -> VarToken(Name[Id]("input"), IntoField[Id]("arrow") :: Nil)
)
)
@ -36,8 +34,7 @@ class AbilityValueExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
}
"multiline line struct value" should "be parsed" in {
parseAndCheckAbility(
"""AbilityA(v1 = 1, f1 = input.arrow)""".stripMargin)
parseAndCheckAbility("""AbilityA(v1 = 1, f1 = input.arrow)""".stripMargin)
}
}

View File

@ -24,7 +24,7 @@ class InfixTokenSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
private def literal(n: Int): ValueToken[Id] = LiteralToken[Id](n.toString, LiteralType.number)
private def literal(n: Int): ValueToken[Id] = toNumber(n)
private def infixToken(left: ValueToken[Id], right: ValueToken[Id], op: Op) =
InfixToken[Id](left, right, op)

View File

@ -4,7 +4,19 @@ import aqua.AquaSpec
import aqua.AquaSpec.{toNumber, toStr, toVar}
import aqua.parser.expr.ConstantExpr
import aqua.parser.expr.func.AssignmentExpr
import aqua.parser.lexer.{Ability, CallArrowToken, CollectionToken, IntoArrow, LiteralToken, Name, NamedTypeToken, NamedValueToken, Token, ValueToken, VarToken}
import aqua.parser.lexer.{
Ability,
CallArrowToken,
CollectionToken,
IntoArrow,
LiteralToken,
Name,
NamedTypeToken,
NamedValueToken,
Token,
ValueToken,
VarToken
}
import aqua.parser.lexer.CollectionToken.Mode.ArrayMode
import aqua.types.LiteralType
import cats.Id
@ -16,9 +28,10 @@ class StructValueExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
private def parseAndCheckStruct(str: String) = {
val one = LiteralToken[Id]("1", LiteralType.number)
val two = LiteralToken[Id]("2", LiteralType.number)
val three = LiteralToken[Id]("3", LiteralType.number)
val one = toNumber(1)
val two = toNumber(2)
val three = toNumber(3)
val a = LiteralToken[Id]("\"a\"", LiteralType.string)
val b = LiteralToken[Id]("\"b\"", LiteralType.string)
val c = LiteralToken[Id]("\"c\"", LiteralType.string)
@ -51,30 +64,39 @@ class StructValueExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
"one named arg" should "be parsed" in {
val result = aqua.parser.lexer.Token.namedArg
.parseAll(
""" a
| =
| 3""".stripMargin)
.map(v => (v._1, v._2.mapK(spanToId))).value
.parseAll(""" a
| =
| 3""".stripMargin)
.map(v => (v._1, v._2.mapK(spanToId)))
.value
result should be(("a", toNumber(3)))
}
"named args" should "be parsed" in {
val result = Token.namedArgs.parseAll(
"""(
|a = "str",
|b = 3,
|c
| =
| 5
|)""".stripMargin).value.map{ case (str, vt) => (str, vt.mapK(spanToId)) }
val result = Token.namedArgs
.parseAll("""(
|a = "str",
|b = 3,
|c
| =
| 5
|)""".stripMargin)
.value
.map { case (str, vt) => (str, vt.mapK(spanToId)) }
result should be(NonEmptyList[(String, ValueToken[Id])](("a", toStr("str")), ("b", toNumber(3)) :: ("c", toNumber(5)) :: Nil))
result should be(
NonEmptyList[(String, ValueToken[Id])](
("a", toStr("str")),
("b", toNumber(3)) :: ("c", toNumber(5)) :: Nil
)
)
}
"one line struct value" should "be parsed" in {
parseAndCheckStruct("""Obj(f1 = 1, f2 = "a", f3 = [1,2,3], f4=["b", "c"], f5 =NestedObj(i1 = 2, i2 = "b", i3= funcCall(3), i4 = value), f6=funcCall(1), f7 = Serv.call(2))""")
parseAndCheckStruct(
"""Obj(f1 = 1, f2 = "a", f3 = [1,2,3], f4=["b", "c"], f5 =NestedObj(i1 = 2, i2 = "b", i3= funcCall(3), i4 = value), f6=funcCall(1), f7 = Serv.call(2))"""
)
}
"multiline line struct value" should "be parsed" in {
@ -91,7 +113,8 @@ class StructValueExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
| i1
| =
| 2,
| i2 = "b", i3= funcCall(3), i4 = value), f6=funcCall(1), f7 = Serv.call(2))""".stripMargin)
| i2 = "b", i3= funcCall(3), i4 = value), f6=funcCall(1), f7 = Serv.call(2))""".stripMargin
)
}
}

View File

@ -27,12 +27,15 @@ class PropertyOpSpec extends AnyFlatSpec with Matchers with EitherValues {
val idx2 = PropertyOp.ops.parseAll("[ 1 ]").value.map(_.mapK(spanToId)).head
idx2 shouldBe IntoIndex[Id]((), Option(toNumber(1)))
val idx3 = PropertyOp.ops.parseAll(
"""[ -- comment1
| -- comment2
| 1 -- comment3
| -- comment4
|]""".stripMargin).value.map(_.mapK(spanToId)).head
val idx3 = PropertyOp.ops
.parseAll("""[ -- comment1
| -- comment2
| 1 -- comment3
| -- comment4
|]""".stripMargin)
.value
.map(_.mapK(spanToId))
.head
idx3 shouldBe IntoIndex[Id]((), Option(toNumber(1)))
PropertyOp.ops.parseAll("[-1]").isLeft shouldBe true
@ -48,7 +51,7 @@ class PropertyOpSpec extends AnyFlatSpec with Matchers with EitherValues {
(),
NonEmptyMap.of(
"a" -> LiteralToken("\"str\"", LiteralType.string),
"b" -> LiteralToken("12", LiteralType.number)
"b" -> toNumber(12)
)
)
)
@ -60,13 +63,13 @@ class PropertyOpSpec extends AnyFlatSpec with Matchers with EitherValues {
(),
NonEmptyMap.of(
"a" -> LiteralToken("\"str\"", LiteralType.string),
"b" -> LiteralToken("12", LiteralType.number)
"b" -> toNumber(12)
)
),
IntoCopy[Id](
(),
NonEmptyMap.of(
"c" -> LiteralToken("54", LiteralType.number),
"c" -> toNumber(54),
"d" -> VarToken("someVar")
)
)

View File

@ -12,7 +12,9 @@ class ValueTokenSpec extends AnyFlatSpec with Matchers with EitherValues {
import aqua.AquaSpec._
"var getter" should "parse" in {
ValueToken.`value`.parseAll("varname").value.mapK(spanToId) should be(VarToken(Name[Id]("varname"), Nil))
ValueToken.`value`.parseAll("varname").value.mapK(spanToId) should be(
VarToken(Name[Id]("varname"), Nil)
)
ValueToken.`value`.parseAll("varname.field").value.mapK(spanToId) should be(
VarToken(Name[Id]("varname"), IntoField[Id]("field") :: Nil)
)
@ -22,17 +24,40 @@ class ValueTokenSpec extends AnyFlatSpec with Matchers with EitherValues {
}
"literals" should "parse" in {
ValueToken.`value`.parseAll("true").value.mapK(spanToId) should be(LiteralToken[Id]("true", LiteralType.bool))
ValueToken.`value`.parseAll("false").value.mapK(spanToId) should be(LiteralToken[Id]("false", LiteralType.bool))
ValueToken.`value`.parseAll("true").value.mapK(spanToId) should be(
LiteralToken[Id]("true", LiteralType.bool)
)
ValueToken.`value`.parseAll("false").value.mapK(spanToId) should be(
LiteralToken[Id]("false", LiteralType.bool)
)
ValueToken.`value`.parseAll("1").value.mapK(spanToId) should be(LiteralToken[Id]("1", LiteralType.number))
ValueToken.`value`.parseAll("1111").value.mapK(spanToId) should be(LiteralToken[Id]("1111", LiteralType.number))
ValueToken.`value`.parseAll("-1").value.mapK(spanToId) should be(
LiteralToken[Id]("-1", LiteralType.signed)
)
ValueToken.`value`.parseAll("-1111").value.mapK(spanToId) should be(
LiteralToken[Id]("-1111", LiteralType.signed)
)
ValueToken.`value`.parseAll("-1543").value.mapK(spanToId) should be(LiteralToken[Id]("-1543", LiteralType.signed))
ValueToken.`value`.parseAll("1").value.mapK(spanToId) should be(
LiteralToken[Id]("1", LiteralType.unsigned)
)
ValueToken.`value`.parseAll("1111").value.mapK(spanToId) should be(
LiteralToken[Id]("1111", LiteralType.unsigned)
)
ValueToken.`value`.parseAll("1.0").value.mapK(spanToId) should be(LiteralToken[Id]("1.0", LiteralType.float))
ValueToken.`value`.parseAll("1.23").value.mapK(spanToId) should be(LiteralToken[Id]("1.23", LiteralType.float))
ValueToken.`value`.parseAll("-1.23").value.mapK(spanToId) should be(LiteralToken[Id]("-1.23", LiteralType.float))
ValueToken.`value`.parseAll("-1543").value.mapK(spanToId) should be(
LiteralToken[Id]("-1543", LiteralType.signed)
)
ValueToken.`value`.parseAll("1.0").value.mapK(spanToId) should be(
LiteralToken[Id]("1.0", LiteralType.float)
)
ValueToken.`value`.parseAll("1.23").value.mapK(spanToId) should be(
LiteralToken[Id]("1.23", LiteralType.float)
)
ValueToken.`value`.parseAll("-1.23").value.mapK(spanToId) should be(
LiteralToken[Id]("-1.23", LiteralType.float)
)
ValueToken.`value`.parseAll("\"some crazy string\"").value.mapK(spanToId) should be(
LiteralToken[Id]("\"some crazy string\"", LiteralType.string)

View File

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

View File

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

View File

@ -17,6 +17,7 @@ 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
@ -24,7 +25,7 @@ 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)
@ -129,18 +130,17 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
typeFromFieldsWithData = rawFields
.map(rf =>
resolvedType match {
case struct@StructType(_, _) =>
case struct @ StructType(_, _) =>
(
StructType(typeName.value, rf.map(_.`type`)),
Some(MakeStructRaw(rf, struct))
)
case scope@AbilityType(_, _) =>
case scope @ AbilityType(_, _) =>
(
AbilityType(typeName.value, rf.map(_.`type`)),
Some(AbilityRaw(rf, scope))
)
}
)
.getOrElse(BottomType -> None)
(typeFromFields, data) = typeFromFieldsWithData
@ -175,50 +175,75 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
callArrowToRaw(ca).map(_.widen[ValueRaw])
case it @ InfixToken(l, r, _) =>
(valueToRaw(l), valueToRaw(r)).mapN((ll, rr) => ll -> rr).flatMap {
(valueToRaw(l), valueToRaw(r)).flatMapN {
case (Some(leftRaw), Some(rightRaw)) =>
// TODO handle literal types
val hasFloats = ScalarType.float
.exists(ft => leftRaw.`type`.acceptsValueOf(ft) || rightRaw.`type`.acceptsValueOf(ft))
val lType = leftRaw.`type`
val rType = rightRaw.`type`
lazy val uType = lType `` rType
val hasFloat = List(lType, rType).exists(
_ acceptsValueOf LiteralType.float
)
// https://github.com/fluencelabs/aqua-lib/blob/main/math.aqua
// Expected types of left and right operands, result type if known, service ID and function name
val (leftType, rightType, res, id, fn) = it.op match {
case Op.Add =>
(ScalarType.i64, ScalarType.i64, None, "math", "add")
case Op.Sub => (ScalarType.i64, ScalarType.i64, None, "math", "sub")
case Op.Mul if hasFloats =>
// TODO may it be i32?
(ScalarType.f64, ScalarType.f64, Some(ScalarType.i64), "math", "fmul")
case Op.Mul =>
(ScalarType.i64, ScalarType.i64, None, "math", "mul")
case Op.Div => (ScalarType.i64, ScalarType.i64, None, "math", "div")
case Op.Rem => (ScalarType.i64, ScalarType.i64, None, "math", "rem")
case Op.Pow => (ScalarType.i64, ScalarType.u32, None, "math", "pow")
case Op.Gt => (ScalarType.i64, ScalarType.i64, Some(ScalarType.bool), "cmp", "gt")
case Op.Gte => (ScalarType.i64, ScalarType.i64, Some(ScalarType.bool), "cmp", "gte")
case Op.Lt => (ScalarType.i64, ScalarType.i64, Some(ScalarType.bool), "cmp", "lt")
case Op.Lte => (ScalarType.i64, ScalarType.i64, Some(ScalarType.bool), "cmp", "lte")
// See https://github.com/fluencelabs/aqua-lib/blob/main/math.aqua
val (id, fn) = it.op match {
case Op.Add => ("math", "add")
case Op.Sub => ("math", "sub")
case Op.Mul if hasFloat => ("math", "fmul")
case Op.Mul => ("math", "mul")
case Op.Div => ("math", "div")
case Op.Rem => ("math", "rem")
case Op.Pow => ("math", "pow")
case Op.Gt => ("cmp", "gt")
case Op.Gte => ("cmp", "gte")
case Op.Lt => ("cmp", "lt")
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, uTypeBounded)
case Op.Pow =>
(ScalarType.integer, ScalarType.unsigned, uTypeBounded)
case Op.Mul if hasFloat =>
(ScalarType.float, ScalarType.float, ScalarType.i64)
case Op.Mul =>
(ScalarType.integer, ScalarType.integer, uTypeBounded)
case Op.Gt | Op.Lt | Op.Gte | Op.Lte =>
(ScalarType.integer, ScalarType.integer, ScalarType.bool)
}
for {
ltm <- T.ensureTypeMatches(l, leftType, leftRaw.`type`)
rtm <- T.ensureTypeMatches(r, rightType, rightRaw.`type`)
} yield Option.when(ltm && rtm)(
leftChecked <- T.ensureTypeOneOf(l, leftExp, lType)
rightChecked <- T.ensureTypeOneOf(r, rightExp, rType)
} yield Option.when(
leftChecked.isDefined && rightChecked.isDefined
)(
CallArrowRaw(
Some(id),
fn,
leftRaw :: rightRaw :: Nil,
ArrowType(
ProductType(leftType :: rightType :: Nil),
ProductType(
res.getOrElse(
// If result type is not known/enforced, then assume it's the widest type of operands
// E.g. 1:i8 + 1:i8 -> i8, not i64
leftRaw.`type` `` rightRaw.`type`
) :: Nil
)
ability = Some(id),
name = fn,
arguments = leftRaw :: rightRaw :: Nil,
baseType = ArrowType(
ProductType(lType :: rType :: Nil),
ProductType(resType :: Nil)
),
Some(LiteralRaw.quote(id))
serviceId = Some(LiteralRaw.quote(id))
)
)
@ -228,9 +253,14 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
}
// Generate CallArrowRaw for arrow in ability
def callAbType(ab: String, abType: AbilityType, ca: CallArrowToken[S]): Alg[Option[CallArrowRaw]] =
def callAbType(
ab: String,
abType: AbilityType,
ca: CallArrowToken[S]
): Alg[Option[CallArrowRaw]] =
abType.arrows.get(ca.funcName.value) match {
case Some(arrowType) => Option(CallArrowRaw(None, s"$ab.${ca.funcName.value}", Nil, arrowType, None)).pure[Alg]
case Some(arrowType) =>
Option(CallArrowRaw(None, s"$ab.${ca.funcName.value}", Nil, arrowType, None)).pure[Alg]
case None => None.pure[Alg]
}
@ -279,7 +309,6 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
case _ => none
}
}
)
result <- raw.flatTraverse(r =>
val arr = r.baseType

View File

@ -36,6 +36,12 @@ trait TypesAlgebra[S[_], Alg[_]] {
def ensureTypeMatches(token: Token[S], expected: Type, givenType: Type): Alg[Boolean]
def ensureTypeOneOf[T <: Type](
token: Token[S],
expected: Set[T],
givenType: Type
): Alg[Option[Type]]
def expectNoExport(token: Token[S]): Alg[Unit]
def checkArgumentsNumber(token: Token[S], expected: Int, givenNum: Int): Alg[Boolean]

View File

@ -36,6 +36,7 @@ import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.{~>, Applicative}
import cats.syntax.option.*
import monocle.Lens
import monocle.macros.GenLens
@ -296,6 +297,21 @@ class TypesInterpreter[S[_], X](implicit
}
}
override def ensureTypeOneOf[T <: Type](
token: Token[S],
expected: Set[T],
givenType: Type
): State[X, Option[Type]] = expected
.find(_ acceptsValueOf givenType)
.fold(
reportError(
token,
"Types mismatch." ::
s"expected one of: ${expected.mkString(", ")}" ::
s"given: $givenType" :: Nil
).as(none)
)(_.some.pure)
override def expectNoExport(token: Token[S]): State[X, Unit] =
report(
token,

View File

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

View File

@ -106,46 +106,49 @@ object CompareTypes {
* -1 if left is a subtype of the right
*/
def apply(l: Type, r: Type): Double =
if (l == r) 0.0
else
(l, r) match {
case (TopType, _) | (_, BottomType) => 1.0
case (BottomType, _) | (_, TopType) => -1.0
(l, r) match {
case _ if l == r => 0.0
// 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 (x: ScalarType, LiteralType(ys, _)) if ys == Set(x) => 0.0
case (x: ScalarType, LiteralType(ys, _)) if ys(x) => 1.0
case (TopType, _) | (_, BottomType) => 1.0
case (BottomType, _) | (_, TopType) => -1.0
// Collections
case (x: ArrayType, y: ArrayType) => apply(x.element, y.element)
case (x: ArrayType, y: StreamType) => apply(x.element, y.element)
case (x: ArrayType, y: OptionType) => apply(x.element, y.element)
case (x: OptionType, y: OptionType) => apply(x.element, y.element)
case (x: OptionType, y: StreamType) => apply(x.element, y.element)
case (x: OptionType, y: ArrayType) => apply(x.element, y.element)
case (x: StreamType, y: StreamType) => apply(x.element, y.element)
case (lnt: AbilityType, rnt: AbilityType) => compareNamed(lnt.fields, rnt.fields)
case (lnt: StructType, rnt: StructType) => compareNamed(lnt.fields, rnt.fields)
// Collections
case (x: ArrayType, y: ArrayType) => apply(x.element, y.element)
case (x: ArrayType, y: StreamType) => apply(x.element, y.element)
case (x: ArrayType, y: OptionType) => apply(x.element, y.element)
case (x: OptionType, y: OptionType) => apply(x.element, y.element)
case (x: OptionType, y: StreamType) => apply(x.element, y.element)
case (x: OptionType, y: ArrayType) => apply(x.element, y.element)
case (x: StreamType, y: StreamType) => apply(x.element, y.element)
case (lnt: AbilityType, rnt: AbilityType) => compareNamed(lnt.fields, rnt.fields)
case (lnt: StructType, rnt: StructType) => compareNamed(lnt.fields, rnt.fields)
// Products
case (l: ProductType, r: ProductType) => compareProducts(l, r)
// 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 (x: ScalarType, LiteralType(ys, _)) if ys == Set(x) => 0.0
case (x: ScalarType, LiteralType(ys, _)) if ys(x) => 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
// Arrows
case (ArrowType(ldom, lcodom), ArrowType(rdom, rcodom)) =>
val cmpDom = apply(ldom, rdom)
val cmpCodom = apply(lcodom, rcodom)
// Products
case (l: ProductType, r: ProductType) => compareProducts(l, r)
if (cmpDom == 0 && cmpCodom == 0) 0
else if (cmpDom <= 0 && cmpCodom >= 0) 1.0
else if (cmpDom >= 0 && cmpCodom <= 0) -1.0
else NaN
// Arrows
case (ArrowType(ldom, lcodom), ArrowType(rdom, rcodom)) =>
val cmpDom = apply(ldom, rdom)
val cmpCodom = apply(lcodom, rcodom)
case _ =>
Double.NaN
}
if (cmpDom == 0 && cmpCodom == 0) 0
else if (cmpDom <= 0 && cmpCodom >= 0) 1.0
else if (cmpDom >= 0 && cmpCodom <= 0) -1.0
else NaN
case _ =>
Double.NaN
}
implicit val partialOrder: PartialOrder[Type] =
PartialOrder.from(CompareTypes.apply)

View File

@ -172,22 +172,30 @@ object ScalarType {
val string = ScalarType("string")
val float = Set(f32, f64)
val signed = float ++ Set(i8, i16, i32, i64)
val signed = Set(i8, i16, i32, i64)
val unsigned = Set(u8, u16, u32, u64)
val number = signed ++ unsigned
val integer = signed ++ unsigned
val number = float ++ integer
val all = number ++ Set(bool, string)
}
case class LiteralType private (oneOf: Set[ScalarType], name: String) extends DataType {
override def toString: String = s"$name:lt"
override def toString: String = s"$name literal"
}
object LiteralType {
val float = LiteralType(ScalarType.float, "float")
val signed = LiteralType(ScalarType.signed, "signed")
/*
* Literals without sign could be either signed or unsigned
* so `ScalarType.integer` is used here
*/
val unsigned = LiteralType(ScalarType.integer, "unsigned")
val number = LiteralType(ScalarType.number, "number")
val bool = LiteralType(Set(ScalarType.bool), "bool")
val string = LiteralType(Set(ScalarType.string), "string")
def forInt(n: Int): LiteralType = if (n < 0) signed else unsigned
}
sealed trait BoxType extends DataType {
@ -234,7 +242,8 @@ sealed trait NamedType extends Type {
}
// Struct is an unordered collection of labelled types
case class StructType(name: String, fields: NonEmptyMap[String, Type]) extends DataType with NamedType {
case class StructType(name: String, fields: NonEmptyMap[String, Type])
extends DataType with NamedType {
override def toString: String =
s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
@ -244,11 +253,11 @@ case class StructType(name: String, fields: NonEmptyMap[String, Type]) extends D
case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends NamedType {
lazy val arrows: Map[String, ArrowType] = fields.toNel.collect {
case (name, at@ArrowType(_, _)) => (name, at)
case (name, at @ ArrowType(_, _)) => (name, at)
}.toMap
lazy val abilities: List[(String, AbilityType)] = fields.toNel.collect {
case (name, at@AbilityType(_, _)) => (name, at)
case (name, at @ AbilityType(_, _)) => (name, at)
}
lazy val variables: List[(String, Type)] = fields.toNel.filter {

View File

@ -30,8 +30,6 @@ case class UniteTypes(scalarsCombine: ScalarsCombine.T) extends Monoid[Type]:
override def combine(a: Type, b: Type): Type =
(a, b) match {
case _ if CompareTypes(a, b) == 0.0 => a
case (ap: ProductType, bp: ProductType) =>
combineProducts(ap, bp)
@ -75,8 +73,7 @@ case class UniteTypes(scalarsCombine: ScalarsCombine.T) extends Monoid[Type]:
case 1.0 => a
case -1.0 => b
case 0.0 => a
case _ =>
TopType
case _ => TopType
}
}