feat(compiler): Add boolean algebra [fixes LNG-211] (#814)

This commit is contained in:
InversionSpaces 2023-07-27 15:11:36 +04:00 committed by GitHub
parent fabf8d7d61
commit a5b6102422
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1313 additions and 450 deletions

View File

@ -0,0 +1,40 @@
aqua Bool
export main, Effector
service Effector("effector"):
effect(name: string) -> bool
func foo(x: i8) -> bool:
y = x + 1
<- y < 5
func bar(x: i8) -> i8:
y = x - 1
<- y
func main(peer: string) -> []bool:
res: *bool
on peer:
a = 1 + 2
b = 2 - 1
res <<- true || false && true -- true
res <<- (true || false) && true -- true
res <<- foo(3) && b > 0 || a > 4 -- true
res <<- bar(a) > 2 || true -- true
res <<- foo(4) && bar(2) < 2 -- false
res <<- !foo(10) && !!true -- true
res <<- !(bar(2) < 1) || !!(a < 2) -- true
-- Effector is only registered on init_peer
res <<- true || Effector.effect("impossible") -- true
res <<- !!false && Effector.effect("impossible") -- false
res <<- foo(0) || Effector.effect("impossible") -- true
res <<- foo(10) && Effector.effect("impossible") -- false
res <<- Effector.effect("true") || true -- true
res <<- Effector.effect("true") && false -- false
res <<- !foo(10) || Effector.effect("impossible") -- true
res <<- !(1 < 2) && !Effector.effect("impossible") -- false
<- res

View File

@ -1,103 +1,108 @@
import {Fluence, IFluenceClient, createClient} from '@fluencelabs/js-client.api';
import "@fluencelabs/js-client.node"
import {getObjAssignCall, getObjCall, getObjRelayCall} from "../examples/objectCall.js";
import {callArrowCall, reproArgsBug426Call} from '../examples/callArrowCall.js';
import {dataAliasCall} from '../examples/dataAliasCall.js';
import {onCall} from '../examples/onCall.js';
import {onPropagateCall, nestedOnPropagateCall, seqOnPropagateCall} from '../examples/onErrorPropagation.js';
import {funcCall} from '../examples/funcCall.js';
import {registerPrintln} from '../compiled/examples/println.js';
import {helloWorldCall} from '../examples/helloWorldCall.js';
import {foldBug499Call, foldCall} from '../examples/foldCall.js';
import {bugNG69Call, ifCall, ifWrapCall} from '../examples/ifCall.js';
import {parCall, testTimeoutCall} from '../examples/parCall.js';
import {complexCall} from '../examples/complex.js';
import {constantsCall, particleTtlAndTimestampCall} from '../examples/constantsCall.js';
import { Fluence, IFluenceClient, createClient } from '@fluencelabs/js-client.api';
import '@fluencelabs/js-client.node';
import { getObjAssignCall, getObjCall, getObjRelayCall } from '../examples/objectCall.js';
import { callArrowCall, reproArgsBug426Call } from '../examples/callArrowCall.js';
import { dataAliasCall } from '../examples/dataAliasCall.js';
import { onCall } from '../examples/onCall.js';
import { onPropagateCall, nestedOnPropagateCall, seqOnPropagateCall } from '../examples/onErrorPropagation.js';
import { funcCall } from '../examples/funcCall.js';
import { registerPrintln } from '../compiled/examples/println.js';
import { helloWorldCall } from '../examples/helloWorldCall.js';
import { foldBug499Call, foldCall } from '../examples/foldCall.js';
import { bugNG69Call, ifCall, ifWrapCall } from '../examples/ifCall.js';
import { parCall, testTimeoutCall } from '../examples/parCall.js';
import { complexCall } from '../examples/complex.js';
import { constantsCall, particleTtlAndTimestampCall } from '../examples/constantsCall.js';
import {
nilLengthCall,
nilLiteralCall,
returnNilCall,
returnNoneCall, streamAssignmentCall,
returnNoneCall,
streamAssignmentCall,
streamCall,
streamFunctorCall, streamIntFunctorCall, streamJoinCall,
streamReturnFromInnerFunc
streamFunctorCall,
streamIntFunctorCall,
streamJoinCall,
streamReturnFromInnerFunc,
} from '../examples/streamCall.js';
import {topologyBug205Call, topologyBug394Call, topologyBug427Call, topologyCall} from '../examples/topologyCall.js';
import {foldJoinCall} from '../examples/foldJoinCall.js';
import {registerHandlers, returnNull, returnOptionalCall, useOptionalCall} from '../examples/useOptionalCall.js';
import {viaArrCall, viaOptCall, viaOptNullCall, viaStreamCall} from '../examples/viaCall.js';
import {nestedFuncsCall} from '../examples/nestedFuncsCall.js';
import {assignmentCall} from '../examples/assignment.js';
import {tryCatchCall} from '../examples/tryCatchCall.js';
import {tryOtherwiseCall} from '../examples/tryOtherwiseCall.js';
import {coCall} from '../examples/coCall.js';
import {bugLNG60Call, passArgsCall} from '../examples/passArgsCall.js';
import {streamArgsCall} from '../examples/streamArgsCall.js';
import {streamResultsCall} from '../examples/streamResultsCall.js';
import {pushToStreamCall} from '../examples/pushToStreamCall.js';
import {literalCall} from '../examples/returnLiteralCall.js';
import {multiReturnCall} from '../examples/multiReturnCall.js';
import {declareCall} from '../examples/declareCall.js';
import {genOptions, genOptionsEmptyString} from '../examples/optionsCall.js';
import { topologyBug205Call, topologyBug394Call, topologyBug427Call, topologyCall } from '../examples/topologyCall.js';
import { foldJoinCall } from '../examples/foldJoinCall.js';
import { registerHandlers, returnNull, returnOptionalCall, useOptionalCall } from '../examples/useOptionalCall.js';
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 { tryCatchCall } from '../examples/tryCatchCall.js';
import { tryOtherwiseCall } from '../examples/tryOtherwiseCall.js';
import { coCall } from '../examples/coCall.js';
import { bugLNG60Call, passArgsCall } from '../examples/passArgsCall.js';
import { streamArgsCall } from '../examples/streamArgsCall.js';
import { streamResultsCall } from '../examples/streamResultsCall.js';
import { pushToStreamCall } from '../examples/pushToStreamCall.js';
import { literalCall } from '../examples/returnLiteralCall.js';
import { multiReturnCall } from '../examples/multiReturnCall.js';
import { declareCall } from '../examples/declareCall.js';
import { genOptions, genOptionsEmptyString } from '../examples/optionsCall.js';
import { lng193BugCall } from '../examples/closureReturnRename.js';
import {closuresCall} from '../examples/closures.js';
import {bugLNG63_2Call, bugLNG63_3Call, bugLNG63Call, streamCanCall} from '../examples/streamCanCall.js';
import {streamCallbackCall} from '../examples/streamCallback.js';
import {streamResCall} from '../examples/streamRestrictionsCall.js';
import {joinIdxCall, joinIdxLocalCall, joinIdxRelayCall} from '../examples/joinCall.js';
import {recursiveStreamsCall} from '../examples/recursiveStreamsCall.js';
import { closuresCall } from '../examples/closures.js';
import { bugLNG63_2Call, bugLNG63_3Call, bugLNG63Call, streamCanCall } from '../examples/streamCanCall.js';
import { streamCallbackCall } from '../examples/streamCallback.js';
import { streamResCall } from '../examples/streamRestrictionsCall.js';
import { joinIdxCall, joinIdxLocalCall, joinIdxRelayCall } from '../examples/joinCall.js';
import { recursiveStreamsCall } from '../examples/recursiveStreamsCall.js';
import { arraySugarCall, bugLNG59Call, optionSugarCall, streamSugarCall } from '../examples/collectionSugarCall.js';
import { funcsCall } from '../examples/funcsCall.js';
import { nestedDataCall } from '../examples/nestedDataCall.js';
import {
arraySugarCall,
bugLNG59Call,
optionSugarCall,
streamSugarCall,
} from '../examples/collectionSugarCall.js';
import {funcsCall} from '../examples/funcsCall.js';
import {nestedDataCall} from '../examples/nestedDataCall.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";
import {bugLng119Call} from "../examples/functorsCall.js";
import {returnArrowCall, returnArrowChainCall} from "../examples/returnArrowCall.js";
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';
import { bugLng119Call } from '../examples/functorsCall.js';
import { returnArrowCall, returnArrowChainCall } from '../examples/returnArrowCall.js';
var selfPeerId: string;
var peer1: IFluenceClient;
var peer2: IFluenceClient;
export const relay1 = config.relays[0]
const relayPeerId1 = relay1.peerId
export const relay2 = config.relays[1]
const relayPeerId2 = relay2.peerId
export const relay1 = config.relays[0];
const relayPeerId1 = relay1.peerId;
export const relay2 = config.relays[1];
const relayPeerId2 = relay2.peerId;
import log from 'loglevel';
import {abilityCall} from "../examples/abilityCall";
import { abilityCall } from '../examples/abilityCall';
// log.setDefaultLevel("debug")
async function start() {
console.log("CONNECTING TO FIRST:")
console.log('CONNECTING TO FIRST:');
Fluence.onConnectionStateChange((s) => {
console.log(s)
})
await Fluence.connect(relay1)
console.log(s);
});
await Fluence.connect(relay1);
const cl = await Fluence.getClient();
peer1 = cl;
selfPeerId = cl.getPeerId()
console.log("CONNECTED")
selfPeerId = cl.getPeerId();
console.log('CONNECTED');
peer2 = await createClient(relay2)
console.log("CONNECTING TO SECOND:")
peer2 = await createClient(relay2);
console.log('CONNECTING TO SECOND:');
peer2.onConnectionStateChange((s) => {
console.log(s)
})
await peer2.connect()
console.log("CONNECTED")
console.log(s);
});
await peer2.connect();
console.log('CONNECTED');
}
async function stop() {
await Fluence.disconnect();
if (peer2) {
await peer2.disconnect();
}
}
@ -116,7 +121,6 @@ describe('Testing examples', () => {
afterAll(async () => {
await stop();
});
it('callArrow.aqua args bug 426', async () => {
@ -135,12 +139,16 @@ describe('Testing examples', () => {
it('returnArrow.aqua chain', async () => {
let argResult = await returnArrowChainCall();
expect(argResult).toStrictEqual(
["first", "firstarg for func1 literal",
"second", "secondarg for func2 second literal",
"third", "thirdarg for func2 second literal",
"fourth", "fourth from second literal"
]);
expect(argResult).toStrictEqual([
'first',
'firstarg for func1 literal',
'second',
'secondarg for func2 second literal',
'third',
'thirdarg for func2 second literal',
'fourth',
'fourth from second literal',
]);
});
it('streamRestrictions.aqua', async () => {
@ -191,24 +199,24 @@ describe('Testing examples', () => {
});
it('stream.aqua return stream from inner func', async () => {
let streamResult = await streamReturnFromInnerFunc()
let streamResult = await streamReturnFromInnerFunc();
expect(streamResult).toEqual([1, 2, 3, 4]);
})
});
it('stream.aqua functor', async () => {
let streamResult = await streamFunctorCall()
expect(streamResult).toEqual("123");
})
let streamResult = await streamFunctorCall();
expect(streamResult).toEqual('123');
});
it('stream.aqua assignment', async () => {
let streamResult = await streamAssignmentCall()
expect(streamResult).toEqual("333");
})
let streamResult = await streamAssignmentCall();
expect(streamResult).toEqual('333');
});
it('stream.aqua nil literal', async () => {
let result = await nilLiteralCall()
let result = await nilLiteralCall();
expect(result).toEqual([]);
})
});
it('collectionSugar array', async () => {
let result = await arraySugarCall();
@ -219,35 +227,38 @@ describe('Testing examples', () => {
});
it('object creation getObj', async () => {
let result = await getObjCall()
let result = await getObjCall();
expect(result).toEqual({
str: "some str",
str: 'some str',
num: 5,
inner: {
arr: ["a", "b", "c"],
num: 6
}
arr: ['a', 'b', 'c'],
num: 6,
},
});
});
it('object creation getObjAssign', async () => {
let result = await getObjAssignCall()
expect(result).toEqual([{
str: "first str",
num: 5,
inner: {
arr: ["d", "e", "f"],
num: 7
}
}, {
str: "some str",
num: 6,
inner: {
arr: ["a", "b", "c"],
num: 7
}
},
1]);
let result = await getObjAssignCall();
expect(result).toEqual([
{
str: 'first str',
num: 5,
inner: {
arr: ['d', 'e', 'f'],
num: 7,
},
},
{
str: 'some str',
num: 6,
inner: {
arr: ['a', 'b', 'c'],
num: 7,
},
},
1,
]);
});
it('collectionSugar stream', async () => {
@ -340,7 +351,7 @@ describe('Testing examples', () => {
it('ability.aqua', async () => {
let result = await abilityCall();
expect(result).toStrictEqual(['declare_const123', "efre123", "declare_const123", 12]);
expect(result).toStrictEqual(['declare_const123', 'efre123', 'declare_const123', 12]);
});
it('functors.aqua LNG-119 bug', async () => {
@ -373,6 +384,27 @@ describe('Testing examples', () => {
expect(assignmentResult).toEqual(['abc', 'hello']);
});
it('boolAlgebra.aqua', async () => {
let boolAlgebraResult = await boolAlgebraCall(relayPeerId1);
expect(boolAlgebraResult).toEqual([
true,
true,
true,
true,
false,
true,
true,
true,
false,
true,
false,
true,
false,
true,
false,
]);
});
it('join.aqua local', async () => {
let joinLocalCallResult = await joinIdxLocalCall(relayPeerId1);
expect(joinLocalCallResult.length).toBeGreaterThanOrEqual(2);
@ -389,14 +421,14 @@ describe('Testing examples', () => {
});
it('stream.aqua nil length', async () => {
let result = await nilLengthCall()
let result = await nilLengthCall();
expect(result).toEqual(0);
})
});
it('stream.aqua int functor', async () => {
let streamResult = await streamIntFunctorCall()
expect(streamResult).toEqual("123");
})
let streamResult = await streamIntFunctorCall();
expect(streamResult).toEqual('123');
});
it('streamCan.aqua LNG-63', async () => {
let result = await bugLNG63Call();
@ -444,9 +476,9 @@ describe('Testing examples', () => {
});
it('stream.aqua join', async () => {
let streamResult = await streamJoinCall()
expect(streamResult).toEqual("444");
})
let streamResult = await streamJoinCall();
expect(streamResult).toEqual('444');
});
it('funcs.aqua', async () => {
let result = await funcsCall();
@ -501,22 +533,28 @@ describe('Testing examples', () => {
it('onErrorPropagate.aqua', async () => {
let call = onPropagateCall(peer2, relay2.peerId);
expect(call).rejects.toMatchObject({
message: expect.stringContaining("propagated error")
})
message: expect.stringContaining('propagated error'),
});
});
it('onErrorPropagate.aqua nested', async () => {
let call = nestedOnPropagateCall(peer2, relay2.peerId, config.relays[3].peerId, config.relays[4].peerId, config.relays[5].peerId);
let call = nestedOnPropagateCall(
peer2,
relay2.peerId,
config.relays[3].peerId,
config.relays[4].peerId,
config.relays[5].peerId,
);
expect(call).rejects.toMatchObject({
message: expect.stringContaining("propagated error")
})
message: expect.stringContaining('propagated error'),
});
});
it('onErrorPropagate.aqua sequential', async () => {
let call = seqOnPropagateCall(peer2, relay2.peerId, config.relays[3].peerId, config.relays[4].peerId);
expect(call).rejects.toMatchObject({
message: expect.stringContaining("propagated error")
})
message: expect.stringContaining('propagated error'),
});
});
it('complex.aqua', async () => {
@ -525,14 +563,14 @@ describe('Testing examples', () => {
});
it('object creation getObjRelay', async () => {
let result = await getObjRelayCall()
let result = await getObjRelayCall();
expect(result).toEqual({
str: "some str",
str: 'some str',
num: 5,
inner: {
arr: ["a", "b", "c"],
num: 6
}
arr: ['a', 'b', 'c'],
num: 6,
},
});
});
@ -560,7 +598,12 @@ describe('Testing examples', () => {
});
it('topology.aqua bug 394', async () => {
let topologyResult = await topologyBug394Call(peer1.getPeerId(), relay1.peerId, peer2.getPeerId(), relay2.peerId);
let topologyResult = await topologyBug394Call(
peer1.getPeerId(),
relay1.peerId,
peer2.getPeerId(),
relay2.peerId,
);
expect(topologyResult).toEqual(selfPeerId);
});
@ -582,8 +625,8 @@ describe('Testing examples', () => {
it('closureReturnRename.aqua bug LNG-193', async () => {
let result = await lng193BugCall();
expect(result).toEqual((1 + 42) + (2 + 42) + (3 + 42) + (4 + 42))
}, 20000)
expect(result).toEqual(1 + 42 + (2 + 42) + (3 + 42) + (4 + 42));
}, 20000);
it('closures.aqua', async () => {
let closuresResult = await closuresCall();
@ -618,6 +661,4 @@ describe('Testing examples', () => {
let joinCallResult = await joinIdxCall(relayPeerId1);
expect(joinCallResult.length).toBeGreaterThanOrEqual(2);
}, 10000);
});

View File

@ -1,73 +1,51 @@
import {
krasnodar,
stage,
testNet,
} from "@fluencelabs/fluence-network-environment";
import { local } from "./local-nodes.js";
import { krasnodar, stage, testNet } from '@fluencelabs/fluence-network-environment';
import { local } from './local-nodes.js';
declare global {
namespace NodeJS {
interface ProcessEnv {
FLUENCE_ENV?: string;
namespace NodeJS {
interface ProcessEnv {
FLUENCE_ENV?: string;
}
}
}
}
function setConfig(env) {
switch (env) {
case "krasnodar":
return { config: krasnodarConfig, isEphemeral: false };
case "testnet":
return { config: testNetConfig, isEphemeral: false };
case "ephemeral":
return { config: null, isEphemeral: true };
case "local":
return { config: localConfig, isEphemeral: false };
default:
return { config: stageConfig, isEphemeral: false };
}
switch (env) {
case 'krasnodar':
return { config: krasnodarConfig, isEphemeral: false };
case 'testnet':
return { config: testNetConfig, isEphemeral: false };
case 'ephemeral':
return { config: null, isEphemeral: true };
case 'local':
return { config: localConfig, isEphemeral: false };
default:
return { config: stageConfig, isEphemeral: false };
}
}
export const krasnodarConfig = {
relays: krasnodar,
externalAddressesRelay1: [
"/ip4/164.90.171.139/tcp/7770",
"/ip4/164.90.171.139/tcp/9990/ws",
],
externalAddressesRelay2: [
"/ip4/164.90.164.229/tcp/7001",
"/ip4/164.90.164.229/tcp/9001/ws",
],
tryCatchError:
"Local service error, ret_code is 1, error message is '\"Service with id 'unex' not found (function getStr)\"'",
relays: krasnodar,
externalAddressesRelay1: ['/ip4/164.90.171.139/tcp/7770', '/ip4/164.90.171.139/tcp/9990/ws'],
externalAddressesRelay2: ['/ip4/164.90.164.229/tcp/7001', '/ip4/164.90.164.229/tcp/9001/ws'],
tryCatchError:
"Local service error, ret_code is 1, error message is '\"Service with id 'unex' not found (function getStr)\"'",
};
export const stageConfig = {
relays: stage,
externalAddressesRelay1: [
"/ip4/134.209.186.43/tcp/7001",
"/ip4/134.209.186.43/tcp/9001/ws",
],
externalAddressesRelay2: [
"/ip4/134.209.186.43/tcp/7770",
"/ip4/134.209.186.43/tcp/9990/ws",
],
tryCatchError:
"Local service error, ret_code is 1, error message is '\"Service with id 'unex' not found (function getStr)\"'",
relays: stage,
externalAddressesRelay1: ['/ip4/134.209.186.43/tcp/7001', '/ip4/134.209.186.43/tcp/9001/ws'],
externalAddressesRelay2: ['/ip4/134.209.186.43/tcp/7770', '/ip4/134.209.186.43/tcp/9990/ws'],
tryCatchError:
"Local service error, ret_code is 1, error message is '\"Service with id 'unex' not found (function getStr)\"'",
};
export const testNetConfig = {
relays: testNet,
externalAddressesRelay1: [
"/ip4/165.227.164.206/tcp/7001",
"/ip4/165.227.164.206/tcp/9001/ws",
],
externalAddressesRelay2: [
"/ip4/142.93.169.49/tcp/7001",
"/ip4/142.93.169.49/tcp/9001/ws",
],
tryCatchError:
"Local service error, ret_code is 1, error message is '\"Service with id 'unex' not found (function getStr)\"'",
relays: testNet,
externalAddressesRelay1: ['/ip4/165.227.164.206/tcp/7001', '/ip4/165.227.164.206/tcp/9001/ws'],
externalAddressesRelay2: ['/ip4/142.93.169.49/tcp/7001', '/ip4/142.93.169.49/tcp/9001/ws'],
tryCatchError:
"Local service error, ret_code is 1, error message is '\"Service with id 'unex' not found (function getStr)\"'",
};
// export const ephemeralConfig = {
@ -82,21 +60,21 @@ export const testNetConfig = {
// };
export const localConfig = {
relays: local,
externalAddressesRelay1: [
"/ip4/10.50.10.10/tcp/7771",
"/ip4/10.50.10.10/tcp/9991/ws",
"/dns4/nox-1/tcp/7771",
"/dns4/nox-1/tcp/9991/ws",
],
externalAddressesRelay2: [
"/ip4/10.50.10.60/tcp/7776",
"/ip4/10.50.10.60/tcp/9996/ws",
"/dns4/nox-6/tcp/7776",
"/dns4/nox-6/tcp/9996/ws",
],
tryCatchError:
"Local service error, ret_code is 1, error message is '\"Service with id 'unex' not found (function getStr)\"'",
relays: local,
externalAddressesRelay1: [
'/ip4/10.50.10.10/tcp/7771',
'/ip4/10.50.10.10/tcp/9991/ws',
'/dns4/nox-1/tcp/7771',
'/dns4/nox-1/tcp/9991/ws',
],
externalAddressesRelay2: [
'/ip4/10.50.10.60/tcp/7776',
'/ip4/10.50.10.60/tcp/9996/ws',
'/dns4/nox-6/tcp/7776',
'/dns4/nox-6/tcp/9996/ws',
],
tryCatchError:
"Local service error, ret_code is 1, error message is '\"Service with id 'unex' not found (function getStr)\"'",
};
export const { config, isEphemeral } = setConfig("local");
export const { config, isEphemeral } = setConfig('local');

View File

@ -0,0 +1,12 @@
import { main, registerEffector } from '../compiled/examples/boolAlgebra.js';
export async function boolAlgebraCall(relay: string): Promise<boolean[]> {
registerEffector({
effect(name, _) {
if (name == 'true') return Promise.resolve(true);
else return Promise.reject(`unknown effect: ${name}`);
},
});
return await main(relay);
}

View File

@ -3,76 +3,76 @@ package aqua.model.inline
import aqua.model.{EmptyModel, OpModel, ParModel, SeqModel}
import aqua.raw.ops.RawTag
import aqua.raw.value.ValueRaw
import aqua.model.inline.Inline.MergeMode
import aqua.model.inline.Inline.MergeMode.*
import cats.Monoid
import cats.data.Chain
import cats.data.Chain.*
import cats.syntax.option.*
import scala.collection.immutable.ListMap
sealed trait MergeMode
object SeqMode extends MergeMode
object ParMode extends MergeMode
/**
* @param flattenValues values that need to be resolved before `predo`.
* ListMap for keeping order of values (mostly for debugging purposes)
* Inlining result
*
* @param predo operations tree
* @param mergeMode how `flattenValues` and `predo` must be merged
* @param mergeMode how `predo` must be merged
*/
private[inline] case class Inline(
flattenValues: ListMap[String, ValueRaw] = ListMap.empty,
predo: Chain[OpModel.Tree] = Chain.empty,
mergeMode: MergeMode = ParMode
) {
def desugar: Inline = {
val desugaredPredo =
predo.toList match {
case Nil => Chain.empty
case x :: Nil =>
Chain.one(x)
case l =>
mergeMode match
case SeqMode =>
val wrapped = SeqModel.wrap(l: _*)
wrapped match
case EmptyModel.leaf => Chain.empty
case _ => Chain.one(wrapped)
case ParMode => Chain.one(ParModel.wrap(l: _*))
}
val desugaredPredo = predo match {
case Chain.nil | _ ==: Chain.nil => predo
case chain =>
mergeMode match
case SeqMode =>
val wrapped = SeqModel.wrap(chain)
wrapped match
case EmptyModel.leaf => Chain.empty
case _ => Chain.one(wrapped)
case ParMode => Chain.one(ParModel.wrap(chain))
}
Inline(
flattenValues,
desugaredPredo
)
Inline(desugaredPredo)
}
def mergeWith(inline: Inline, mode: MergeMode): Inline = {
val left = desugar
val right = inline.desugar
Inline(left.flattenValues ++ right.flattenValues, left.predo ++ right.predo, mode)
Inline(left.predo ++ right.predo, mode)
}
}
// TODO may not be needed there
private[inline] object Inline {
val empty: Inline = Inline()
def preload(pairs: (String, ValueRaw)*): Inline = Inline(ListMap.from(pairs))
enum MergeMode {
case SeqMode
case ParMode
}
val empty: Inline = Inline()
def tree(tr: OpModel.Tree): Inline = Inline(predo = Chain.one(tr))
given Monoid[Inline] with
override val empty: Inline = Inline()
override val empty: Inline = Inline.empty
override def combine(a: Inline, b: Inline): Inline =
Inline(a.flattenValues ++ b.flattenValues, a.predo ++ b.predo)
// TODO: Is it ok to ignore merge mode?
Inline(a.predo ++ b.predo)
def parDesugarPrefix(ops: List[OpModel.Tree]): Option[OpModel.Tree] = ops match {
case Nil => None
case x :: Nil => Option(x)
case _ => Option(ParModel.wrap(ops: _*))
}
def parDesugarPrefix(ops: List[OpModel.Tree]): Option[OpModel.Tree] =
ops match {
case Nil => none
case x :: Nil => x.some
case _ => ParModel.wrap(ops).some
}
def parDesugarPrefixOpt(ops: Option[OpModel.Tree]*): Option[OpModel.Tree] =
parDesugarPrefix(ops.toList.flatten)

View File

@ -1,19 +1,29 @@
package aqua.model.inline
import aqua.model.{CallModel, CallServiceModel, LiteralModel, OpModel, SeqModel, ValueModel, VarModel}
import aqua.model.{
CallModel,
CallServiceModel,
LiteralModel,
OpModel,
SeqModel,
ValueModel,
VarModel
}
import aqua.model.inline.raw.RawInliner
import cats.data.Chain
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.value.{LiteralRaw, MakeStructRaw}
import cats.data.{NonEmptyMap, State}
import aqua.model.inline.Inline
import aqua.model.inline.RawValueInliner.{unfold, valueToModel}
import aqua.types.ScalarType
import cats.data.Chain
import cats.data.{NonEmptyMap, State}
import cats.syntax.traverse.*
import cats.syntax.monoid.*
import cats.syntax.functor.*
import cats.syntax.flatMap.*
import cats.syntax.apply.*
import cats.syntax.foldable.*
object MakeStructRawInliner extends RawInliner[MakeStructRaw] {
@ -45,15 +55,14 @@ object MakeStructRawInliner extends RawInliner[MakeStructRaw] {
name <- Mangler[S].findAndForbidName(raw.structType.name + "_obj")
foldedFields <- raw.fields.nonEmptyTraverse(unfold(_))
varModel = VarModel(name, raw.baseType)
valsInline = foldedFields.toSortedMap.values.map(_._2).fold(Inline.empty)(_ |+| _).desugar
fields = foldedFields.map(_._1)
valsInline = foldedFields.foldMap { case (_, inline) => inline }.desugar
fields = foldedFields.map { case (vm, _) => vm }
objCreation <- createObj(fields, varModel)
} yield {
(
varModel,
Inline(
valsInline.flattenValues,
Chain.one(SeqModel.wrap((valsInline.predo :+ objCreation).toList: _*))
Chain.one(SeqModel.wrap(valsInline.predo :+ objCreation))
)
)
}

View File

@ -1,11 +1,22 @@
package aqua.model.inline
import aqua.model.inline.state.{Arrows, Counter, Exports, Mangler}
import aqua.model.inline.Inline.MergeMode.*
import aqua.model.*
import aqua.model.inline.raw.{ApplyFunctorRawInliner, ApplyGateRawInliner, ApplyPropertiesRawInliner, CallArrowRawInliner, CollectionRawInliner, MakeAbilityRawInliner}
import aqua.model.inline.raw.{
ApplyBinaryOpRawInliner,
ApplyFunctorRawInliner,
ApplyGateRawInliner,
ApplyPropertiesRawInliner,
ApplyUnaryOpRawInliner,
CallArrowRawInliner,
CollectionRawInliner,
MakeAbilityRawInliner
}
import aqua.raw.ops.*
import aqua.raw.value.*
import aqua.types.{ArrayType, LiteralType, OptionType, StreamType}
import cats.syntax.traverse.*
import cats.syntax.monoid.*
import cats.syntax.functor.*
@ -13,6 +24,7 @@ import cats.syntax.flatMap.*
import cats.syntax.apply.*
import cats.instances.list.*
import cats.data.{Chain, State, StateT}
import cats.syntax.applicative.*
import scribe.Logging
object RawValueInliner extends Logging {
@ -42,8 +54,14 @@ object RawValueInliner extends Logging {
case dr: MakeStructRaw =>
MakeStructRawInliner(dr, propertiesAllowed)
case sr: AbilityRaw =>
MakeAbilityRawInliner(sr, propertiesAllowed)
case ar: AbilityRaw =>
MakeAbilityRawInliner(ar, propertiesAllowed)
case auor: ApplyUnaryOpRaw =>
ApplyUnaryOpRawInliner(auor, propertiesAllowed)
case abbor: ApplyBinaryOpRaw =>
ApplyBinaryOpRawInliner(abbor, propertiesAllowed)
case cr: CallArrowRaw =>
CallArrowRawInliner(cr, propertiesAllowed)
@ -51,22 +69,11 @@ object RawValueInliner extends Logging {
private[inline] def inlineToTree[S: Mangler: Exports: Arrows](
inline: Inline
): State[S, List[OpModel.Tree]] = {
inline.flattenValues.toList.traverse { case (name, v) =>
valueToModel(v).map {
case (vv, Some(op)) =>
SeqModel.wrap(op, FlattenModel(vv, name).leaf)
case (vv, _) =>
FlattenModel(vv, name).leaf
}
}.map { predo =>
inline.mergeMode match
case SeqMode =>
SeqModel.wrap((inline.predo.toList ++ predo): _*) :: Nil
case ParMode => inline.predo.toList ::: predo
}
}
): State[S, List[OpModel.Tree]] =
(inline.mergeMode match {
case SeqMode => SeqModel.wrap(inline.predo) :: Nil
case ParMode => inline.predo.toList
}).pure
private[inline] def toModel[S: Mangler: Exports: Arrows](
unfoldF: State[S, (ValueModel, Inline)]

View File

@ -0,0 +1,111 @@
package aqua.model.inline.raw
import aqua.model.*
import aqua.model.inline.raw.RawInliner
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.value.{AbilityRaw, LiteralRaw, MakeStructRaw}
import cats.data.{NonEmptyList, NonEmptyMap, State}
import aqua.model.inline.Inline
import aqua.model.inline.RawValueInliner.{unfold, valueToModel}
import aqua.types.{ArrowType, ScalarType}
import aqua.raw.value.ApplyBinaryOpRaw
import aqua.raw.value.ApplyBinaryOpRaw.Op.*
import cats.data.Chain
import cats.syntax.traverse.*
import cats.syntax.monoid.*
import cats.syntax.functor.*
import cats.syntax.flatMap.*
import cats.syntax.apply.*
import cats.syntax.foldable.*
import cats.syntax.applicative.*
object ApplyBinaryOpRawInliner extends RawInliner[ApplyBinaryOpRaw] {
override def apply[S: Mangler: Exports: Arrows](
raw: ApplyBinaryOpRaw,
propertiesAllowed: Boolean
): State[S, (ValueModel, Inline)] = for {
left <- unfold(raw.left)
(lmodel, linline) = left
right <- unfold(raw.right)
(rmodel, rinline) = right
result <- (lmodel, rmodel) match {
// Optimize in case of left value is known at compile time
case (LiteralModel.Bool(lvalue), _) =>
(raw.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 {
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)
}
} yield result
private def fullInline[S: Mangler: Exports: Arrows](
lmodel: ValueModel,
rmodel: ValueModel,
linline: Inline,
rinline: Inline,
op: ApplyBinaryOpRaw.Op
): State[S, (ValueModel, Inline)] = {
val (name, compareWith) = op match {
case And => ("and", false)
case Or => ("or", true)
}
/**
* (seq
* <left-inline>
* (xor
* (match <left-res> <compare-with>
* (ap <left-res> <res-name>)
* )
* (seq
* <right-inline>
* (ap <right-res> <res-name>)
* )
* )
* )
*/
val predo = (resName: String) =>
SeqModel.wrap(
linline.predo :+ XorModel.wrap(
MatchMismatchModel(
lmodel,
LiteralModel.bool(compareWith),
shouldMatch = true
).wrap(
FlattenModel(
lmodel,
resName
).leaf
),
SeqModel.wrap(
rinline.predo :+ FlattenModel(
rmodel,
resName
).leaf
)
)
)
Mangler[S]
.findAndForbidName(name)
.map(resName =>
(
VarModel(resName, ScalarType.bool),
Inline(Chain.one(predo(resName)))
)
)
}
}

View File

@ -10,7 +10,8 @@ import aqua.model.{
ValueModel,
VarModel
}
import aqua.model.inline.{Inline, SeqMode}
import aqua.model.inline.Inline.MergeMode.*
import aqua.model.inline.Inline
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.value.{FunctorRaw, ValueRaw}
import cats.data.State
@ -53,10 +54,12 @@ object ApplyFunctorRawInliner extends Logging {
}
} yield {
val tree = Inline(
predo = Chain.one(SeqModel.wrap(
flat,
FlattenModel(apVar, resultName).leaf
)),
predo = Chain.one(
SeqModel.wrap(
flat,
FlattenModel(apVar, resultName).leaf
)
),
mergeMode = SeqMode
)

View File

@ -121,8 +121,7 @@ object ApplyGateRawInliner extends RawInliner[ApplyGateRaw] with Logging {
val tree = SeqModel.wrap(idxInline.predo.toList :+ gate)
val treeInline =
Inline(idxInline.flattenValues, predo = Chain.one(tree))
val treeInline = Inline(predo = Chain.one(tree))
(
VarModel(uniqueResultName, ArrayType(afr.streamType.element)),

View File

@ -1,12 +1,22 @@
package aqua.model.inline.raw
import aqua.model.{CallModel, CallServiceModel, LiteralModel, OpModel, SeqModel, ValueModel, VarModel}
import aqua.model.inline.{Inline, SeqMode, TagInliner}
import aqua.model.{
CallModel,
CallServiceModel,
LiteralModel,
OpModel,
SeqModel,
ValueModel,
VarModel
}
import aqua.model.inline.Inline.MergeMode.*
import aqua.model.inline.{Inline, TagInliner}
import aqua.model.inline.MakeStructRawInliner.createObj
import aqua.model.inline.RawValueInliner.unfold
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.value.{IntoCopyRaw, LiteralRaw}
import aqua.types.ScalarType
import cats.data.{Chain, NonEmptyMap, State}
import scribe.Logging
import cats.syntax.traverse.*
@ -14,6 +24,7 @@ import cats.syntax.monoid.*
import cats.syntax.functor.*
import cats.syntax.flatMap.*
import cats.syntax.apply.*
import cats.syntax.foldable.*
object ApplyIntoCopyRawInliner extends Logging {
@ -45,15 +56,14 @@ object ApplyIntoCopyRawInliner extends Logging {
name <- Mangler[S].findAndForbidName(value.name + "_obj_copy")
foldedFields <- intoCopy.fields.nonEmptyTraverse(unfold(_))
varModel = VarModel(name, value.baseType)
valsInline = foldedFields.toSortedMap.values.map(_._2).fold(Inline.empty)(_ |+| _).desugar
valsInline = foldedFields.toList.foldMap { case (_, inline) => inline }.desugar
fields = foldedFields.map(_._1)
objCopy <- copyObj(value, fields, varModel)
} yield {
(
varModel,
Inline(
valsInline.flattenValues,
Chain.one(SeqModel.wrap((valsInline.predo :+ objCopy).toList: _*)),
Chain.one(SeqModel.wrap(valsInline.predo :+ objCopy)),
SeqMode
)
)

View File

@ -20,7 +20,7 @@ import aqua.model.{
XorModel
}
import aqua.model.inline.Inline
import aqua.model.inline.{ParMode, SeqMode}
import aqua.model.inline.Inline.MergeMode.*
import aqua.model.inline.RawValueInliner.unfold
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.value.{
@ -48,10 +48,14 @@ import aqua.types.{
StreamType,
Type
}
import cats.Eval
import cats.syntax.bifunctor.*
import cats.data.{Chain, IndexedStateT, State}
import cats.syntax.monoid.*
import cats.syntax.traverse.*
import cats.syntax.foldable.*
import cats.Id
import cats.instances.list.*
import scribe.Logging
@ -183,7 +187,6 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
newVI <- ApplyFunctorRawInliner(flatten, f)
} yield {
newVI._1 -> Inline(
inline.flattenValues ++ newVI._2.flattenValues,
inline.predo ++ newVI._2.predo,
mergeMode = SeqMode
)
@ -198,7 +201,6 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
newVI <- ApplyIntoCopyRawInliner(varModel, ic)
} yield {
newVI._1 -> Inline(
inline.flattenValues ++ newVI._2.flattenValues,
inline.predo ++ newVI._2.predo,
mergeMode = SeqMode
)
@ -218,7 +220,6 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
case (vm @ VarModel(_, _, _), inline) if vm.properties.nonEmpty =>
removeProperties(vm).map { case (vf, inlf) =>
PropertyRawWithModel(iir, Option(IntoIndexModel(vf.name, t))) -> Inline(
inline.flattenValues ++ inlf.flattenValues,
inline.predo ++ inlf.predo,
mergeMode = SeqMode
)
@ -230,11 +231,7 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
}
case p => State.pure(PropertyRawWithModel(p, None) -> Inline.empty)
}.sequence.map { (propsWithInline: Chain[(PropertyRawWithModel, Inline)]) =>
val fullInline = propsWithInline.map(_._2).foldLeft(Inline.empty)(_ |+| _)
val props = propsWithInline.map(_._1)
(props, fullInline)
}
}.sequence.map(_.toList.unzip.bimap(Chain.fromSeq, _.combineAll))
}
private def unfoldProperties[S: Mangler: Exports: Arrows](
@ -254,7 +251,6 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
(
vm,
Inline(
leftInline.flattenValues ++ inl.flattenValues,
leftInline.predo ++ inl.predo,
mergeMode = SeqMode
)
@ -269,7 +265,6 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
case (v, i) if !propertiesAllowed && v.properties.nonEmpty =>
removeProperties(v).map { case (vf, inlf) =>
vf -> Inline(
leftInline.flattenValues ++ i.flattenValues ++ inlf.flattenValues,
leftInline.predo ++ i.predo ++ inlf.predo,
mergeMode = SeqMode
)
@ -277,7 +272,6 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
case (v, i) =>
State.pure(
v -> Inline(
leftInline.flattenValues ++ i.flattenValues,
leftInline.predo ++ i.predo,
mergeMode = SeqMode
)
@ -304,7 +298,6 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
unfoldProperties(gateResInline, gateResVal, properties, propertiesAllowed).map {
case (v, i) =>
v -> Inline(
inl.flattenValues ++ i.flattenValues,
inl.predo ++ i.predo,
mergeMode = SeqMode
)

View File

@ -0,0 +1,92 @@
package aqua.model.inline.raw
import aqua.model.*
import aqua.model.inline.raw.RawInliner
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.value.{AbilityRaw, LiteralRaw, MakeStructRaw}
import cats.data.{NonEmptyList, NonEmptyMap, State}
import aqua.model.inline.Inline
import aqua.model.inline.RawValueInliner.{unfold, valueToModel}
import aqua.types.{ArrowType, ScalarType}
import aqua.raw.value.ApplyUnaryOpRaw
import aqua.raw.value.ApplyUnaryOpRaw.Op.*
import cats.data.Chain
import cats.syntax.traverse.*
import cats.syntax.monoid.*
import cats.syntax.functor.*
import cats.syntax.flatMap.*
import cats.syntax.apply.*
import cats.syntax.foldable.*
import cats.syntax.applicative.*
object ApplyUnaryOpRawInliner extends RawInliner[ApplyUnaryOpRaw] {
override def apply[S: Mangler: Exports: Arrows](
raw: ApplyUnaryOpRaw,
propertiesAllowed: Boolean
): State[S, (ValueModel, Inline)] = for {
value <- unfold(raw.value)
(vm, vinline) = value
result <- vm match {
// Optimize in case of value is known at compile time
case LiteralModel.Bool(bvalue) =>
(raw.op match {
case Not => (LiteralModel.bool(!bvalue), vinline)
}).pure[State[S, *]]
// Produce unoptimized inline
case _ => fullInline(vm, vinline, raw.op)
}
} yield result
private def fullInline[S: Mangler: Exports: Arrows](
vm: ValueModel,
vinline: Inline,
op: ApplyUnaryOpRaw.Op
): State[S, (ValueModel, Inline)] = {
val name = op match {
case Not => "not"
}
/*
* (seq
* <value-inline>
* (xor
* (match <value-res> true
* (ap false <res-name>)
* )
* (ap true <res-name>)
* )
* )
*/
val predo = (resName: String) =>
SeqModel.wrap(
vinline.predo :+ XorModel.wrap(
MatchMismatchModel(
vm,
LiteralModel.bool(true),
shouldMatch = true
).wrap(
FlattenModel(
LiteralModel.bool(false),
resName
).leaf
),
FlattenModel(
LiteralModel.bool(true),
resName
).leaf
)
)
Mangler[S]
.findAndForbidName(name)
.map(resName =>
(
VarModel(resName, ScalarType.bool),
Inline(Chain.one(predo(resName)))
)
)
}
}

View File

@ -29,11 +29,10 @@ object CallArrowRawInliner extends RawInliner[CallArrowRaw] with Logging {
cd <- callToModel(call, true)
sd <- valueToModel(serviceId)
} yield cd._1.exportTo.map(_.asVar.resolveWith(exports)) -> Inline(
ListMap.empty,
Chain(
SeqModel.wrap(
sd._2.toList ++
cd._2.toList :+ CallServiceModel(sd._1, value.name, cd._1).leaf: _*
cd._2.toList :+ CallServiceModel(sd._1, value.name, cd._1).leaf
)
)
)
@ -58,7 +57,6 @@ object CallArrowRawInliner extends RawInliner[CallArrowRaw] with Logging {
.callArrowRet(fn, cm)
.map { case (body, vars) =>
vars -> Inline(
ListMap.empty,
Chain.one(
// Leave meta information in tree after inlining
MetaModel

View File

@ -22,6 +22,7 @@ import cats.syntax.monoid.*
import cats.syntax.functor.*
import cats.syntax.flatMap.*
import cats.syntax.apply.*
import cats.syntax.foldable.*
object MakeAbilityRawInliner extends RawInliner[AbilityRaw] {
@ -33,16 +34,14 @@ object MakeAbilityRawInliner extends RawInliner[AbilityRaw] {
name <- Mangler[S].findAndForbidName(raw.abilityType.name + "_ab")
foldedFields <- raw.fieldsAndArrows.nonEmptyTraverse(unfold(_))
varModel = VarModel(name, raw.baseType)
valsInline = foldedFields.toSortedMap.values.map(_._2).fold(Inline.empty)(_ |+| _).desugar
_ <- foldedFields.map(_._1).toNel.toList.traverse { case (n, vm) =>
val namef = s"$name.$n"
Exports[S].resolved(namef, vm)
valsInline = foldedFields.toList.foldMap { case (_, inline) => inline }.desugar
_ <- foldedFields.toNel.traverse { case (n, (vm, _)) =>
Exports[S].resolved(s"$name.$n", vm)
}
} yield {
(
varModel,
Inline(
valsInline.flattenValues,
Chain.one(SeqModel.wrap(valsInline.predo))
)
)

View File

@ -22,6 +22,8 @@ import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import scala.collection.immutable.SortedMap
import aqua.raw.value.ApplyBinaryOpRaw
import aqua.raw.value.CallArrowRaw
class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
@ -127,9 +129,8 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
"raw value inliner" should "desugarize a single non-recursive raw value" in {
// x[y]
valueToModel[InliningState](`raw x[y]`)
.run(InliningState(noNames = Set("x", "y")))
.value
._2 should be(
.runA(InliningState(noNames = Set("x", "y")))
.value shouldBe (
VarModel(
"x",
ArrayType(ScalarType.string),
@ -143,13 +144,12 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
import aqua.model.inline.state.Mangler.Simple
// a.field1.field2
valueToModel[InliningState](`raw res.c`)
.run(
.runA(
InliningState(resolvedExports =
Map("res" -> VarModel("a", aType, Chain.one(IntoFieldModel("b", bType))))
)
)
.value
._2 should be(
.value shouldBe (
VarModel(
"a",
aType,
@ -158,14 +158,13 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
)
}
"raw value inliner" should "desugarize a single recursive raw value" in {
it should "desugarize a single recursive raw value" in {
// x[ys!]
val (resVal, resTree) = valueToModel[InliningState](
`raw x[ys[0]]`
)
.run(InliningState(noNames = Set("x", "ys")))
.runA(InliningState(noNames = Set("x", "ys")))
.value
._2
resVal should be(
VarModel(
@ -189,10 +188,10 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
) should be(true)
}
"raw value inliner" should "desugarize properties with functors x[ys[ys.length]][2] and make proper flattener tags" in {
it should "desugarize properties with functors x[ys[ys.length]][2] and make proper flattener tags" in {
val (resVal, resTree) = valueToModel[InliningState](
`x[xs[ys.length]][xss[yss.length]]`
).run(InliningState(noNames = Set("x", "ys", "xs", "yss", "xss"))).value._2
).runA(InliningState(noNames = Set("x", "ys", "xs", "yss", "xss"))).value
resVal should be(
VarModel(
@ -263,13 +262,12 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
) should be(true)
}
"raw value inliner" should "desugarize x[ys[0]][ys[1]] and make proper flattener tags" in {
it should "desugarize x[ys[0]][ys[1]] and make proper flattener tags" in {
val (resVal, resTree) = valueToModel[InliningState](
`raw x[ys[0]][ys[1]]`
)
.run(InliningState(noNames = Set("x", "ys")))
.runA(InliningState(noNames = Set("x", "ys")))
.value
._2
resVal should be(
VarModel(
@ -306,16 +304,15 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
) should be(true)
}
"raw value inliner" should "desugarize stream with gate" in {
it should "desugarize stream with gate" in {
val streamWithProps =
VarRaw("x", StreamType(ScalarType.string)).withProperty(
IntoIndexRaw(ysVarRaw(1), ScalarType.string)
)
val (resVal, resTree) = valueToModel[InliningState](streamWithProps)
.run(InliningState(noNames = Set("x", "ys")))
.runA(InliningState(noNames = Set("x", "ys")))
.value
._2
resVal should be(
VarModel(
@ -328,28 +325,23 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
)
}
"raw value inliner" should "desugarize stream with length" in {
it should "desugarize stream with length" in {
val streamWithProps =
VarRaw("x", StreamType(ScalarType.string)).withProperty(
FunctorRaw("length", ScalarType.u32)
)
val (resVal, resTree) = valueToModel[InliningState](streamWithProps)
.run(InliningState(noNames = Set("x", "ys")))
.runA(InliningState(noNames = Set("x", "ys")))
.value
._2
// println(resVal)
// println(resTree)
}
"raw value inliner" should "desugarize a recursive lambda value" in {
it should "desugarize a recursive lambda value" in {
val (resVal, resTree) = valueToModel[InliningState](
`raw x[zs[ys[0]]][ys[1]]`
)
.run(InliningState(noNames = Set("x", "ys", "zs")))
.runA(InliningState(noNames = Set("x", "ys", "zs")))
.value
._2
// This is x[zs-0][ys-0]
// zs-0 should be zs[ys[0]], which should be already flattened
@ -396,5 +388,4 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers {
)
) should be(true)
}
}

View File

@ -179,6 +179,56 @@ case class AbilityRaw(fieldsAndArrows: NonEmptyMap[String, ValueRaw], abilityTyp
copy(fieldsAndArrows = fieldsAndArrows.map(_.renameVars(map)))
}
case class ApplyBinaryOpRaw(
op: ApplyBinaryOpRaw.Op,
left: ValueRaw,
right: ValueRaw
) extends ValueRaw {
// Only boolean operations are supported for now
override def baseType: Type = ScalarType.bool
override def map(f: ValueRaw => ValueRaw): ValueRaw =
f(copy(left = f(left), right = f(right)))
override def varNames: Set[String] = left.varNames ++ right.varNames
override def renameVars(map: Map[String, String]): ValueRaw =
copy(left = left.renameVars(map), right = right.renameVars(map))
}
object ApplyBinaryOpRaw {
enum Op {
case And
case Or
}
}
case class ApplyUnaryOpRaw(
op: ApplyUnaryOpRaw.Op,
value: ValueRaw
) extends ValueRaw {
// Only boolean operations are supported for now
override def baseType: Type = ScalarType.bool
override def map(f: ValueRaw => ValueRaw): ValueRaw =
f(copy(value = f(value)))
override def varNames: Set[String] = value.varNames
override def renameVars(map: Map[String, String]): ValueRaw =
copy(value = value.renameVars(map))
}
object ApplyUnaryOpRaw {
enum Op {
case Not
}
}
case class CallArrowRaw(
// TODO: ability should hold a type, not name
ability: Option[String],

View File

@ -62,6 +62,19 @@ case class LiteralModel(value: String, `type`: Type) extends ValueModel {
object LiteralModel {
/**
* Used to match bool literals in pattern matching
*/
object Bool {
def unapply(lm: LiteralModel): Option[Boolean] =
lm match {
case LiteralModel("true", ScalarType.bool | LiteralType.bool) => true.some
case LiteralModel("false", ScalarType.bool | LiteralType.bool) => false.some
case _ => none
}
}
// AquaVM will return empty string for
// %last_error%.$.error_code if there is no %last_error%
val emptyErrorCode = quote("")
@ -71,6 +84,8 @@ object LiteralModel {
def quote(str: String): LiteralModel = LiteralModel(s"\"$str\"", LiteralType.string)
def number(n: Int): LiteralModel = LiteralModel(n.toString, LiteralType.forInt(n))
def bool(b: Boolean): LiteralModel = LiteralModel(b.toString.toLowerCase, LiteralType.bool)
}
sealed trait PropertyModel {

View File

@ -3,17 +3,19 @@ package aqua.parser.lexer
import aqua.parser.Expr
import aqua.parser.head.FilenameExpr
import aqua.parser.lexer.Token.*
import aqua.parser.lexer.ValueToken.{initPeerId, literal}
import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser.*
import aqua.types.LiteralType
import aqua.parser.lift.Span
import aqua.parser.lift.Span.{P0ToSpan, PToSpan, S}
import cats.parse.{Numbers, Parser as P, Parser0 as P0}
import cats.syntax.comonad.*
import cats.syntax.functor.*
import cats.{~>, Comonad, Functor}
import cats.data.{NonEmptyList, NonEmptyMap}
import aqua.parser.lift.Span
import aqua.parser.lift.Span.{P0ToSpan, PToSpan, S}
import cats.syntax.foldable.*
import cats.arrow.FunctionK
sealed trait ValueToken[F[_]] extends Token[F] {
def mapK[K[_]: Comonad](fk: F ~> K): ValueToken[K]
@ -156,29 +158,60 @@ case class InfixToken[F[_]: Comonad](
object InfixToken {
import ValueToken._
enum BoolOp(val symbol: String):
case Or extends BoolOp("||")
case And extends BoolOp("&&")
enum MathOp(val symbol: String):
case Pow extends MathOp("**")
case Mul extends MathOp("*")
case Div extends MathOp("/")
case Rem extends MathOp("%")
case Add extends MathOp("+")
case Sub extends MathOp("-")
enum CmpOp(val symbol: String):
case Gt extends CmpOp(">")
case Gte extends CmpOp(">=")
case Lt extends CmpOp("<")
case Lte extends CmpOp("<=")
enum Op(val symbol: String):
case Pow extends Op("**")
case Mul extends Op("*")
case Div extends Op("/")
case Rem extends Op("%")
case Add extends Op("+")
case Sub extends Op("-")
case Gt extends Op(">")
case Gte extends Op(">=")
case Lt extends Op("<")
case Lte extends Op("<=")
/**
* Scala3 does not support nested enums with fields
* so this type acrobatics is used to enable exhaustive matching check
*/
case Math(mathOp: MathOp) extends Op(mathOp.symbol)
case Cmp(cmpOp: CmpOp) extends Op(cmpOp.symbol)
case Bool(boolOp: BoolOp) extends Op(boolOp.symbol)
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)
val Pow = Math(MathOp.Pow)
val Mul = Math(MathOp.Mul)
val Div = Math(MathOp.Div)
val Rem = Math(MathOp.Rem)
val Add = Math(MathOp.Add)
val Sub = Math(MathOp.Sub)
val math = MathOp.values.map(Math(_)).toList
val Gt = Cmp(CmpOp.Gt)
val Gte = Cmp(CmpOp.Gte)
val Lt = Cmp(CmpOp.Lt)
val Lte = Cmp(CmpOp.Lte)
val cmp = CmpOp.values.map(Cmp(_)).toList
val And = Bool(BoolOp.And)
val Or = Bool(BoolOp.Or)
val bool = BoolOp.values.map(Bool(_)).toList
}
private def opsParser(ops: List[Op]): P[(Span, Op)] =
P.oneOf(ops.map(op => op.p.lift.map(s => s.as(op))))
P.oneOf(ops.map(op => op.p.lift.map(_.as(op))))
// Parse left-associative operations `basic (OP basic)*`.
// We use this form to avoid left recursion.
@ -209,26 +242,8 @@ object InfixToken {
vt
}
def brackets(basic: P[ValueToken[Span.S]]): P[ValueToken[Span.S]] =
basic.between(`(`, `)`).backtrack
// One element of math expression
val atom: P[ValueToken[S]] = P.oneOf(
literal.backtrack ::
initPeerId.backtrack ::
P.defer(
CollectionToken.collection
) ::
P.defer(NamedValueToken.dataValue).backtrack ::
P.defer(CallArrowToken.callArrow).backtrack ::
P.defer(abProperty).backtrack ::
P.defer(brackets(InfixToken.mathExpr)).backtrack ::
varProperty ::
Nil
)
private val pow: P[ValueToken[Span.S]] =
infixParserRight(atom, Op.Pow :: Nil)
infixParserRight(P.defer(ValueToken.atom), Op.Pow :: Nil)
private val mult: P[ValueToken[Span.S]] =
infixParserLeft(pow, Op.Mul :: Op.Div :: Op.Rem :: Nil)
@ -245,6 +260,12 @@ object InfixToken {
Op.Gte :: Op.Lte :: Op.Gt :: Op.Lt :: Nil
)
private val and: P[ValueToken[Span.S]] =
infixParserLeft(compare, Op.And :: Nil)
private val or: P[ValueToken[Span.S]] =
infixParserLeft(and, Op.Or :: Nil)
/**
* The math expression parser.
*
@ -291,9 +312,17 @@ object InfixToken {
*
* The grammar below expresses the operator precedence and associativity we expect from math expressions:
*
* -- Comparison is the entry point because it has the lowest priority.
* -- Logical OR is the entry point because it has the lowest priority.
* mathExpr
* -> cmpExpr
* -> orExpr
*
* -- Logical OR is left associative.
* orExpr
* -> andExpr OR_OP andExpr
*
* -- Logical AND is left associative.
* andExpr
* -> cmpExpr AND_OP cmpExpr
*
* -- Comparison isn't an associative operation so it's not a recursive definition.
* cmpExpr
@ -326,19 +355,51 @@ object InfixToken {
* | ...
* | ( mathExpr )
*/
val mathExpr: P[ValueToken[Span.S]] = compare
val value: P[ValueToken[Span.S]] = or
}
case class PrefixToken[F[_]: Comonad](
operand: ValueToken[F],
prefix: F[PrefixToken.Op]
) extends ValueToken[F] {
def op: PrefixToken.Op = prefix.extract
override def as[T](v: T): F[T] = prefix.as(v)
override def mapK[K[_]: Comonad](fk: FunctionK[F, K]): ValueToken[K] =
copy(operand.mapK(fk), fk(prefix))
}
object PrefixToken {
enum Op(val symbol: String) {
case Not extends Op("!")
def p: P[Unit] = P.string(symbol)
}
private def parseOps(ops: List[Op]): P[S[Op]] =
P.oneOf(ops.map(op => op.p.lift.map(_.as(op))))
private def parsePrefix(basic: P[ValueToken[S]], ops: List[Op]) =
(parseOps(ops).surroundedBy(`/s*`) ~ basic).map { case (op, vt) =>
PrefixToken(vt, op)
}
val value: P[ValueToken[Span.S]] =
parsePrefix(P.defer(ValueToken.atom), Op.Not :: Nil)
}
object ValueToken {
val varProperty: P[VarToken[Span.S]] =
(Name.dotted ~ PropertyOp.ops.?).map { case (n, l)
VarToken(n, l.fold[List[PropertyOp[Span.S]]](Nil)(_.toList))
VarToken(n, l.foldMap(_.toList))
}
val abProperty: P[VarToken[Span.S]] =
(Name.cl ~ PropertyOp.ops.?).map { case (n, l)
VarToken(n, l.fold[List[PropertyOp[Span.S]]](Nil)(_.toList))
VarToken(n, l.foldMap(_.toList))
}
val bool: P[LiteralToken[Span.S]] =
@ -375,8 +436,25 @@ object ValueToken {
val literal: P[LiteralToken[Span.S]] =
P.oneOf(bool.backtrack :: float.backtrack :: num.backtrack :: string :: Nil)
private def brackets(basic: P[ValueToken[Span.S]]): P[ValueToken[Span.S]] =
basic.between(`(`, `)`).backtrack
// Basic element of math expression
val atom: P[ValueToken[S]] = P.oneOf(
literal.backtrack ::
initPeerId.backtrack ::
P.defer(CollectionToken.collection).backtrack ::
P.defer(NamedValueToken.dataValue).backtrack ::
P.defer(CallArrowToken.callArrow).backtrack ::
P.defer(abProperty).backtrack ::
P.defer(PrefixToken.value).backtrack ::
P.defer(brackets(InfixToken.value)).backtrack ::
varProperty ::
Nil
)
// One of entry points for parsing the whole math expression
val `value`: P[ValueToken[Span.S]] =
P.defer(InfixToken.mathExpr)
P.defer(InfixToken.value)
}

View File

@ -2,18 +2,22 @@ package aqua.parser
import aqua.AquaSpec
import aqua.parser.expr.func.IfExpr
import aqua.parser.lexer.InfixToken.Op
import aqua.parser.lexer.{EqOp, InfixToken, LiteralToken, ValueToken}
import aqua.parser.lexer.InfixToken.Op as InfixOp
import aqua.parser.lexer.PrefixToken.Op as PrefixOp
import aqua.parser.lexer.*
import aqua.parser.lexer.InfixToken.Op.*
import aqua.parser.lexer.PrefixToken.Op.*
import aqua.parser.lift.Span
import aqua.types.LiteralType
import cats.syntax.comonad.*
import cats.{~>, Comonad, Id}
import cats.parse.{Numbers, Parser as P, Parser0 as P0}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import cats.parse.{Numbers, Parser as P, Parser0 as P0}
import org.scalatest.Inside
class InfixTokenSpec extends AnyFlatSpec with Matchers with AquaSpec {
class ValueTokenComplexSpec extends AnyFlatSpec with Matchers with Inside with AquaSpec {
def spanToId: Span.S ~> Id = new (Span.S ~> Id) {
@ -24,9 +28,20 @@ class InfixTokenSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
private def variable(name: String): ValueToken[Id] =
VarToken(Name(name), Nil)
private def func(name: String, args: List[ValueToken[Id]]): ValueToken[Id] =
CallArrowToken(None, Name(name), args)
private def literal(n: Int): ValueToken[Id] = toNumber(n)
private def infixToken(left: ValueToken[Id], right: ValueToken[Id], op: Op) =
private def literalBool(b: Boolean): ValueToken[Id] = toBool(b)
private def prefixToken(value: ValueToken[Id], op: PrefixOp) =
PrefixToken[Id](value, op)
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] =
@ -257,9 +272,14 @@ class InfixTokenSpec extends AnyFlatSpec with Matchers with AquaSpec {
}
"complex cmp math expression " should "be parsed" in {
val test = (op: Op) => {
val vt =
ValueToken.`value`.parseAll(s"(1 + 2) ** 3 ${op.symbol} 4 - 5 * 6").right.get.mapK(spanToId)
val test = (op: InfixOp) => {
val vt = ValueToken.`value`
.parseAll(
s"(1 + 2) ** 3 ${op.symbol} 4 - 5 * 6"
)
.right
.get
.mapK(spanToId)
val left = pow(add(literal(1), literal(2)), literal(3))
val right = sub(literal(4), mul(literal(5), literal(6)))
val exp = infixToken(left, right, op)
@ -273,4 +293,220 @@ class InfixTokenSpec extends AnyFlatSpec with Matchers with AquaSpec {
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 {
val vtAnd = ValueToken.`value`.parseAll("true && false").map(_.mapK(spanToId))
inside(vtAnd) { case Right(vt) =>
vt shouldBe infixToken(literalBool(true), literalBool(false), And)
}
val vtOr = ValueToken.`value`.parseAll("false || true").map(_.mapK(spanToId))
inside(vtOr) { case Right(vt) =>
vt shouldBe infixToken(literalBool(false), literalBool(true), Or)
}
val vtAndOr = ValueToken.`value`.parseAll("false && true || false").map(_.mapK(spanToId))
inside(vtAndOr) { case Right(vt) =>
vt shouldBe infixToken(
infixToken(literalBool(false), literalBool(true), And),
literalBool(false),
Or
)
}
val vtOrAnd = ValueToken.`value`.parseAll("false || true && false").map(_.mapK(spanToId))
inside(vtOrAnd) { case Right(vt) =>
vt shouldBe infixToken(
literalBool(false),
infixToken(literalBool(true), literalBool(false), And),
Or
)
}
val vtOrNotAnd = ValueToken.`value`.parseAll("false || !true && false").map(_.mapK(spanToId))
inside(vtOrNotAnd) { case Right(vt) =>
vt shouldBe infixToken(
literalBool(false),
infixToken(
prefixToken(literalBool(true), Not),
literalBool(false),
And
),
Or
)
}
}
"logical expression with brackets" should "be parsed" in {
val vtAndOr = ValueToken.`value`.parseAll("false && (true || false)").map(_.mapK(spanToId))
inside(vtAndOr) { case Right(vt) =>
vt shouldBe infixToken(
literalBool(false),
infixToken(literalBool(true), literalBool(false), Or),
And
)
}
val vtOrAnd = ValueToken.`value`.parseAll("(false || true) && false").map(_.mapK(spanToId))
inside(vtOrAnd) { case Right(vt) =>
vt shouldBe infixToken(
infixToken(literalBool(false), literalBool(true), Or),
literalBool(false),
And
)
}
val vtNotAndOr = ValueToken.`value`.parseAll("!false && (true || false)").map(_.mapK(spanToId))
inside(vtNotAndOr) { case Right(vt) =>
vt shouldBe infixToken(
prefixToken(literalBool(false), Not),
infixToken(literalBool(true), literalBool(false), Or),
And
)
}
val vtNotOrAnd = ValueToken.`value`.parseAll("!(false || true) && false").map(_.mapK(spanToId))
inside(vtNotOrAnd) { case Right(vt) =>
vt shouldBe infixToken(
prefixToken(
infixToken(literalBool(false), literalBool(true), Or),
Not
),
literalBool(false),
And
)
}
}
"logical expression with math expressions" should "be parsed" in {
val vt1 = ValueToken.`value`.parseAll("1 < 2 + 3 || 3 % 2 > 1").map(_.mapK(spanToId))
inside(vt1) { case Right(vt) =>
vt shouldBe infixToken(
infixToken(
literal(1),
infixToken(literal(2), literal(3), Add),
Lt
),
infixToken(
infixToken(literal(3), literal(2), Rem),
literal(1),
Gt
),
Or
)
}
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
),
infixToken(
infixToken(literal(3), literal(2), Pow),
literal(1),
Lte
),
And
)
}
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
),
Not
),
infixToken(
infixToken(literal(3), literal(2), Pow),
literal(1),
Lte
),
And
)
}
}
"logical expression with function calls and variables" should "be parsed" in {
val vt1 = ValueToken.`value`.parseAll("foo() || a + 1 < 2 && b").map(_.mapK(spanToId))
inside(vt1) { case Right(vt) =>
vt shouldBe infixToken(
func("foo", Nil),
infixToken(
infixToken(
infixToken(
variable("a"),
literal(1),
Add
),
literal(2),
Lt
),
variable("b"),
And
),
Or
)
}
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(
variable("b"),
literal(5),
Gt
),
variable("c"),
Or
),
And
)
}
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(
variable("b"),
literal(4),
Gt
),
Not
),
prefixToken(variable("c"), Not),
Or
),
And
)
}
}
}

View File

@ -32,7 +32,7 @@ class VarLambdaSpec extends AnyFlatSpec with Matchers with EitherValues {
}
"var lambda in value" should "parse" in {
val opsP = (s: String) => InfixToken.atom.parseAll(s).value.mapK(spanToId)
val opsP = (s: String) => ValueToken.atom.parseAll(s).value.mapK(spanToId)
opsP("some_val") should be(VarToken[Id](Name[Id]("some_val")))
opsP("SomeClass.SOME_CONST") should be(VarToken[Id](Name[Id]("SomeClass.SOME_CONST")))
}

View File

@ -1,12 +1,14 @@
package aqua.semantics.rules
import aqua.parser.lexer.*
import aqua.parser.lexer.InfixToken.Op
import aqua.parser.lexer.InfixToken.{BoolOp, CmpOp, MathOp, Op as InfOp}
import aqua.parser.lexer.PrefixToken.Op as PrefOp
import aqua.raw.value.*
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.*
import cats.Monad
import cats.data.Chain
import cats.syntax.applicative.*
@ -17,6 +19,7 @@ import cats.syntax.traverse.*
import cats.syntax.option.*
import cats.instances.list.*
import cats.data.{NonEmptyList, NonEmptyMap}
import cats.data.OptionT
import scribe.Logging
import scala.collection.immutable.SortedMap
@ -174,82 +177,126 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
case ca: CallArrowToken[S] =>
callArrowToRaw(ca).map(_.widen[ValueRaw])
case pr @ PrefixToken(operand, _) =>
(for {
raw <- OptionT(
valueToRaw(operand)
)
typeCheck <- OptionT.liftF(
T.ensureTypeMatches(operand, ScalarType.bool, raw.`type`)
)
result <- OptionT.when(typeCheck)(
ApplyUnaryOpRaw(
op = pr.op match {
case PrefOp.Not => ApplyUnaryOpRaw.Op.Not
},
value = raw
)
)
} yield result).value
case it @ InfixToken(l, r, _) =>
(valueToRaw(l), valueToRaw(r)).flatMapN {
case (Some(leftRaw), Some(rightRaw)) =>
val lType = leftRaw.`type`
val rType = rightRaw.`type`
lazy val uType = lType `` rType
val hasFloat = List(lType, rType).exists(
_ acceptsValueOf LiteralType.float
)
// 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")
it.op match {
case InfOp.Bool(bop) =>
for {
leftChecked <- T.ensureTypeMatches(l, ScalarType.bool, lType)
rightChecked <- T.ensureTypeMatches(r, ScalarType.bool, rType)
} yield Option.when(
leftChecked && rightChecked
)(
ApplyBinaryOpRaw(
op = bop match {
case BoolOp.And => ApplyBinaryOpRaw.Op.And
case BoolOp.Or => ApplyBinaryOpRaw.Op.Or
},
left = leftRaw,
right = rightRaw
)
)
case op @ (InfOp.Math(_) | InfOp.Cmp(_)) =>
// Some type acrobatics to make
// compiler check exhaustive pattern matching
val iop = op match {
case InfOp.Math(op) => op
case InfOp.Cmp(op) => op
}
val hasFloat = List(lType, rType).exists(
_ acceptsValueOf LiteralType.float
)
// See https://github.com/fluencelabs/aqua-lib/blob/main/math.aqua
val (id, fn) = iop match {
case MathOp.Add => ("math", "add")
case MathOp.Sub => ("math", "sub")
case MathOp.Mul if hasFloat => ("math", "fmul")
case MathOp.Mul => ("math", "mul")
case MathOp.Div => ("math", "div")
case MathOp.Rem => ("math", "rem")
case MathOp.Pow => ("math", "pow")
case CmpOp.Gt => ("cmp", "gt")
case CmpOp.Gte => ("cmp", "gte")
case CmpOp.Lt => ("cmp", "lt")
case CmpOp.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) = iop match {
case MathOp.Add | MathOp.Sub | MathOp.Div | MathOp.Rem =>
(ScalarType.integer, ScalarType.integer, uTypeBounded)
case MathOp.Pow =>
(ScalarType.integer, ScalarType.unsigned, uTypeBounded)
case MathOp.Mul if hasFloat =>
(ScalarType.float, ScalarType.float, ScalarType.i64)
case MathOp.Mul =>
(ScalarType.integer, ScalarType.integer, uTypeBounded)
case CmpOp.Gt | CmpOp.Lt | CmpOp.Gte | CmpOp.Lte =>
(ScalarType.integer, ScalarType.integer, ScalarType.bool)
}
for {
leftChecked <- T.ensureTypeOneOf(l, leftExp, lType)
rightChecked <- T.ensureTypeOneOf(r, rightExp, rType)
} yield Option.when(
leftChecked.isDefined && rightChecked.isDefined
)(
CallArrowRaw(
ability = Some(id),
name = fn,
arguments = leftRaw :: rightRaw :: Nil,
baseType = ArrowType(
ProductType(lType :: rType :: Nil),
ProductType(resType :: Nil)
),
serviceId = Some(LiteralRaw.quote(id))
)
)
}
/*
* 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 {
leftChecked <- T.ensureTypeOneOf(l, leftExp, lType)
rightChecked <- T.ensureTypeOneOf(r, rightExp, rType)
} yield Option.when(
leftChecked.isDefined && rightChecked.isDefined
)(
CallArrowRaw(
ability = Some(id),
name = fn,
arguments = leftRaw :: rightRaw :: Nil,
baseType = ArrowType(
ProductType(lType :: rType :: Nil),
ProductType(resType :: Nil)
),
serviceId = Some(LiteralRaw.quote(id))
)
)
case _ => None.pure[Alg]
}
}
// Generate CallArrowRaw for arrow in ability

View File

@ -14,10 +14,10 @@ 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.value.{ApplyBinaryOpRaw, LiteralRaw}
import aqua.raw.RawContext
import aqua.types.{LiteralType, ScalarType, TopType, Type}
import aqua.parser.lexer.{InfixToken, LiteralToken, Name, ValueToken, VarToken}
import aqua.parser.lexer.{InfixToken, LiteralToken, Name, PrefixToken, ValueToken, VarToken}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
@ -27,6 +27,7 @@ import cats.data.State
import cats.syntax.functor.*
import cats.syntax.comonad.*
import monocle.syntax.all.*
import aqua.raw.value.ApplyUnaryOpRaw
class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
@ -203,4 +204,157 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
}
}
}
it should "handle ||, && on bool values" in {
val types = List(LiteralType.bool, ScalarType.bool)
allPairs(types).foreach { case (lt, rt) =>
InfixToken.BoolOp.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.Bool(op))
val (st, res) = alg
.valueToRaw(token)
.run(state)
.value
inside(res) { case Some(ApplyBinaryOpRaw(bop, _, _)) =>
bop shouldBe (op match {
case InfixToken.BoolOp.And => ApplyBinaryOpRaw.Op.And
case InfixToken.BoolOp.Or => ApplyBinaryOpRaw.Op.Or
})
}
}
}
}
it should "handle ! on bool values" in {
val types = List(LiteralType.bool, ScalarType.bool)
types.foreach { t =>
PrefixToken.Op.values.foreach { op =>
val value = t match {
case lt: LiteralType =>
literal("true", lt)
case _ =>
variable("val")
}
val alg = algebra()
val state = genState(
vars = List("val" -> t).filter(_ => t != LiteralType.bool).toMap
)
val token = PrefixToken[Id](value, op)
val (st, res) = alg
.valueToRaw(token)
.run(state)
.value
inside(res) { case Some(ApplyUnaryOpRaw(uop, _)) =>
uop shouldBe (op match {
case PrefixToken.Op.Not => ApplyUnaryOpRaw.Op.Not
})
}
}
}
}
it should "check type of logical operands (binary)" in {
val types = List(LiteralType.bool, ScalarType.bool).flatMap(t =>
List(t -> ScalarType.i8, ScalarType.i8 -> t)
)
types.foreach { case (lt, rt) =>
InfixToken.BoolOp.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.Bool(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
types.foreach { t =>
PrefixToken.Op.values.foreach { op =>
val value = t match {
case lt: LiteralType =>
literal("42", lt)
case _ =>
variable("val")
}
val alg = algebra()
val state = genState(
vars = Map(
"value" -> t
).filter(_ => t != LiteralType.unsigned)
)
val token = PrefixToken[Id](value, op)
val (st, res) = alg
.valueToRaw(token)
.run(state)
.value
res shouldBe None
st.errors.exists(_.isInstanceOf[RulesViolated[Id]]) shouldBe true
}
}
}
}