diff --git a/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala b/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala index f6acb0135..1e688d2e1 100644 --- a/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala +++ b/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala @@ -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)) } diff --git a/integration-tests/aqua/examples/math.aqua b/integration-tests/aqua/examples/math.aqua index fecb2d2a7..544e8a830 100644 --- a/integration-tests/aqua/examples/math.aqua +++ b/integration-tests/aqua/examples/math.aqua @@ -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 \ No newline at end of file diff --git a/integration-tests/src/__test__/examples.spec.ts b/integration-tests/src/__test__/examples.spec.ts index 5a452e4fd..e72f8eadd 100644 --- a/integration-tests/src/__test__/examples.spec.ts +++ b/integration-tests/src/__test__/examples.spec.ts @@ -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]); diff --git a/integration-tests/src/examples/mathCall.ts b/integration-tests/src/examples/mathCall.ts index 7e908426d..ac57c9e50 100644 --- a/integration-tests/src/examples/mathCall.ts +++ b/integration-tests/src/examples/mathCall.ts @@ -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 { return await test1(); @@ -6,4 +6,20 @@ export async function mathTest1Call(): Promise { export async function mathTest2Call(): Promise { return await test2(); +} + +export async function mathTestI16Call(peer: string): Promise { + return await testI16(peer); +} + +export async function mathTestI32Call(peer: string): Promise { + return await testI32(peer); +} + +export async function mathTestI64Call(peer: string): Promise { + return await testI64(peer); +} + +export async function mathTestU64Call(peer: string): Promise { + return await testU64(peer); } \ No newline at end of file diff --git a/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala b/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala index 6b93b8fa5..3d463013f 100644 --- a/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala +++ b/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala @@ -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 diff --git a/model/src/main/scala/aqua/model/ValueModel.scala b/model/src/main/scala/aqua/model/ValueModel.scala index 565e0ec5e..547a0fccd 100644 --- a/model/src/main/scala/aqua/model/ValueModel.scala +++ b/model/src/main/scala/aqua/model/ValueModel.scala @@ -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 { diff --git a/model/transform/src/test/scala/aqua/model/transform/ModelBuilder.scala b/model/transform/src/test/scala/aqua/model/transform/ModelBuilder.scala index 3e5f4cecf..2ce792646 100644 --- a/model/transform/src/test/scala/aqua/model/transform/ModelBuilder.scala +++ b/model/transform/src/test/scala/aqua/model/transform/ModelBuilder.scala @@ -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 diff --git a/parser/src/main/scala/aqua/parser/lexer/ValueToken.scala b/parser/src/main/scala/aqua/parser/lexer/ValueToken.scala index 6ef8e8e5c..d9deb344b 100644 --- a/parser/src/main/scala/aqua/parser/lexer/ValueToken.scala +++ b/parser/src/main/scala/aqua/parser/lexer/ValueToken.scala @@ -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) } ) diff --git a/parser/src/test/scala/aqua/AquaSpec.scala b/parser/src/test/scala/aqua/AquaSpec.scala index e36eeb1c5..bf2b122c6 100644 --- a/parser/src/test/scala/aqua/AquaSpec.scala +++ b/parser/src/test/scala/aqua/AquaSpec.scala @@ -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) diff --git a/parser/src/test/scala/aqua/parser/AbilityIdExprSpec.scala b/parser/src/test/scala/aqua/parser/AbilityIdExprSpec.scala index bd2783d61..fb94aa7f1 100644 --- a/parser/src/test/scala/aqua/parser/AbilityIdExprSpec.scala +++ b/parser/src/test/scala/aqua/parser/AbilityIdExprSpec.scala @@ -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( diff --git a/parser/src/test/scala/aqua/parser/AbilityValueExprSpec.scala b/parser/src/test/scala/aqua/parser/AbilityValueExprSpec.scala index 4c272e97a..6374ad117 100644 --- a/parser/src/test/scala/aqua/parser/AbilityValueExprSpec.scala +++ b/parser/src/test/scala/aqua/parser/AbilityValueExprSpec.scala @@ -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) } } diff --git a/parser/src/test/scala/aqua/parser/InfixTokenSpec.scala b/parser/src/test/scala/aqua/parser/InfixTokenSpec.scala index bb85ef87c..310aee473 100644 --- a/parser/src/test/scala/aqua/parser/InfixTokenSpec.scala +++ b/parser/src/test/scala/aqua/parser/InfixTokenSpec.scala @@ -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) diff --git a/parser/src/test/scala/aqua/parser/StructValueExprSpec.scala b/parser/src/test/scala/aqua/parser/StructValueExprSpec.scala index ff1f65e7d..f9eb2974f 100644 --- a/parser/src/test/scala/aqua/parser/StructValueExprSpec.scala +++ b/parser/src/test/scala/aqua/parser/StructValueExprSpec.scala @@ -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 + ) } } diff --git a/parser/src/test/scala/aqua/parser/lexer/PropertyOpSpec.scala b/parser/src/test/scala/aqua/parser/lexer/PropertyOpSpec.scala index 6f5ca2242..727d1f612 100644 --- a/parser/src/test/scala/aqua/parser/lexer/PropertyOpSpec.scala +++ b/parser/src/test/scala/aqua/parser/lexer/PropertyOpSpec.scala @@ -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") ) ) diff --git a/parser/src/test/scala/aqua/parser/lexer/ValueTokenSpec.scala b/parser/src/test/scala/aqua/parser/lexer/ValueTokenSpec.scala index 0a2615145..6fe30809e 100644 --- a/parser/src/test/scala/aqua/parser/lexer/ValueTokenSpec.scala +++ b/parser/src/test/scala/aqua/parser/lexer/ValueTokenSpec.scala @@ -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.signed) + ) + ValueToken.`value`.parseAll("-1111").value.mapK(spanToId) should be( + LiteralToken[Id]("-1111", LiteralType.signed) + ) - 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.unsigned) + ) + ValueToken.`value`.parseAll("1111").value.mapK(spanToId) should be( + LiteralToken[Id]("1111", LiteralType.unsigned) + ) - ValueToken.`value`.parseAll("-1543").value.mapK(spanToId) should be(LiteralToken[Id]("-1543", LiteralType.signed)) + 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("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) diff --git a/semantics/src/main/scala/aqua/semantics/CompilerState.scala b/semantics/src/main/scala/aqua/semantics/CompilerState.scala index 3a98e1da3..366f76b7e 100644 --- a/semantics/src/main/scala/aqua/semantics/CompilerState.scala +++ b/semantics/src/main/scala/aqua/semantics/CompilerState.scala @@ -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")) diff --git a/semantics/src/main/scala/aqua/semantics/Semantics.scala b/semantics/src/main/scala/aqua/semantics/Semantics.scala index 0d6549f41..27ad39e05 100644 --- a/semantics/src/main/scala/aqua/semantics/Semantics.scala +++ b/semantics/src/main/scala/aqua/semantics/Semantics.scala @@ -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]] diff --git a/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala index a699c8730..d5ddca72c 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala @@ -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") + // 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.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") + (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 diff --git a/semantics/src/main/scala/aqua/semantics/rules/types/TypesAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/types/TypesAlgebra.scala index 59b867637..b278ab0ec 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/types/TypesAlgebra.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/types/TypesAlgebra.scala @@ -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] diff --git a/semantics/src/main/scala/aqua/semantics/rules/types/TypesInterpreter.scala b/semantics/src/main/scala/aqua/semantics/rules/types/TypesInterpreter.scala index ea671d63a..95273aa37 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/types/TypesInterpreter.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/types/TypesInterpreter.scala @@ -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, diff --git a/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala b/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala new file mode 100644 index 000000000..b01814503 --- /dev/null +++ b/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala @@ -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 + } + } + } +} diff --git a/types/src/main/scala/aqua/types/CompareTypes.scala b/types/src/main/scala/aqua/types/CompareTypes.scala index c32fd2c2c..74a9e9218 100644 --- a/types/src/main/scala/aqua/types/CompareTypes.scala +++ b/types/src/main/scala/aqua/types/CompareTypes.scala @@ -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 - - // 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 - - // 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) - - // Arrows - case (ArrowType(ldom, lcodom), ArrowType(rdom, rcodom)) => - val cmpDom = apply(ldom, rdom) - val cmpCodom = apply(lcodom, rcodom) - - 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 - } + (l, r) match { + case _ if l == r => 0.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) + + // 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 + + // Products + case (l: ProductType, r: ProductType) => compareProducts(l, r) + + // Arrows + case (ArrowType(ldom, lcodom), ArrowType(rdom, rcodom)) => + val cmpDom = apply(ldom, rdom) + val cmpCodom = apply(lcodom, rcodom) + + 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) diff --git a/types/src/main/scala/aqua/types/Type.scala b/types/src/main/scala/aqua/types/Type.scala index de4b6b0d1..4dfe58fd6 100644 --- a/types/src/main/scala/aqua/types/Type.scala +++ b/types/src/main/scala/aqua/types/Type.scala @@ -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 { diff --git a/types/src/main/scala/aqua/types/UniteTypes.scala b/types/src/main/scala/aqua/types/UniteTypes.scala index 32c2297f6..edbc74f68 100644 --- a/types/src/main/scala/aqua/types/UniteTypes.scala +++ b/types/src/main/scala/aqua/types/UniteTypes.scala @@ -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 } }