From 33e83d2904b8296814f63794160e552ba16a6cb0 Mon Sep 17 00:00:00 2001 From: Mohammed Ibrahim Date: Mon, 2 Oct 2023 17:04:20 -0400 Subject: [PATCH] Add preEval of SchemaState type (#2339) --- .../core/pure/router/preeval/preeval.pure | 2180 +++++------ .../core/pure/router/preeval/tests.pure | 3198 +++++++++-------- .../relational/router/tests/testPreeval.pure | 264 +- 3 files changed, 2824 insertions(+), 2818 deletions(-) diff --git a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/preeval/preeval.pure b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/preeval/preeval.pure index 0b3a399c4f4..c132f44d392 100644 --- a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/preeval/preeval.pure +++ b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/preeval/preeval.pure @@ -1,1090 +1,1092 @@ -// Copyright 2023 Goldman Sachs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import meta::pure::router::preeval::*; -import meta::pure::extension::*; -import meta::pure::metamodel::path::*; -import meta::pure::router::extension::*; -import meta::pure::router::utils::*; - -Class meta::pure::router::preeval::State -{ - inScopeVars : Map>[1]; - rollingInScopeVars : Map>[1]; - inScopeTypeParams : Map[1]; - - debug : DebugContext[1]; - depth : Integer[0..1]; - path : FunctionDefinition[*]; - - shouldInlineFxn : FunctionDefinition<{Function[1] -> Boolean[1]}>[1]; - stopPreeval : Function<{Any[1] -> Boolean[1]}>[1]; - - toString() { $this->simpleToString() }:String[1]; -} - -Class meta::pure::router::preeval::PrevalWrapper -{ - canPreval : Boolean[1]; - value : T[1]; - openVars : String[*]; - modified : Boolean[1]; - - toString() { $this->simpleToString() }:String[1]; -} - - -// ==================================================================================================================================================================== -// Public Apis -// ==================================================================================================================================================================== - -function <> meta::pure::router::preeval::preval(f:FunctionDefinition[1], extensions:Extension[*]):FunctionDefinition[1] -{ - preval($f, $extensions, noDebug()); -} - -function <> meta::pure::router::preeval::preval(f:FunctionDefinition[1], extensions:Extension[*], debug:DebugContext[1]):FunctionDefinition[1] -{ - preval($f, newMap([]->cast(@Pair)), $f->openVariableValues(), $extensions, $debug).value->toOne()->cast($f); -} - -function <> meta::pure::router::preeval::preval(f : FunctionDefinition[1], vars:Map[1], inScopeVars:Map>[1], extensions:Extension[*]):PrevalWrapper>[1] -{ - preval($f, $vars, $inScopeVars, $extensions, noDebug()); -} - -function <> meta::pure::router::preeval::preval(f : FunctionDefinition[1], vars:Map[1], inScopeVars:Map>[1], extensions:Extension[*], debug:DebugContext[1]):PrevalWrapper>[1] -{ - let state = ^meta::pure::router::preeval::State(inScopeVars = $inScopeVars, - shouldInlineFxn = defaultFunctionInlineStrategy($extensions), - stopPreeval = defaultPreevalStopStrategy($extensions), - rollingInScopeVars = $inScopeVars, - debug = $debug, - inScopeTypeParams = ^Map()); - preval($f, $state, $extensions); -} - -function <> meta::pure::router::preeval::preval(f : FunctionDefinition[1], state:State[1], extensions:Extension[*]):PrevalWrapper>[1] -{ - let r = prevalInternal($f->evaluateAndDeactivate(), $state, $extensions)->toOne()->cast(@PrevalWrapper>); - - if(!$r->anyModified(), - | - $state->printDebug(|'No changes made for: '); - $state->printDebug(|$f->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::toPureGrammar::toPure($extensions));, - | - let res = $r.value->toOne(); - - // $state->printDebug('Transformed From:'); - // $state->printDebug(|$f->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::json::toJSON(50000)); - // $state->printDebug(|$f->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::protocols::pure::vX_X_X::transformation::toPureGrammar::toPure($extensions)); - - $state->printDebug('Transformed To:'); - $state->printDebug(|$res->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::json::toJSON(50000)); - $state->printDebug(|$res->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::toPureGrammar::toPure($extensions)); - - $state->printDebug($res); - ); - - $r; -} - -function <> meta::pure::router::preeval::preval(f : FunctionExpression[1], inScopeVars:Map>[1], extensions:Extension[*], debug:DebugContext[1]):ValueSpecification[1] -{ - let state = ^meta::pure::router::preeval::State(inScopeVars = $inScopeVars, - shouldInlineFxn = defaultFunctionInlineStrategy($extensions), - stopPreeval = defaultPreevalStopStrategy($extensions), - rollingInScopeVars = $inScopeVars, - debug = $debug, - inScopeTypeParams = ^Map()); - prevalInternal($f, $state, $extensions).value->cast(@ValueSpecification); -} - -// ==================================================================================================================================================================== -// Processor Functions -// ==================================================================================================================================================================== - -function <> meta::pure::router::preeval::prevalInternal(item : Any[1], origState : meta::pure::router::preeval::State[1], extensions:Extension[*]):PrevalWrapper[1] -{ - let state = ^$origState(depth = if($origState.depth->isEmpty(), | 0, | $origState.depth->toOne() + 1), - path = if(!$item->instanceOf(FunctionDefinition), | $origState.path, | $origState.path->concatenate($item->cast(@FunctionDefinition)))); - - - $state->printDebugWithDepth(|'Processing ' + $item->type()->makeString() + ' ' - + $item->evaluateAndDeactivate() - ->match([ - iv:InstanceValue[1] | $iv.values->map(x|$x->type())->makeString(), - sfe:SimpleFunctionExpression[1] | '(' + $sfe.func->elementToPath() + ')', - ve:VariableExpression[1] | '(' + $ve.name + ')', - a:Any[*]|''] - ) - + ', inScopeVars: ' + $state.inScopeVars->keys()->joinStrings('[', ',', ']')); - - let newItem = $item->evaluateAndDeactivate() - ->match([ - {lambda : LambdaFunction[1]| - let fnArgNames = $lambda->functionType().parameters.name; - let fnOpenVariables = $lambda->openVariableValues()->putAll($state.inScopeVars->keyValues()->filter(kv | !$kv.first->in($fnArgNames))->newMap()); - - $lambda->prevalFunctionDefinition(^$state(inScopeVars = $fnOpenVariables), $extensions); - }, - {fd : FunctionDefinition[1]| - $fd->prevalFunctionDefinition(^$state(inScopeVars = newMap([])), $extensions); // Assigning empty map to inScopeVars as function definition can't have openVars - }, - {sfe : SimpleFunctionExpression[1]| - let handlers = [ - // Avoid handling known 'false' case (which may not be evalulatable / have runtime issues, e.g. a toOne() after an isNotEmpty condition) - ^Pair, LambdaFunction<{->PrevalWrapper[1]}>>(first = if_Boolean_1__Function_1__Function_1__T_m_, second = {| - let conditionPreVal = $sfe.parametersValues->at(0)->prevalInternal($state, $extensions); - if($conditionPreVal.value->toOne()->cast(@ValueSpecification)->isInstanceValue($state.inScopeVars), - | let valueValueSpecification = if($conditionPreVal.value->cast(@InstanceValue).values == true, - | $sfe.parametersValues->at(1), - | $sfe.parametersValues->at(2) - ); - let newSfe = ^$sfe(func = eval_Function_1__V_m_, parametersValues = $valueValueSpecification); - - $newSfe->prevalInternal($state, $extensions)->markModified();, - | prevalGenericSimpleFunctionExpression($sfe, $state, $extensions); - ); - }), - - // Avoid handling conditions after the first false (which may not be evalulatable / have runtime issues, e.g. a toOne() after an isNotEmpty condition) - ^Pair, LambdaFunction<{->PrevalWrapper[1]}>>(first = meta::pure::functions::boolean::and_Boolean_1__Boolean_1__Boolean_1_, second = {| - prevalBooleanSimpleFunctionExpression($sfe, $state, $extensions); - }), - - // Avoid handling conditions after the first true (which may not be evalulatable / have runtime issues, e.g. a toOne() after an isEmpty condition) - ^Pair, LambdaFunction<{->PrevalWrapper[1]}>>(first = meta::pure::functions::boolean::or_Boolean_1__Boolean_1__Boolean_1_, second = {| - prevalBooleanSimpleFunctionExpression($sfe, $state, $extensions); - }) - - // TODO: - // 1. Optimise and_Boolean_MANY__Boolean_1_ and or_Boolean_$1_MANY$__Boolean_1_ - // 1. Optimise plus(many), minus(many) and times(many) (plus_String_MANY__String_1_, plus_Float_MANY__Float_1_, times_Number_MANY__Number_1_, minus_Float_MANY__Float_1_) - ]; - - let handler = $handlers->filter(p|$p.first == $sfe.func).second; - - if($handler->isNotEmpty(), - | $handler->toOne()->eval(), - | prevalGenericSimpleFunctionExpression($sfe, $state, $extensions)); - }, - {ve : VariableExpression[1]| - let varValue = $state.inScopeVars->get($ve.name).values; - let iv = if($state.inScopeVars->get($ve.name)->isEmpty(), - | ^PrevalWrapper(value = $ve, canPreval = true, modified = false, openVars = [$ve.name]), - | $varValue->match([ - n : Nil[0]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(), - iv : InstanceValue[1]| $iv, - ve : VariableExpression[1]| $ve->evaluateAndDeactivate(), - sfe : SimpleFunctionExpression[1]| $sfe, - a : meta::pure::tds::AggregateValue[*]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(), - a : meta::pure::functions::collection::AggregateValue[*]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(), - a : meta::pure::tds::BasicColumnSpecification[*]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(), - r : meta::pure::graphFetch::RootGraphFetchTree[1]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate();, - b : meta::external::format::shared::binding::Binding[1]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate();, - s : meta::pure::store::Store[1]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate();, - a : Any[*]| assert($state.stopPreeval->eval($a), | 'Unsupported type: ' + $a->type()->match([pe:PackageableElement[1]|$pe->elementToPath(), t:Type[1]|$t->makeString()])); - ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(); - ]) - ->map(x| - let newState = ^$state(inScopeVars = $state.inScopeVars->keyValues()->filter(p|$p.first != $ve.name)->newMap()); - $x->prevalInternal($newState, $extensions); - ) - ->markModified() - ); - }, - {iv : InstanceValue[1]| - let subVals = $iv.values->map(x|$x->prevalInternal($state, $extensions)); - let r = if(!$subVals->anyModified(), - | pair(false, $iv), - | let cleanIv = ^$iv(values = $subVals.value->map(x|$x->match([subIv:InstanceValue[1]|if($subIv.values->size() == 1, |$subIv.values, |$subIv), a:Any[*]|$a]))); - let cleanIv2 = ^$cleanIv(genericType = $cleanIv.genericType->resolveGenericType($state).value, - multiplicity = if($cleanIv.multiplicity == PureOne && $cleanIv.values->isEmpty(), - | $cleanIv.multiplicity, //This seems to be a quirk of Cast(@ABC) - | $cleanIv.values->size()->toMultiplicity()) - ); - pair(true, $cleanIv2); - ); - ^PrevalWrapper(value = $r.second, canPreval = $subVals->canPreval(), openVars = $subVals->openVars($state.inScopeVars), modified = $r.first); - }, - {k : KeyExpression[1]| - let e = $k.expression->prevalInternal($state, $extensions)->toOne(); - let ke = if(!$e->anyModified(), - | pair(false, $k), - | pair(true, ^$k(expression = $e.value->cast(@ValueSpecification)->toOne()))); - - ^PrevalWrapper(value = $ke.second, canPreval = $e.canPreval, openVars = $e->openVars($state.inScopeVars), modified = $ke.first); - }, - {p : ColumnSpecification[1]| - $p->match([ - bcs : BasicColumnSpecification[1]| - let f = $bcs.func->evaluateAndDeactivate(); - let newFuncWrapper = prevalInternal($f, ^$state(inScopeTypeParams = ^Map(), inScopeVars = $state.inScopeVars->putAll($f->openVariableValues())), $extensions); - - if(!$newFuncWrapper->anyModified(), - | ^PrevalWrapper(value = $bcs, canPreval = true, modified = false), - | ^PrevalWrapper(value = ^$bcs(func = $newFuncWrapper.value->cast($f)), canPreval = true, modified = true); - );, - o:ColumnSpecification[1] | ^PrevalWrapper(value = $o, canPreval = true, modified = false); - ]); - - }, - {av : meta::pure::tds::AggregateValue[1]| - let mapFn = $av.mapFn->evaluateAndDeactivate(); - let aggFn = $av.aggregateFn->evaluateAndDeactivate(); - - let newFuncWrapper_map = prevalInternal($mapFn, $state, $extensions); - let newFuncWrapper_agg = prevalInternal($aggFn, $state, $extensions); - - if(![$newFuncWrapper_map, $newFuncWrapper_agg]->anyModified(), - | ^PrevalWrapper(value = $av, canPreval = true, modified = false), - | ^PrevalWrapper(value = ^$av(mapFn = $newFuncWrapper_map.value->cast($mapFn), aggregateFn = $newFuncWrapper_agg.value->cast($aggFn)), canPreval = true, modified = true); - ); - }, - {av : meta::pure::functions::collection::AggregateValue[1]| - let mapFn = $av.mapFn->evaluateAndDeactivate(); - let aggFn = $av.aggregateFn->evaluateAndDeactivate(); - - let newFuncWrapper_map = prevalInternal($mapFn, $state, $extensions); - let newFuncWrapper_agg = prevalInternal($aggFn, $state, $extensions); - - if(![$newFuncWrapper_map, $newFuncWrapper_agg]->anyModified(), - | ^PrevalWrapper(value = $av, canPreval = true, modified = false), - | ^PrevalWrapper(value = ^$av(mapFn = $newFuncWrapper_map.value->cast($mapFn), aggregateFn = $newFuncWrapper_agg.value->cast($aggFn)), canPreval = true, modified = true); - ); - }, - {r: meta::pure::graphFetch::RootGraphFetchTree[1] | ^PrevalWrapper(value = $r, canPreval = true, modified = false);}, - {b: meta::external::format::shared::binding::Binding[1] | ^PrevalWrapper(value = $b, canPreval = true, modified = false);}, - {s: meta::pure::store::Store[1] | ^PrevalWrapper(value = $s, canPreval = true, modified = false);}, - {a : Any[1]| - assert($state.stopPreeval->eval($a), | 'Unsupported type: ' + $a->type()->match([pe:PackageableElement[1]|$pe->elementToPath(), t:Type[1]|$t->makeString()])); - ^PrevalWrapper(value = $a, canPreval = true, modified = false); - } - ]); - - $state->printDebugWithDepth(|'Returning ' + $newItem.value->type()->makeString() + ' ' + $newItem.value->match([iv:InstanceValue[1]|$iv.values->map(x|$x->type())->makeString(), sfe:SimpleFunctionExpression[1]|'(' + $sfe.func->elementToPath() + ')', ve : VariableExpression[1]|'(' + $ve.name + ')', a:Any[*]|'']) + ' ' + $newItem.toString()); - $newItem; -} - -function <> meta::pure::router::preeval::prevalFunctionDefinition(fd : FunctionDefinition[1], origState : meta::pure::router::preeval::State[1], extensions:Extension[*]):PrevalWrapper[1] -{ - let items = $fd.expressionSequence; - - let r = range($items->size()) - ->fold({index, p| - let item = $items->at($index); - let state = $p.first; - - let isLastExpression = ($index == ($items->size()-1)); - - if(!$isLastExpression && !$item->isLetFunction(), - | - $state->printDebugWithDepth(|'Dropping non-variable assignement non-return statement: ' + $item->type()->makeString() + ' ' - + $item->match([ - iv:InstanceValue[1]|$iv.values->map(x|$x->type())->makeString(), - sfe:SimpleFunctionExpression[1]|'(' + $sfe.func->elementToPath() + ')', - ve : VariableExpression[1]|'(' + $ve.name + ')', - a:Any[*]|'' - ]) - + ', inScopeVars: ' + $state.inScopeVars->keys()->joinStrings('[', ',', ']')); - - pair($state, $p.second);, - | - let r = $item->prevalInternal($state, $extensions); - - let isLetFunctionValueSpecification = $r.value->instanceOf(ValueSpecification) && $r.value->cast(@ValueSpecification)->isLetFunction(); - - if(!$isLetFunctionValueSpecification, - | pair($state, list(if($p.second.values->isEmpty(), | $r, | $p.second.values->concatenate($r)))), - | - let fe = $r.value->cast(@FunctionExpression); - - let varName = $fe.parametersValues->at(0)->reactivate($state.inScopeVars)->cast(@String)->toOne(); - let varValue = $fe.parametersValues->at(1) - ->match([ - iv:InstanceValue[1]|$iv.values, - fe:ValueSpecification[1]|$fe - ]); - - $state->printDebugWithDepth(|'adding variable ' + $varName + ' to rolling scope, with value ' + $varValue->makeString()); - - let shouldInlineVariableExpression = ($varValue->match([v:VariableExpression[1]|true, a: Any[*]|false])) - || ($r->canPreval() && $r->areAllInScope($state.inScopeVars) && !$varValue->match([v:LambdaFunction[1]|true, a: Any[*]|false])); - - let newRollingInScopeVars = $state.rollingInScopeVars->put($varName, list($varValue)); - let newInScopeVars = if(!$shouldInlineVariableExpression, - | $state.inScopeVars, - | $state.inScopeVars->put($varName, list($varValue)) - ); - let newState = ^$state(inScopeVars = $newInScopeVars, rollingInScopeVars = $newRollingInScopeVars); - - let expValue = if($isLastExpression, - | ^PrevalWrapper(canPreval = $r.canPreval, modified = true, openVars =$r.openVars, value = $varValue->match([vs:ValueSpecification[1]|$vs, a:Any[*]|^InstanceValue(genericType = $fe.genericType, multiplicity = $a->size()->toMultiplicity(), values = $a)])), - | if($shouldInlineVariableExpression, - | [], - | $r - ) - ); - if($expValue->isEmpty(), - | pair($newState, $p.second), - | pair($newState, list(if($p.second.values->isEmpty(), | $expValue, | $p.second.values->concatenate($expValue)))) - ); - ); - ); - }, [pair($origState, list([]->cast(@PrevalWrapper)))] - ).second.values; - - - /* - This should not be necessary, but seems to be required otherwise fold can end up wrapping InstanceValues in InstanceValues, - which map doesn't seem to - */ - let newExpressionSequncePrevalWrappers = $r->evaluateAndDeactivate() - ->match([ - iv:InstanceValue[1]|$iv.values->cast(@PrevalWrapper), - a:PrevalWrapper[*]|$a - ]); - - let value = if(($newExpressionSequncePrevalWrappers->size() == $items->size()) && !$newExpressionSequncePrevalWrappers->anyModified(), - | pair(false, $fd), - | let newFd = ^$fd(expressionSequence = $newExpressionSequncePrevalWrappers.value->cast(@ValueSpecification)->toOneMany()) - ->match([ - lf : LambdaFunction[1]| ^$lf(openVariables = $lf.openVariables->map(v|$v->resolveVariable($origState)) - ->distinct() - ->intersection($newExpressionSequncePrevalWrappers.openVars->map(v|$v->resolveVariable($origState)))), - fd2:FunctionDefinition[1]| $fd2 - ]); - pair(true, $newFd); - ); - - ^PrevalWrapper>( - value = $value.second, - canPreval = $newExpressionSequncePrevalWrappers->canPreval(), - openVars = $fd->match([lf : LambdaFunction[1]|$value.second->cast(@LambdaFunction).openVariables, fd:FunctionDefinition[1]|[]]), - modified = $value.first - ); -} - -function <> meta::pure::router::preeval::prevalBooleanSimpleFunctionExpression(sfe : SimpleFunctionExpression[1], state : meta::pure::router::preeval::State[1], extensions:Extension[*]):PrevalWrapper[1] -{ - assert($sfe.func->in([and_Boolean_1__Boolean_1__Boolean_1_, or_Boolean_1__Boolean_1__Boolean_1_])); - assertEquals(2, $sfe.parametersValues->size()); - - let shortcutVal = !($sfe.func == and_Boolean_1__Boolean_1__Boolean_1_); - - let newParam1Wrapper = $sfe.parametersValues->at(0)->prevalInternal($state, $extensions); - - if($newParam1Wrapper.value->toOne()->isInstanceValue($state.inScopeVars), - | if(($newParam1Wrapper.value->cast(@InstanceValue).values == $shortcutVal), - | $newParam1Wrapper->markModified(), - | let newParam2Wrapper = $sfe.parametersValues->at(1)->prevalInternal($state, $extensions); - $newParam2Wrapper->markModified();), - | let newParam2Wrapper = $sfe.parametersValues->at(1)->prevalInternal($state, $extensions); - - if($newParam2Wrapper.value->toOne()->isInstanceValue($state.inScopeVars), - | if($newParam2Wrapper.value->cast(@InstanceValue).values != $shortcutVal, - | $newParam1Wrapper->markModified(), - | $newParam2Wrapper->markModified()), - | let newParamWrappers = [$newParam1Wrapper, $newParam2Wrapper]; - ^PrevalWrapper( - value = ^$sfe(parametersValues = $newParamWrappers.value->cast(@ValueSpecification)), - canPreval = $newParamWrappers->canPreval(), - openVars = $newParamWrappers->openVars($state.inScopeVars), - modified = $newParamWrappers->size() != $sfe.parametersValues->size() || $newParamWrappers->anyModified() - ); - ); - ); -} - -function <> meta::pure::router::preeval::prevalGenericSimpleFunctionExpression(sfe : SimpleFunctionExpression[1], state : meta::pure::router::preeval::State[1], extensions:Extension[*]):PrevalWrapper[1] -{ - let newParamWrappers = if($sfe.parametersValues->isEmpty(), - | [], - | $sfe.func->match([p:Property[1]|'this', f:Function[1]|$f->functionType().parameters.name]) - ->zip($sfe.parametersValues) - ->evaluateAndDeactivate() - ->map(p| $state->printDebugWithDepth(|'Processing parameter: ' + $p.first); - let r = $p.second->prevalInternal($state, $extensions); - $state->printDebugWithDepth(|'Completed processing parameter: ' + $p.first); - $r;) - ); - let newSfeModified = $newParamWrappers->anyModified(); - - let newSfe = if($newSfeModified, - | ^$sfe(parametersValues = $newParamWrappers.value->cast(@ValueSpecification) - ->map(pv|^$pv(genericType = $pv.genericType->resolveGenericType($state).value)), - genericType = $sfe.genericType->resolveGenericType($state).value), - | $sfe); - - let canPrevalFunc = !($newSfe.func->hasStereotype('SideEffectFunction', functionType) || $newSfe.func->hasStereotype('NotImplementedFunction', functionType) || $state.stopPreeval->eval($newSfe)); - - if(!$canPrevalFunc, - | $state->printDebugWithDepth(|'Unable to perform preval: ' + $newSfe.func->elementToPath() + ' (' + $newSfe.func->instanceOf(NativeFunction)->makeString() + ')'); - - ^PrevalWrapper( - value = $newSfe, - canPreval = ($canPrevalFunc || ($newSfe.func == letFunction_String_1__T_m__T_m_)) && $newParamWrappers->canPreval(), - openVars = $newParamWrappers->openVars($state.inScopeVars), - modified = $newSfeModified - );, - | if(shouldInline($newSfe.func, $state), - | - $state->printDebugWithDepth(|'Inlining: ' + $newSfe.func->elementToPath()); - assert($newSfe.func->instanceOf(FunctionDefinition) && $newSfe.func->cast(@FunctionDefinition).expressionSequence->size() == 1); - - let newState = $newSfe->addToScope($state)->map(s|^$s(path = $state.path->concatenate($newSfe.func->cast(@FunctionDefinition)))); - - let subSfe1 = $newSfe.func->cast(@FunctionDefinition).expressionSequence->toOne()->evaluateAndDeactivate(); - let subSfe = ^$subSfe1(genericType = $subSfe1.genericType->resolveGenericType($newState).value); - - let sPreValWrapper = $subSfe->prevalInternal($newState, $extensions); - - $sPreValWrapper->markModified();, - | if($newSfe->canInlineEvalFunctionExpression(), - | - $state->printDebugWithDepth(|'Expanding eval: ' + $newSfe.func->elementToPath()); - - let func = $newSfe.parametersValues->at(0)->match([ - iv:InstanceValue[1]|$iv.values->toOne(), - vs:ValueSpecification[*]|$vs - ])->cast(@FunctionDefinition)->toOne(); - - let newState = $newSfe->addToScope($state)->addToScope($func, $sfe.resolvedTypeParameters, $sfe.parametersValues->drop(1)); - - let subSfe1 = $func.expressionSequence->toOne()->evaluateAndDeactivate(); - let subSfe = ^$subSfe1(genericType = $subSfe1.genericType->resolveGenericType($newState).value); - - let sPreValWrapper = $subSfe->prevalInternal($newState, $extensions); - $sPreValWrapper->markModified();, - | - let notPrevalReason = if($newSfe.parametersValues->exists(pv|!$pv->isInstanceValue($state.inScopeVars)), - | 'params are not instance values';, - | if (!$newParamWrappers->canPreval(), - | 'params can not preval';, - | if(!$newParamWrappers->areAllInScope($state.inScopeVars), - | 'open variables not in scope', - | if($newSfe.func == meta::pure::functions::lang::cast_Any_m__T_1__T_m_ && $newParamWrappers.value->at(0)->map(x|$x->instanceOf(InstanceValue) && $x->cast(@InstanceValue).values->isEmpty()), - | 'cast of empty collection', - | [] - ) - ); - ) - ); - if($notPrevalReason->isNotEmpty(), - | - let handlers = [] - ->cast(@PairBoolean[1]}>, Function<{SimpleFunctionExpression[1]->PrevalWrapper[1]}>>) - ->concatenate([ - pair( - {theSfe:SimpleFunctionExpression[1]| - $newSfe.func.name == 'columns' - && $newSfe.func->instanceOf(AbstractProperty) - && $newSfe.func->cast(@AbstractProperty).owner == TabularDataSet - && $newParamWrappers->areAllInScope($state.rollingInScopeVars) - }, - {theSfe:SimpleFunctionExpression[1]| - /* - Simplistically we could just re-activate the expression here as well, however there are some - TDS functions that are not implemented in the "Pure" implementation and only work in relational - queries. Therefore we have to specifically redirect to a helper function to resolve the schema - instead - */ - let val = meta::pure::tds::schema::resolveSchema($newSfe.parametersValues->toOne(), $state.rollingInScopeVars, $extensions); - - let iv = ^InstanceValue(genericType = $newSfe.genericType,multiplicity = $val->size()->toMultiplicity(), values = $val)->evaluateAndDeactivate(); - ^PrevalWrapper(value = $iv, canPreval = true, modified = true); - }), - pair( - {theSfe:SimpleFunctionExpression[1]|$newSfe.func->in([map_T_m__Function_1__V_m_, map_T_$0_1$__Function_1__V_$0_1$_, map_T_MANY__Function_1__V_MANY_]) && $newSfe.parametersValues->forAll(pv|$pv->isInstanceValue($state.inScopeVars))}, - {theSfe:SimpleFunctionExpression[1]| - // This special handling is required to cover cases where 'fixed'/ InstanceValue inputs are being mapped - // using a function that can't be pre-evalled (because it has external input), so wouldn't normally be modified. - // However we can actually just expand the lambda applied to each of the inputs, to elimniate the Map. - - $state->printDebugWithDepth(|'Expanding: ' + $newSfe.func->elementToPath()); - - let inputVals = $newSfe.parametersValues->at(0)->reactivate($state.inScopeVars) - ->map(v|$v->match([ - vs:ValueSpecification[1]|$vs, - a:Any[*]| ^InstanceValue( - genericType = $newSfe.parametersValues->at(0).genericType, - multiplicity = PureOne, - values = $v->toOne() - )->evaluateAndDeactivate() - ])); - - let lambda = $newSfe.parametersValues->at(1)->cast(@InstanceValue).values->toOne()->cast(@FunctionDefinition); - //TODO: This should really be pre-checked as part of the If condition to get here (so that the assertion never fails) - assert($lambda->instanceOf(FunctionDefinition) && $lambda->cast(@FunctionDefinition).expressionSequence->size() == 1); - - let paramName = $lambda->functionType().parameters->toOne().name; - - let resultValWrappers = $inputVals->map(v| - let newState = $state->addToScope($lambda, [], $v); - - let subSfe1 = $lambda.expressionSequence->toOne()->evaluateAndDeactivate(); - let subSfe = ^$subSfe1(genericType = $sfe.genericType->resolveGenericType($newState).value); - - $subSfe->prevalInternal($newState, $extensions); - ); - - let resultVals = $resultValWrappers.value->map(v|$v->match([iv:InstanceValue[0..1]|$iv->evaluateAndDeactivate().values, a:Any[*]|$a])); - - let r = ^InstanceValue( - genericType = $newSfe.genericType, - multiplicity = $resultVals->size()->toMultiplicity(), - values = $resultVals - )->evaluateAndDeactivate(); - - ^PrevalWrapper( - value = $r, - canPreval = $resultValWrappers->canPreval(), - openVars = $resultValWrappers->openVars($state.inScopeVars), - modified = true - ); - }), - pair( - {theSfe:SimpleFunctionExpression[1]|$newSfe.func == fold_T_MANY__Function_1__V_m__V_m_ && $newSfe.parametersValues->forAll(pv|$pv->isInstanceValue($state.inScopeVars))}, - {theSfe:SimpleFunctionExpression[1]| - // This special handling is required to cover cases where 'fixed'/ InstanceValue inputs are being folded - // using a function that can't be pre-evalled (because it has external input), so wouldn't normally be modified. - // However we can actually just repeat apply the lambda applied to each of the inputs, to elimniate the Fold. - $state->printDebugWithDepth(|'Expanding: ' + $newSfe.func->elementToPath()); - - let inputVals = $newSfe.parametersValues->at(0)->reactivate($state.inScopeVars) - ->map(v| $v->match([ - vs:ValueSpecification[1]|$vs, - a:Any[1]| - ^InstanceValue( - genericType = $newSfe.parametersValues->at(0).genericType, - multiplicity = PureOne, - values = $v->toOne() - ) - ])); - - let lambda = $newSfe.parametersValues->at(1)->cast(@InstanceValue).values->toOne()->cast(@FunctionDefinition);//->evaluateAndDeactivate(); - //TODO: This should really be pre-checked as part of the If condition to get here (so that the assertion never fails) - assert($lambda->instanceOf(FunctionDefinition) && $lambda->cast(@FunctionDefinition).expressionSequence->size() == 1); - - let intialAccumlatorVal = $newSfe.parametersValues->at(2); - - let resultVal = $inputVals->fold({v,a| - let newState = $state->addToScope($lambda, [], $v->concatenate($a.value->cast(@ValueSpecification))->evaluateAndDeactivate()); - - let subSfe1 = $lambda.expressionSequence->toOne(); - let subSfe = ^$subSfe1(genericType = $sfe.genericType->resolveGenericType($newState).value); - - $subSfe->prevalInternal($newState, $extensions); - }, $intialAccumlatorVal->prevalInternal($state, $extensions)); - - $resultVal->markModified(); - }), - pair( - {theSfe:SimpleFunctionExpression[1]| - ($newSfe.func == concatenate_T_MANY__T_MANY__T_MANY_) - && ($newSfe.parametersValues.multiplicity->forAll(m|$m->in([PureOne, PureZero]) || $m->isMultiplicityConcrete() && $m->hasUpperBound() && $m->hasLowerBound() && ($m.lowerBound.value == $m.upperBound.value))) - /* - This last condition should not be needed, but there are circumstances where the inferred type of some lambdas is incorrect. - So this is guard to avoid trying to pre-optimise in those situations (where in the example below, we'd optimised to the lambda - simply return $index because accumulator is detected as a PureZero) - - let input = {| - [1,2]->fold({index, accumulator| - $accumulator->concatenate($index); - }, []) - }; - - let foldLambda = $input->evaluateAndDeactivate().expressionSequence->cast(@SimpleFunctionExpression).parametersValues->at(1)->cast(@InstanceValue).values->cast(@FunctionDefinition)->toOne(); - let foldLambdaAccumulatorParam = $foldLambda->functionType().parameters->at(1); - - assertEquals(ZeroMany, $input->functionReturnMultiplicity(), 'Original lambda return multiplicty expected (zero many)'); - assertEquals($foldLambda->functionReturnMultiplicity(), $input->functionReturnMultiplicity(), | 'Fold lambda return multiplicity matches the original function multiplicity'); - //fails - assertEquals($foldLambda->functionReturnMultiplicity(), $foldLambdaAccumulatorParam.multiplicity, 'Accumulator parameter multiplicity matches the return multiplicty of the fold lambda: %r vs %r', [$foldLambda->functionReturnMultiplicity(), $foldLambdaAccumulatorParam.multiplicity]); - - assertEquals(Integer, $input->functionReturnType().rawType, | 'Original lambda return type expected (integer)'); - assertEquals($foldLambda->functionReturnType().rawType, $input->functionReturnType().rawType, | 'Fold lambda matchs original lambda return type'); - //fails - assertEquals($foldLambda->functionReturnType().rawType, $foldLambdaAccumulatorParam.genericType.rawType, 'Accumulator parameter matches lambda return type: %r vs %r', [$foldLambda->functionReturnType().rawType->toOne(), $foldLambdaAccumulatorParam.genericType.rawType->toOne()]); - - */ - && $newParamWrappers->filter(pw|$pw.value->match([v:VariableExpression[1]|$v.multiplicity == PureZero, a:Any[*]|false]))->areAllInScope($state.inScopeVars) - }, - {theSfe:SimpleFunctionExpression[1]| - $state->printDebugWithDepth(|'Handling expand: ' + $newSfe.func->elementToPath()); - $state->printDebugWithDepth(|'Parameter multiplicities: ' + $newSfe.parametersValues.multiplicity->map(x|$x->makeString())->joinStrings('[',',',']')); - - let r = if($newSfe.parametersValues->at(0).multiplicity == PureZero, - | $newSfe.parametersValues->at(1);, - | if($newSfe.parametersValues->at(1).multiplicity == PureZero, - | $newSfe.parametersValues->at(0);, - | let parametersValues = $newSfe.parametersValues - ->map(x|$x->match([iv:InstanceValue[*]| $iv.values;, a:Any[*]| $a])); - - ^InstanceValue( - genericType = $newSfe.genericType->resolveGenericType($state).value, - multiplicity = $parametersValues->size()->toMultiplicity(), - values = $parametersValues - )->evaluateAndDeactivate() - ->map(x|^$x(values = $parametersValues)); - ) - ); - - ^PrevalWrapper( - value = $r, - canPreval = $newParamWrappers->canPreval(), - openVars = $newParamWrappers->openVars($state.inScopeVars), - modified = true - ); - }), - pair( - {theSfe:SimpleFunctionExpression[1]|$newSfe.func == meta::pure::functions::lang::cast_Any_m__T_1__T_m_ && $newParamWrappers.value->at(0)->map(x|$x->instanceOf(InstanceValue) && $x->cast(@InstanceValue).values->isEmpty())}, - {theSfe:SimpleFunctionExpression[1]| - //This special handling should not be required but []->cast(@MyType) seems to fail in compiled mode - - $state->printDebugWithDepth(|'Handling cast of empty collection: ' + $newSfe.func->elementToPath()); - - - let r = ^InstanceValue( - genericType = $newSfe.genericType->resolveGenericType($state).value, - multiplicity = $newSfe.multiplicity, - values = [] - )->evaluateAndDeactivate(); - - ^PrevalWrapper( - value = $r, - canPreval = true, - openVars = [], - modified = true - ); - }), - pair( - {theSfe:SimpleFunctionExpression[1] | - //TODO getAll check is only needed as router does not route FunctionDefinition wth constant result. - isFilterFunctionReturningConstant($theSfe, false) && !isGetAll($theSfe.parametersValues->at(0))}, - {theSfe:SimpleFunctionExpression[1] | - $state->printDebugWithDepth(|'Handling filter which returns false: ' + $newSfe.func->elementToPath()); - - let r = ^InstanceValue( - genericType = ^GenericType(rawType = Nil), - multiplicity = PureZero - )->evaluateAndDeactivate(); - - ^PrevalWrapper( - value = $r, - canPreval = true, - openVars = [], - modified = true - ); - } - ), - pair( - {theSfe:SimpleFunctionExpression[1] | isFilterFunctionReturningConstant($theSfe, true)}, - {theSfe:SimpleFunctionExpression[1] | - $state->printDebugWithDepth(|'Handling filter which returns true: ' + $newSfe.func->elementToPath()); - - ^PrevalWrapper( - value = $theSfe.parametersValues->at(0), - canPreval = true, - openVars = [], - modified = true - ); - } - ), - pair( - {theSfe:SimpleFunctionExpression[1] | ($theSfe.func == toOneMany_T_MANY__T_$1_MANY$_) && - $theSfe.parametersValues->match([ - vs:ValueSpecification[1]|$vs.multiplicity->isMultiplicityConcrete() && $vs.multiplicity->hasLowerBound() - && ($vs.multiplicity->getLowerBound() > 0), - a:Any[*]|false - ])}, - {theSfe:SimpleFunctionExpression[1] | - $state->printDebugWithDepth(|'Handling toOneMany'); - - $newParamWrappers->toOne()->map(pw|^$pw(modified=true)); - } - ), - pair( - {theSfe:SimpleFunctionExpression[1] | ($theSfe.func == toOne_T_MANY__T_1_) && - $theSfe.parametersValues->match([ - vs:VariableExpression[1]|$vs.multiplicity->isMultiplicityConcrete() - && $vs.multiplicity == PureOne, - a:Any[*]|false - ])}, - {theSfe:SimpleFunctionExpression[1] | - $state->printDebugWithDepth(|'Handling toOne'); - - $newParamWrappers->toOne()->map(pw|^$pw(modified=true)); - } - ) - ]); - - let handler = $handlers->filter(p|$p.first->eval($newSfe))->first(); - - if($handler->isNotEmpty(), - | $handler->toOne().second->eval($newSfe), - | $state->printDebugWithDepth(|'Not prevalling: ' + $newSfe.func->elementToPath() + ' (' + $notPrevalReason->toOne() + ')'); - - ^PrevalWrapper( - value = $newSfe, - canPreval = $newParamWrappers->canPreval(), - openVars = $newParamWrappers->openVars($state.inScopeVars), - modified = $newSfeModified - ); - );, - | - $state->printDebugWithDepth(|'Performing preval: ' + $newSfe.func->elementToPath() + ' (can recativate dynamically: '+ $newSfe->canReactivateDynamically()->makeString() + ')'); - - let valX = $newSfe->reactivate($state.inScopeVars)->cast(@Any); - - let val = $valX->map(x|$x->match([ - {bcs : BasicColumnSpecification[1]| prevalInternal($bcs, ^$state(inScopeTypeParams = ^Map(), inScopeVars = ^Map>()), $extensions).value}, - {av : meta::pure::functions::collection::AggregateValue[1]| prevalInternal($av, ^$state(inScopeTypeParams = ^Map(), inScopeVars = ^Map>()), $extensions).value}, - {av : meta::pure::tds::AggregateValue[1]| prevalInternal($av, ^$state(inScopeTypeParams = ^Map(), inScopeVars = ^Map>()), $extensions).value}, - {a:Any[1]|$a} - ])); - - let iv = $val->match([ - x : InstanceValue[1]| $x, - a : Any[*]| ^InstanceValue(genericType = $newSfe.genericType,multiplicity = $val->size()->toMultiplicity(), values = $val); - ])->evaluateAndDeactivate(); - ^PrevalWrapper(value = $iv, canPreval = true, modified = true); - ); - ); - ); - ); -} - -function meta::pure::router::preeval::isGetAll(v:ValueSpecification[0..1]):Boolean[1] -{ - $v->match([ - s:SimpleFunctionExpression[1] | meta::pure::router::routing::isGetAllFunction($s.func) || $s.parametersValues->first()->isGetAll(), - a:Any[*] | false; - ]); -} - -function <> meta::pure::router::preeval::isFilterFunctionReturningConstant(sfe:SimpleFunctionExpression[1], value:Boolean[1]) : Boolean[1] -{ - let isFilter = $sfe.func == filter_T_MANY__Function_1__T_MANY_; - - if ($isFilter, - | - let es = $sfe.parametersValues->last()->cast(@InstanceValue).values->cast(@LambdaFunction).expressionSequence->evaluateAndDeactivate(); - $es->last()->toOne()->instanceOf(InstanceValue) && $es->last()->toOne()->cast(@InstanceValue).values == $value;, - | false); -} - -// ==================================================================================================================================================================== -// State Building Functions -// ==================================================================================================================================================================== - -function <> meta::pure::router::preeval::defaultFunctionInlineStrategy(extensions:Extension[*]) : FunctionDefinition<{Function[1]->Boolean[1]}>[1] -{ - {f:Function[1] | $f->in([col_Function_1__String_1__BasicColumnSpecification_1_, - col_Function_1__String_1__String_1__BasicColumnSpecification_1_, - // TODO: We should confirm presence of below 2 functions - meta::pure::functions::date::mostRecentDayOfWeek_DayOfWeek_1__Date_1_, - meta::pure::functions::date::previousDayOfWeek_DayOfWeek_1__Date_1_]) - || (!meta::pure::router::routing::shouldStop($f, $extensions) && !($f->instanceOf(NativeFunction)))} -} - -function <> meta::pure::router::preeval::defaultPreevalStopStrategy(extensions:Extension[*]) : FunctionDefinition<{Any[1]->Boolean[1]}>[1] -{ - {a:Any[1] | stopPreeval($a, $extensions)}; -} - -function meta::pure::router::preeval::getPreevalStateWithAdditionalStopInlineFunc(inScopeVars:Map>[1], extensions:meta::pure::extension::Extension[*], stopInlineFunctions:Function[*]):meta::pure::router::preeval::State[1] -{ - ^meta::pure::router::preeval::State(inScopeVars = $inScopeVars, - shouldInlineFxn = {f:Function[1] | meta::pure::router::preeval::defaultFunctionInlineStrategy($extensions)->eval($f) - && !$f->in($stopInlineFunctions)}, - stopPreeval = meta::pure::router::preeval::defaultPreevalStopStrategy($extensions), - rollingInScopeVars = $inScopeVars, - debug = noDebug(), - inScopeTypeParams = ^Map()); -} - -function <> meta::pure::router::preeval::stopPreeval(value : Any[*], extensions:Extension[*]) : Boolean[1] -{ - $extensions.routerExtensions().shouldStopPreeval->exists(p | $p->eval($value)) || - $value->match([ - p : String[*]| true, - p : Float[*]| true, - p : Decimal[*] | true, - p : Enum[*]| true, - p : StrictDate[*]| true, - p : LatestDate[*]| true, - p : Integer[*]| true, - p : Boolean[*]| true, - p : DateTime[*]| true, - p : SortInformation[*]| true, - p : Enumeration[*]| true, - p : Class[*]| true, - p : Pair[*]| true, - p : Property[*]| true, - p : Type[*]| true, - p : Path[*]|true, - p : meta::pure::mapping::Mapping[1]| true, - p : meta::pure::runtime::PackageableRuntime[1]| true, - p : meta::pure::runtime::Runtime[1]| true, - p : meta::pure::runtime::Connection[1] | true, - p : meta::pure::runtime::ExecutionContext[1]| true, - p : meta::pure::tds::TDSColumn[*]| true, - p : TdsOlapRank[*]| true, - p : TdsOlapAggregation[*]| true, - p : meta::pure::graphFetch::execution::AlloySerializationConfig[1]| true, - f : SimpleFunctionExpression[1]| $f.func->in([letFunction_String_1__T_m__T_m_]), - a : Any[*]| $a->type() - ->match([ - c:Class[1]|$c->isSimpleType(), - o:Any[1]|false - ]) - ]); -} - -function <> meta::pure::router::preeval::isSimpleType(c : Class[1]) : Boolean[1] -{ - $c->isSimpleType([]); -} - -function <> meta::pure::router::preeval::isSimpleType(c : Class[1], visitedClasses : Class[*]) : Boolean[1] -{ - let types = $c.properties->map(p|$p->functionReturnType().rawType) - ->distinct() - ->removeAll([String, Integer, Date, DateTime, StrictDate, LatestDate,Float]) - ->removeAll($visitedClasses) - ->filter(x|!$x->instanceOf(Enumeration)); - - let newClassTypes = $types->filter(x|$x->instanceOf(Class))->cast(@Class); - let unsupporedBasicTypes = $types->removeAll($newClassTypes); - - $unsupporedBasicTypes->isEmpty() && $newClassTypes->forAll(newC|$newC->isSimpleType($visitedClasses->concatenate($newClassTypes))); -} - - -// ==================================================================================================================================================================== -// Utility Functions -// ==================================================================================================================================================================== - -function <> meta::pure::router::preeval::resolveGenericType(in : GenericType[1], state : meta::pure::router::preeval::State[1]):PrevalWrapper[1] -{ - if($in.typeParameter->isEmpty(), - | - let newTypeArgsWrappers = $in.typeArguments->map(ta|$ta->resolveGenericType($state)); - if(!$newTypeArgsWrappers->anyModified(), - | ^PrevalWrapper(value = $in, canPreval=true, modified = false), - | let value = ^$in(typeArguments = $newTypeArgsWrappers.value); - ^PrevalWrapper(value = $value, canPreval=$newTypeArgsWrappers->canPreval(), modified = $newTypeArgsWrappers->anyModified()); - );, - | - let value = $state.inScopeTypeParams->get($in.typeParameter->toOne().name); - if($value->isEmpty(), - | ^PrevalWrapper(value = $in, canPreval=true, modified = false), - | ^PrevalWrapper(value = $value->toOne(), canPreval=true, modified = true) - ); - ); -} - -function <> meta::pure::router::preeval::shouldInline(f:Function[1], state : meta::pure::router::preeval::State[1]):Boolean[1] -{ - ($f->instanceOf(FunctionDefinition) && ($f->cast(@FunctionDefinition).expressionSequence->size() == 1 )) - && !($f->instanceOf(QualifiedProperty) && $f->cast(@QualifiedProperty).owner == TDSRow) - && !($f->instanceOf(AbstractProperty) && $f->cast(@AbstractProperty)->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype()) - && ($state.shouldInlineFxn->eval($f)) - && ($state.path->filter(x|$x == $f)->isEmpty()) -} - -function <> meta::pure::router::preeval::canInlineEvalFunctionExpression(fe:SimpleFunctionExpression[1]):Boolean[1] -{ - $fe.func->in([eval_Function_1__V_m_, - eval_Function_1__T_n__V_m_, - eval_Function_1__T_n__U_p__V_m_, - eval_Function_1__T_n__U_p__W_q__V_m_, - eval_Function_1__T_n__U_p__W_q__X_r__V_m_, - eval_Function_1__T_n__U_p__W_q__X_r__Y_s__V_m_, - eval_Function_1__T_n__U_p__W_q__X_r__Y_s__Z_t__V_m_, - eval_Function_1__S_n__T_o__U_p__W_q__X_r__Y_s__Z_t__V_m_]) - && $fe.parametersValues->at(0)->match([ - iv:InstanceValue[1]|$iv.values->toOne(), - vs:ValueSpecification[1]|$vs - ])->instanceOf(FunctionDefinition) - && $fe.parametersValues->at(0)->match([ - iv:InstanceValue[1]|$iv.values->toOne(), - vs:ValueSpecification[1]|$vs - ])->cast(@FunctionDefinition).expressionSequence->size() == 1 -} - -function <> meta::pure::router::preeval::toMultiplicity(size : Integer[1]) : Multiplicity[1] -{ - if($size == 0, - | PureZero, - | if($size == 1, - | PureOne, - | ^Multiplicity(lowerBound = ^MultiplicityValue(value=$size), upperBound = ^MultiplicityValue(value=$size)) - ); - ); -} - -function <> meta::pure::router::preeval::addToScope(sfe : SimpleFunctionExpression[1], state : meta::pure::router::preeval::State[1]):meta::pure::router::preeval::State[1] -{ - addToScope($state, $sfe.func, $sfe.resolvedTypeParameters, $sfe.parametersValues, true); -} - -function <> meta::pure::router::preeval::addToScope(state : meta::pure::router::preeval::State[1], func : Function[1],resolvedTypeParameters : GenericType[*], parametersValues : ValueSpecification[*]):meta::pure::router::preeval::State[1] -{ - addToScope($state, $func, $resolvedTypeParameters, $parametersValues, false) -} - -function <> meta::pure::router::preeval::addToScope(state : meta::pure::router::preeval::State[1], func : Function[1],resolvedTypeParameters : GenericType[*], parametersValues : ValueSpecification[*], cleanupInScopeVarsAndTypeParams:Boolean[1]):meta::pure::router::preeval::State[1] -{ - let typeParams = $func->functionType().typeParameters.name->zip($resolvedTypeParameters->map(x|$x->resolveGenericType($state).value)); - - let newParams = $parametersValues; - - let funcParamNames = $func->functionType().parameters.name; - let additionalInScopeVars = $funcParamNames - ->zip($newParams->map(pv|list($pv))) - ->map({p| let paramName = $p.first; - let paramValue = $p.second; - - $paramValue.values->match([ - ve:VariableExpression[1]| - if($ve.name == $paramName, - | [], //We drop easy "circular" / self-reference parameter variables - | $p - ), - vs:ValueSpecification[*]| - $p; - ]); - }); - - if($cleanupInScopeVarsAndTypeParams, |^$state(inScopeVars=newMap([]), inScopeTypeParams=newMap([])), |$state)->addToScope($additionalInScopeVars, $typeParams); -} - -function <> meta::pure::router::preeval::addToScope(state : meta::pure::router::preeval::State[1], additionalInScopeVars : Pair>[*], typeParams : Pair[*]):meta::pure::router::preeval::State[1] -{ - $state->printDebugWithDepth(|'Adding variables to scope: ' + $additionalInScopeVars.first->joinStrings('[', ',', ']')); - $state->printDebugWithDepth(|'Adding type params to scope: ' + $typeParams.first->joinStrings('[', ',', ']')); - - let newInScopeVars = $state.inScopeVars->putAll($additionalInScopeVars); - let newInScopeTypeParams = $state.inScopeTypeParams->putAll($typeParams); - - ^$state(inScopeVars = $newInScopeVars, inScopeTypeParams = $newInScopeTypeParams); -} - -function <> meta::pure::router::preeval::resolveVariable(name : String[1], state : meta::pure::router::preeval::State[1]):String[1] -{ - $state.inScopeVars->get($name).values->match([ - ve:VariableExpression[1]|assert($name != $ve.name, 'Circular variable reference: ' + $name); $ve.name->resolveVariable($state);, - a:Any[*]|$name - ]); -} - -function <> meta::pure::router::preeval::canPreval(items : PrevalWrapper[*]):Boolean[1] -{ - $items->forAll(p | $p.canPreval); -} - -function <> meta::pure::router::preeval::markModified(item : PrevalWrapper[1]):PrevalWrapper[1] -{ - if($item.modified, - | $item, - | ^$item(modified = true) - ); -} - -function <> meta::pure::router::preeval::anyModified(items : PrevalWrapper[*]):Boolean[1] -{ - $items.modified->contains(true); -} - -function <> meta::pure::router::preeval::openVars(items : PrevalWrapper[*], inScopeVars:Map>[1]):String[*] -{ - $items.openVars->openVars($inScopeVars); -} - -function <> meta::pure::router::preeval::openVars(vars : String[*], inScopeVars:Map>[1]):String[*] -{ - $vars->distinct()->filter(v|!$v->areAllInScope($inScopeVars)) -} - -function <> meta::pure::router::preeval::areAllInScope(items : PrevalWrapper[*], inScopeVars:Map>[1]):Boolean[1] -{ - $items.openVars->distinct()->areAllInScope($inScopeVars); -} - -function <> meta::pure::router::preeval::areAllInScope(vars : String[*], inScopeVars:Map>[1]):Boolean[1] -{ - if($vars->isEmpty(), - | true, - | let unresolvedVars = $vars->map(v|let varVal = $inScopeVars->get($v); - - if($varVal->isEmpty(), - | $v, - | $varVal.values->match([ - varExp : VariableExpression[1]|$varExp.name, - a : Any[*]|[] - ]) - ); - )->distinct(); - - let newVars = $unresolvedVars->removeAll($vars); - !$unresolvedVars->containsAny($vars) && $newVars->areAllInScope($inScopeVars); - ); -} - -function <> meta::pure::router::preeval::isInstanceValue(v: Any[1], inScopeVars:Map>[1]):Boolean[1] -{ - $v->match([ - iv : InstanceValue[1]| - $iv.values->forAll(subV|$subV->match([ - subVS : ValueSpecification[1]|$subVS->isInstanceValue($inScopeVars), - a : Any[1]| true; - ]));, - ve : VariableExpression[1]|$inScopeVars->get($ve.name)->isNotEmpty(), - sfe : SimpleFunctionExpression[1]|false; - ]) -} - -function <> meta::pure::router::preeval::printDebugWithDepth(state: meta::pure::router::preeval::State[1], func:Function<{->String[1]}>[1]):Nil[0] -{ - printDebug($state, | ('[' + toString($state.depth->orElse(''))->map(s|'0000'->substring(0, max(0, 3-$s->length())) + $s) + ']' + range($state.depth->orElse(0) + 1)->map(d|'')->joinStrings('', '-', ' ') + $func->eval())); -} - -function <> meta::pure::router::preeval::printDebug(state: meta::pure::router::preeval::State[1], func:Function<{->Any[*]}>[1]):Nil[0] -{ - printDebug($state.debug, $func); -} - -function <> meta::pure::router::preeval::printDebug(debugContext : DebugContext[1], func:Function<{->Any[*]}>[1]):Nil[0] -{ - if(!$debugContext.debug, - | print(''), - | printDebug($debugContext, $func->eval()); - ); -} - -function <> meta::pure::router::preeval::printDebug(state: meta::pure::router::preeval::State[1], param:Any[*]):Nil[0] -{ - printDebug($state.debug, $param); -} - -function <> meta::pure::router::preeval::printDebug(debugContext : DebugContext[1], param:Any[*]):Nil[0] -{ - if(!$debugContext.debug, - | print(''), - | println($param); - ); +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import meta::pure::router::preeval::*; +import meta::pure::extension::*; +import meta::pure::metamodel::path::*; +import meta::pure::router::extension::*; +import meta::pure::router::utils::*; + +Class meta::pure::router::preeval::State +{ + inScopeVars : Map>[1]; + rollingInScopeVars : Map>[1]; + inScopeTypeParams : Map[1]; + + debug : DebugContext[1]; + depth : Integer[0..1]; + path : FunctionDefinition[*]; + + shouldInlineFxn : FunctionDefinition<{Function[1] -> Boolean[1]}>[1]; + stopPreeval : Function<{Any[1] -> Boolean[1]}>[1]; + + toString() { $this->simpleToString() }:String[1]; +} + +Class meta::pure::router::preeval::PrevalWrapper +{ + canPreval : Boolean[1]; + value : T[1]; + openVars : String[*]; + modified : Boolean[1]; + + toString() { $this->simpleToString() }:String[1]; +} + + +// ==================================================================================================================================================================== +// Public Apis +// ==================================================================================================================================================================== + +function <> meta::pure::router::preeval::preval(f:FunctionDefinition[1], extensions:Extension[*]):FunctionDefinition[1] +{ + preval($f, $extensions, noDebug()); +} + +function <> meta::pure::router::preeval::preval(f:FunctionDefinition[1], extensions:Extension[*], debug:DebugContext[1]):FunctionDefinition[1] +{ + preval($f, newMap([]->cast(@Pair)), $f->openVariableValues(), $extensions, $debug).value->toOne()->cast($f); +} + +function <> meta::pure::router::preeval::preval(f : FunctionDefinition[1], vars:Map[1], inScopeVars:Map>[1], extensions:Extension[*]):PrevalWrapper>[1] +{ + preval($f, $vars, $inScopeVars, $extensions, noDebug()); +} + +function <> meta::pure::router::preeval::preval(f : FunctionDefinition[1], vars:Map[1], inScopeVars:Map>[1], extensions:Extension[*], debug:DebugContext[1]):PrevalWrapper>[1] +{ + let state = ^meta::pure::router::preeval::State(inScopeVars = $inScopeVars, + shouldInlineFxn = defaultFunctionInlineStrategy($extensions), + stopPreeval = defaultPreevalStopStrategy($extensions), + rollingInScopeVars = $inScopeVars, + debug = $debug, + inScopeTypeParams = ^Map()); + preval($f, $state, $extensions); +} + +function <> meta::pure::router::preeval::preval(f : FunctionDefinition[1], state:State[1], extensions:Extension[*]):PrevalWrapper>[1] +{ + let r = prevalInternal($f->evaluateAndDeactivate(), $state, $extensions)->toOne()->cast(@PrevalWrapper>); + + if(!$r->anyModified(), + | + $state->printDebug(|'No changes made for: '); + $state->printDebug(|$f->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::toPureGrammar::toPure($extensions));, + | + let res = $r.value->toOne(); + + // $state->printDebug('Transformed From:'); + // $state->printDebug(|$f->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::json::toJSON(50000)); + // $state->printDebug(|$f->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::protocols::pure::vX_X_X::transformation::toPureGrammar::toPure($extensions)); + + $state->printDebug('Transformed To:'); + $state->printDebug(|$res->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::json::toJSON(50000)); + $state->printDebug(|$res->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::transformFunctionBody($extensions)->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::toPureGrammar::toPure($extensions)); + + $state->printDebug($res); + ); + + $r; +} + +function <> meta::pure::router::preeval::preval(f : FunctionExpression[1], inScopeVars:Map>[1], extensions:Extension[*], debug:DebugContext[1]):ValueSpecification[1] +{ + let state = ^meta::pure::router::preeval::State(inScopeVars = $inScopeVars, + shouldInlineFxn = defaultFunctionInlineStrategy($extensions), + stopPreeval = defaultPreevalStopStrategy($extensions), + rollingInScopeVars = $inScopeVars, + debug = $debug, + inScopeTypeParams = ^Map()); + prevalInternal($f, $state, $extensions).value->cast(@ValueSpecification); +} + +// ==================================================================================================================================================================== +// Processor Functions +// ==================================================================================================================================================================== + +function <> meta::pure::router::preeval::prevalInternal(item : Any[1], origState : meta::pure::router::preeval::State[1], extensions:Extension[*]):PrevalWrapper[1] +{ + let state = ^$origState(depth = if($origState.depth->isEmpty(), | 0, | $origState.depth->toOne() + 1), + path = if(!$item->instanceOf(FunctionDefinition), | $origState.path, | $origState.path->concatenate($item->cast(@FunctionDefinition)))); + + + $state->printDebugWithDepth(|'Processing ' + $item->type()->makeString() + ' ' + + $item->evaluateAndDeactivate() + ->match([ + iv:InstanceValue[1] | $iv.values->map(x|$x->type())->makeString(), + sfe:SimpleFunctionExpression[1] | '(' + $sfe.func->elementToPath() + ')', + ve:VariableExpression[1] | '(' + $ve.name + ')', + a:Any[*]|''] + ) + + ', inScopeVars: ' + $state.inScopeVars->keys()->joinStrings('[', ',', ']')); + + let newItem = $item->evaluateAndDeactivate() + ->match([ + {lambda : LambdaFunction[1]| + let fnArgNames = $lambda->functionType().parameters.name; + let fnOpenVariables = $lambda->openVariableValues()->putAll($state.inScopeVars->keyValues()->filter(kv | !$kv.first->in($fnArgNames))->newMap()); + + $lambda->prevalFunctionDefinition(^$state(inScopeVars = $fnOpenVariables), $extensions); + }, + {fd : FunctionDefinition[1]| + $fd->prevalFunctionDefinition(^$state(inScopeVars = newMap([])), $extensions); // Assigning empty map to inScopeVars as function definition can't have openVars + }, + {sfe : SimpleFunctionExpression[1]| + let handlers = [ + // Avoid handling known 'false' case (which may not be evalulatable / have runtime issues, e.g. a toOne() after an isNotEmpty condition) + ^Pair, LambdaFunction<{->PrevalWrapper[1]}>>(first = if_Boolean_1__Function_1__Function_1__T_m_, second = {| + let conditionPreVal = $sfe.parametersValues->at(0)->prevalInternal($state, $extensions); + if($conditionPreVal.value->toOne()->cast(@ValueSpecification)->isInstanceValue($state.inScopeVars), + | let valueValueSpecification = if($conditionPreVal.value->cast(@InstanceValue).values == true, + | $sfe.parametersValues->at(1), + | $sfe.parametersValues->at(2) + ); + let newSfe = ^$sfe(func = eval_Function_1__V_m_, parametersValues = $valueValueSpecification); + + $newSfe->prevalInternal($state, $extensions)->markModified();, + | prevalGenericSimpleFunctionExpression($sfe, $state, $extensions); + ); + }), + + // Avoid handling conditions after the first false (which may not be evalulatable / have runtime issues, e.g. a toOne() after an isNotEmpty condition) + ^Pair, LambdaFunction<{->PrevalWrapper[1]}>>(first = meta::pure::functions::boolean::and_Boolean_1__Boolean_1__Boolean_1_, second = {| + prevalBooleanSimpleFunctionExpression($sfe, $state, $extensions); + }), + + // Avoid handling conditions after the first true (which may not be evalulatable / have runtime issues, e.g. a toOne() after an isEmpty condition) + ^Pair, LambdaFunction<{->PrevalWrapper[1]}>>(first = meta::pure::functions::boolean::or_Boolean_1__Boolean_1__Boolean_1_, second = {| + prevalBooleanSimpleFunctionExpression($sfe, $state, $extensions); + }) + + // TODO: + // 1. Optimise and_Boolean_MANY__Boolean_1_ and or_Boolean_$1_MANY$__Boolean_1_ + // 1. Optimise plus(many), minus(many) and times(many) (plus_String_MANY__String_1_, plus_Float_MANY__Float_1_, times_Number_MANY__Number_1_, minus_Float_MANY__Float_1_) + ]; + + let handler = $handlers->filter(p|$p.first == $sfe.func).second; + + if($handler->isNotEmpty(), + | $handler->toOne()->eval(), + | prevalGenericSimpleFunctionExpression($sfe, $state, $extensions)); + }, + {ve : VariableExpression[1]| + let varValue = $state.inScopeVars->get($ve.name).values; + let iv = if($state.inScopeVars->get($ve.name)->isEmpty(), + | ^PrevalWrapper(value = $ve, canPreval = true, modified = false, openVars = [$ve.name]), + | $varValue->match([ + n : Nil[0]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(), + iv : InstanceValue[1]| $iv, + ve : VariableExpression[1]| $ve->evaluateAndDeactivate(), + sfe : SimpleFunctionExpression[1]| $sfe, + a : meta::pure::tds::AggregateValue[*]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(), + a : meta::pure::functions::collection::AggregateValue[*]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(), + a : meta::pure::tds::BasicColumnSpecification[*]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(), + r : meta::pure::graphFetch::RootGraphFetchTree[1]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate();, + b : meta::external::format::shared::binding::Binding[1]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate();, + sc : meta::pure::tds::schema::SchemaState[1]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $sc)->evaluateAndDeactivate(), + s : meta::pure::store::Store[1]| ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate();, + a : Any[*]| assert($state.stopPreeval->eval($a), | 'Unsupported type: ' + $a->type()->match([pe:PackageableElement[1]|$pe->elementToPath(), t:Type[1]|$t->makeString()])); + ^InstanceValue(multiplicity = $ve.multiplicity, genericType = $ve.genericType, values = $varValue)->evaluateAndDeactivate(); + ]) + ->map(x| + let newState = ^$state(inScopeVars = $state.inScopeVars->keyValues()->filter(p|$p.first != $ve.name)->newMap()); + $x->prevalInternal($newState, $extensions); + ) + ->markModified() + ); + }, + {iv : InstanceValue[1]| + let subVals = $iv.values->map(x|$x->prevalInternal($state, $extensions)); + let r = if(!$subVals->anyModified(), + | pair(false, $iv), + | let cleanIv = ^$iv(values = $subVals.value->map(x|$x->match([subIv:InstanceValue[1]|if($subIv.values->size() == 1, |$subIv.values, |$subIv), a:Any[*]|$a]))); + let cleanIv2 = ^$cleanIv(genericType = $cleanIv.genericType->resolveGenericType($state).value, + multiplicity = if($cleanIv.multiplicity == PureOne && $cleanIv.values->isEmpty(), + | $cleanIv.multiplicity, //This seems to be a quirk of Cast(@ABC) + | $cleanIv.values->size()->toMultiplicity()) + ); + pair(true, $cleanIv2); + ); + ^PrevalWrapper(value = $r.second, canPreval = $subVals->canPreval(), openVars = $subVals->openVars($state.inScopeVars), modified = $r.first); + }, + {k : KeyExpression[1]| + let e = $k.expression->prevalInternal($state, $extensions)->toOne(); + let ke = if(!$e->anyModified(), + | pair(false, $k), + | pair(true, ^$k(expression = $e.value->cast(@ValueSpecification)->toOne()))); + + ^PrevalWrapper(value = $ke.second, canPreval = $e.canPreval, openVars = $e->openVars($state.inScopeVars), modified = $ke.first); + }, + {p : ColumnSpecification[1]| + $p->match([ + bcs : BasicColumnSpecification[1]| + let f = $bcs.func->evaluateAndDeactivate(); + let newFuncWrapper = prevalInternal($f, ^$state(inScopeTypeParams = ^Map(), inScopeVars = $state.inScopeVars->putAll($f->openVariableValues())), $extensions); + + if(!$newFuncWrapper->anyModified(), + | ^PrevalWrapper(value = $bcs, canPreval = true, modified = false), + | ^PrevalWrapper(value = ^$bcs(func = $newFuncWrapper.value->cast($f)), canPreval = true, modified = true); + );, + o:ColumnSpecification[1] | ^PrevalWrapper(value = $o, canPreval = true, modified = false); + ]); + + }, + {av : meta::pure::tds::AggregateValue[1]| + let mapFn = $av.mapFn->evaluateAndDeactivate(); + let aggFn = $av.aggregateFn->evaluateAndDeactivate(); + + let newFuncWrapper_map = prevalInternal($mapFn, $state, $extensions); + let newFuncWrapper_agg = prevalInternal($aggFn, $state, $extensions); + + if(![$newFuncWrapper_map, $newFuncWrapper_agg]->anyModified(), + | ^PrevalWrapper(value = $av, canPreval = true, modified = false), + | ^PrevalWrapper(value = ^$av(mapFn = $newFuncWrapper_map.value->cast($mapFn), aggregateFn = $newFuncWrapper_agg.value->cast($aggFn)), canPreval = true, modified = true); + ); + }, + {av : meta::pure::functions::collection::AggregateValue[1]| + let mapFn = $av.mapFn->evaluateAndDeactivate(); + let aggFn = $av.aggregateFn->evaluateAndDeactivate(); + + let newFuncWrapper_map = prevalInternal($mapFn, $state, $extensions); + let newFuncWrapper_agg = prevalInternal($aggFn, $state, $extensions); + + if(![$newFuncWrapper_map, $newFuncWrapper_agg]->anyModified(), + | ^PrevalWrapper(value = $av, canPreval = true, modified = false), + | ^PrevalWrapper(value = ^$av(mapFn = $newFuncWrapper_map.value->cast($mapFn), aggregateFn = $newFuncWrapper_agg.value->cast($aggFn)), canPreval = true, modified = true); + ); + }, + {s: meta::pure::tds::schema::SchemaState[1] | ^PrevalWrapper(value = $s, canPreval = true, modified = false);}, + {r: meta::pure::graphFetch::RootGraphFetchTree[1] | ^PrevalWrapper(value = $r, canPreval = true, modified = false);}, + {b: meta::external::format::shared::binding::Binding[1] | ^PrevalWrapper(value = $b, canPreval = true, modified = false);}, + {s: meta::pure::store::Store[1] | ^PrevalWrapper(value = $s, canPreval = true, modified = false);}, + {a : Any[1]| + assert($state.stopPreeval->eval($a), | 'Unsupported type: ' + $a->type()->match([pe:PackageableElement[1]|$pe->elementToPath(), t:Type[1]|$t->makeString()])); + ^PrevalWrapper(value = $a, canPreval = true, modified = false); + } + ]); + + $state->printDebugWithDepth(|'Returning ' + $newItem.value->type()->makeString() + ' ' + $newItem.value->match([iv:InstanceValue[1]|$iv.values->map(x|$x->type())->makeString(), sfe:SimpleFunctionExpression[1]|'(' + $sfe.func->elementToPath() + ')', ve : VariableExpression[1]|'(' + $ve.name + ')', a:Any[*]|'']) + ' ' + $newItem.toString()); + $newItem; +} + +function <> meta::pure::router::preeval::prevalFunctionDefinition(fd : FunctionDefinition[1], origState : meta::pure::router::preeval::State[1], extensions:Extension[*]):PrevalWrapper[1] +{ + let items = $fd.expressionSequence; + + let r = range($items->size()) + ->fold({index, p| + let item = $items->at($index); + let state = $p.first; + + let isLastExpression = ($index == ($items->size()-1)); + + if(!$isLastExpression && !$item->isLetFunction(), + | + $state->printDebugWithDepth(|'Dropping non-variable assignement non-return statement: ' + $item->type()->makeString() + ' ' + + $item->match([ + iv:InstanceValue[1]|$iv.values->map(x|$x->type())->makeString(), + sfe:SimpleFunctionExpression[1]|'(' + $sfe.func->elementToPath() + ')', + ve : VariableExpression[1]|'(' + $ve.name + ')', + a:Any[*]|'' + ]) + + ', inScopeVars: ' + $state.inScopeVars->keys()->joinStrings('[', ',', ']')); + + pair($state, $p.second);, + | + let r = $item->prevalInternal($state, $extensions); + + let isLetFunctionValueSpecification = $r.value->instanceOf(ValueSpecification) && $r.value->cast(@ValueSpecification)->isLetFunction(); + + if(!$isLetFunctionValueSpecification, + | pair($state, list(if($p.second.values->isEmpty(), | $r, | $p.second.values->concatenate($r)))), + | + let fe = $r.value->cast(@FunctionExpression); + + let varName = $fe.parametersValues->at(0)->reactivate($state.inScopeVars)->cast(@String)->toOne(); + let varValue = $fe.parametersValues->at(1) + ->match([ + iv:InstanceValue[1]|$iv.values, + fe:ValueSpecification[1]|$fe + ]); + + $state->printDebugWithDepth(|'adding variable ' + $varName + ' to rolling scope, with value ' + $varValue->makeString()); + + let shouldInlineVariableExpression = ($varValue->match([v:VariableExpression[1]|true, a: Any[*]|false])) + || ($r->canPreval() && $r->areAllInScope($state.inScopeVars) && !$varValue->match([v:LambdaFunction[1]|true, a: Any[*]|false])); + + let newRollingInScopeVars = $state.rollingInScopeVars->put($varName, list($varValue)); + let newInScopeVars = if(!$shouldInlineVariableExpression, + | $state.inScopeVars, + | $state.inScopeVars->put($varName, list($varValue)) + ); + let newState = ^$state(inScopeVars = $newInScopeVars, rollingInScopeVars = $newRollingInScopeVars); + + let expValue = if($isLastExpression, + | ^PrevalWrapper(canPreval = $r.canPreval, modified = true, openVars =$r.openVars, value = $varValue->match([vs:ValueSpecification[1]|$vs, a:Any[*]|^InstanceValue(genericType = $fe.genericType, multiplicity = $a->size()->toMultiplicity(), values = $a)])), + | if($shouldInlineVariableExpression, + | [], + | $r + ) + ); + if($expValue->isEmpty(), + | pair($newState, $p.second), + | pair($newState, list(if($p.second.values->isEmpty(), | $expValue, | $p.second.values->concatenate($expValue)))) + ); + ); + ); + }, [pair($origState, list([]->cast(@PrevalWrapper)))] + ).second.values; + + + /* + This should not be necessary, but seems to be required otherwise fold can end up wrapping InstanceValues in InstanceValues, + which map doesn't seem to + */ + let newExpressionSequncePrevalWrappers = $r->evaluateAndDeactivate() + ->match([ + iv:InstanceValue[1]|$iv.values->cast(@PrevalWrapper), + a:PrevalWrapper[*]|$a + ]); + + let value = if(($newExpressionSequncePrevalWrappers->size() == $items->size()) && !$newExpressionSequncePrevalWrappers->anyModified(), + | pair(false, $fd), + | let newFd = ^$fd(expressionSequence = $newExpressionSequncePrevalWrappers.value->cast(@ValueSpecification)->toOneMany()) + ->match([ + lf : LambdaFunction[1]| ^$lf(openVariables = $lf.openVariables->map(v|$v->resolveVariable($origState)) + ->distinct() + ->intersection($newExpressionSequncePrevalWrappers.openVars->map(v|$v->resolveVariable($origState)))), + fd2:FunctionDefinition[1]| $fd2 + ]); + pair(true, $newFd); + ); + + ^PrevalWrapper>( + value = $value.second, + canPreval = $newExpressionSequncePrevalWrappers->canPreval(), + openVars = $fd->match([lf : LambdaFunction[1]|$value.second->cast(@LambdaFunction).openVariables, fd:FunctionDefinition[1]|[]]), + modified = $value.first + ); +} + +function <> meta::pure::router::preeval::prevalBooleanSimpleFunctionExpression(sfe : SimpleFunctionExpression[1], state : meta::pure::router::preeval::State[1], extensions:Extension[*]):PrevalWrapper[1] +{ + assert($sfe.func->in([and_Boolean_1__Boolean_1__Boolean_1_, or_Boolean_1__Boolean_1__Boolean_1_])); + assertEquals(2, $sfe.parametersValues->size()); + + let shortcutVal = !($sfe.func == and_Boolean_1__Boolean_1__Boolean_1_); + + let newParam1Wrapper = $sfe.parametersValues->at(0)->prevalInternal($state, $extensions); + + if($newParam1Wrapper.value->toOne()->isInstanceValue($state.inScopeVars), + | if(($newParam1Wrapper.value->cast(@InstanceValue).values == $shortcutVal), + | $newParam1Wrapper->markModified(), + | let newParam2Wrapper = $sfe.parametersValues->at(1)->prevalInternal($state, $extensions); + $newParam2Wrapper->markModified();), + | let newParam2Wrapper = $sfe.parametersValues->at(1)->prevalInternal($state, $extensions); + + if($newParam2Wrapper.value->toOne()->isInstanceValue($state.inScopeVars), + | if($newParam2Wrapper.value->cast(@InstanceValue).values != $shortcutVal, + | $newParam1Wrapper->markModified(), + | $newParam2Wrapper->markModified()), + | let newParamWrappers = [$newParam1Wrapper, $newParam2Wrapper]; + ^PrevalWrapper( + value = ^$sfe(parametersValues = $newParamWrappers.value->cast(@ValueSpecification)), + canPreval = $newParamWrappers->canPreval(), + openVars = $newParamWrappers->openVars($state.inScopeVars), + modified = $newParamWrappers->size() != $sfe.parametersValues->size() || $newParamWrappers->anyModified() + ); + ); + ); +} + +function <> meta::pure::router::preeval::prevalGenericSimpleFunctionExpression(sfe : SimpleFunctionExpression[1], state : meta::pure::router::preeval::State[1], extensions:Extension[*]):PrevalWrapper[1] +{ + let newParamWrappers = if($sfe.parametersValues->isEmpty(), + | [], + | $sfe.func->match([p:Property[1]|'this', f:Function[1]|$f->functionType().parameters.name]) + ->zip($sfe.parametersValues) + ->evaluateAndDeactivate() + ->map(p| $state->printDebugWithDepth(|'Processing parameter: ' + $p.first); + let r = $p.second->prevalInternal($state, $extensions); + $state->printDebugWithDepth(|'Completed processing parameter: ' + $p.first); + $r;) + ); + let newSfeModified = $newParamWrappers->anyModified(); + + let newSfe = if($newSfeModified, + | ^$sfe(parametersValues = $newParamWrappers.value->cast(@ValueSpecification) + ->map(pv|^$pv(genericType = $pv.genericType->resolveGenericType($state).value)), + genericType = $sfe.genericType->resolveGenericType($state).value), + | $sfe); + + let canPrevalFunc = !($newSfe.func->hasStereotype('SideEffectFunction', functionType) || $newSfe.func->hasStereotype('NotImplementedFunction', functionType) || $state.stopPreeval->eval($newSfe)); + + if(!$canPrevalFunc, + | $state->printDebugWithDepth(|'Unable to perform preval: ' + $newSfe.func->elementToPath() + ' (' + $newSfe.func->instanceOf(NativeFunction)->makeString() + ')'); + + ^PrevalWrapper( + value = $newSfe, + canPreval = ($canPrevalFunc || ($newSfe.func == letFunction_String_1__T_m__T_m_)) && $newParamWrappers->canPreval(), + openVars = $newParamWrappers->openVars($state.inScopeVars), + modified = $newSfeModified + );, + | if(shouldInline($newSfe.func, $state), + | + $state->printDebugWithDepth(|'Inlining: ' + $newSfe.func->elementToPath()); + assert($newSfe.func->instanceOf(FunctionDefinition) && $newSfe.func->cast(@FunctionDefinition).expressionSequence->size() == 1); + + let newState = $newSfe->addToScope($state)->map(s|^$s(path = $state.path->concatenate($newSfe.func->cast(@FunctionDefinition)))); + + let subSfe1 = $newSfe.func->cast(@FunctionDefinition).expressionSequence->toOne()->evaluateAndDeactivate(); + let subSfe = ^$subSfe1(genericType = $subSfe1.genericType->resolveGenericType($newState).value); + + let sPreValWrapper = $subSfe->prevalInternal($newState, $extensions); + + $sPreValWrapper->markModified();, + | if($newSfe->canInlineEvalFunctionExpression(), + | + $state->printDebugWithDepth(|'Expanding eval: ' + $newSfe.func->elementToPath()); + + let func = $newSfe.parametersValues->at(0)->match([ + iv:InstanceValue[1]|$iv.values->toOne(), + vs:ValueSpecification[*]|$vs + ])->cast(@FunctionDefinition)->toOne(); + + let newState = $newSfe->addToScope($state)->addToScope($func, $sfe.resolvedTypeParameters, $sfe.parametersValues->drop(1)); + + let subSfe1 = $func.expressionSequence->toOne()->evaluateAndDeactivate(); + let subSfe = ^$subSfe1(genericType = $subSfe1.genericType->resolveGenericType($newState).value); + + let sPreValWrapper = $subSfe->prevalInternal($newState, $extensions); + $sPreValWrapper->markModified();, + | + let notPrevalReason = if($newSfe.parametersValues->exists(pv|!$pv->isInstanceValue($state.inScopeVars)), + | 'params are not instance values';, + | if (!$newParamWrappers->canPreval(), + | 'params can not preval';, + | if(!$newParamWrappers->areAllInScope($state.inScopeVars), + | 'open variables not in scope', + | if($newSfe.func == meta::pure::functions::lang::cast_Any_m__T_1__T_m_ && $newParamWrappers.value->at(0)->map(x|$x->instanceOf(InstanceValue) && $x->cast(@InstanceValue).values->isEmpty()), + | 'cast of empty collection', + | [] + ) + ); + ) + ); + if($notPrevalReason->isNotEmpty(), + | + let handlers = [] + ->cast(@PairBoolean[1]}>, Function<{SimpleFunctionExpression[1]->PrevalWrapper[1]}>>) + ->concatenate([ + pair( + {theSfe:SimpleFunctionExpression[1]| + $newSfe.func.name == 'columns' + && $newSfe.func->instanceOf(AbstractProperty) + && $newSfe.func->cast(@AbstractProperty).owner == TabularDataSet + && $newParamWrappers->areAllInScope($state.rollingInScopeVars) + }, + {theSfe:SimpleFunctionExpression[1]| + /* + Simplistically we could just re-activate the expression here as well, however there are some + TDS functions that are not implemented in the "Pure" implementation and only work in relational + queries. Therefore we have to specifically redirect to a helper function to resolve the schema + instead + */ + let val = meta::pure::tds::schema::resolveSchema($newSfe.parametersValues->toOne(), $state.rollingInScopeVars, $extensions); + + let iv = ^InstanceValue(genericType = $newSfe.genericType,multiplicity = $val->size()->toMultiplicity(), values = $val)->evaluateAndDeactivate(); + ^PrevalWrapper(value = $iv, canPreval = true, modified = true); + }), + pair( + {theSfe:SimpleFunctionExpression[1]|$newSfe.func->in([map_T_m__Function_1__V_m_, map_T_$0_1$__Function_1__V_$0_1$_, map_T_MANY__Function_1__V_MANY_]) && $newSfe.parametersValues->forAll(pv|$pv->isInstanceValue($state.inScopeVars))}, + {theSfe:SimpleFunctionExpression[1]| + // This special handling is required to cover cases where 'fixed'/ InstanceValue inputs are being mapped + // using a function that can't be pre-evalled (because it has external input), so wouldn't normally be modified. + // However we can actually just expand the lambda applied to each of the inputs, to elimniate the Map. + + $state->printDebugWithDepth(|'Expanding: ' + $newSfe.func->elementToPath()); + + let inputVals = $newSfe.parametersValues->at(0)->reactivate($state.inScopeVars) + ->map(v|$v->match([ + vs:ValueSpecification[1]|$vs, + a:Any[*]| ^InstanceValue( + genericType = $newSfe.parametersValues->at(0).genericType, + multiplicity = PureOne, + values = $v->toOne() + )->evaluateAndDeactivate() + ])); + + let lambda = $newSfe.parametersValues->at(1)->cast(@InstanceValue).values->toOne()->cast(@FunctionDefinition); + //TODO: This should really be pre-checked as part of the If condition to get here (so that the assertion never fails) + assert($lambda->instanceOf(FunctionDefinition) && $lambda->cast(@FunctionDefinition).expressionSequence->size() == 1); + + let paramName = $lambda->functionType().parameters->toOne().name; + + let resultValWrappers = $inputVals->map(v| + let newState = $state->addToScope($lambda, [], $v); + + let subSfe1 = $lambda.expressionSequence->toOne()->evaluateAndDeactivate(); + let subSfe = ^$subSfe1(genericType = $sfe.genericType->resolveGenericType($newState).value); + + $subSfe->prevalInternal($newState, $extensions); + ); + + let resultVals = $resultValWrappers.value->map(v|$v->match([iv:InstanceValue[0..1]|$iv->evaluateAndDeactivate().values, a:Any[*]|$a])); + + let r = ^InstanceValue( + genericType = $newSfe.genericType, + multiplicity = $resultVals->size()->toMultiplicity(), + values = $resultVals + )->evaluateAndDeactivate(); + + ^PrevalWrapper( + value = $r, + canPreval = $resultValWrappers->canPreval(), + openVars = $resultValWrappers->openVars($state.inScopeVars), + modified = true + ); + }), + pair( + {theSfe:SimpleFunctionExpression[1]|$newSfe.func == fold_T_MANY__Function_1__V_m__V_m_ && $newSfe.parametersValues->forAll(pv|$pv->isInstanceValue($state.inScopeVars))}, + {theSfe:SimpleFunctionExpression[1]| + // This special handling is required to cover cases where 'fixed'/ InstanceValue inputs are being folded + // using a function that can't be pre-evalled (because it has external input), so wouldn't normally be modified. + // However we can actually just repeat apply the lambda applied to each of the inputs, to elimniate the Fold. + $state->printDebugWithDepth(|'Expanding: ' + $newSfe.func->elementToPath()); + + let inputVals = $newSfe.parametersValues->at(0)->reactivate($state.inScopeVars) + ->map(v| $v->match([ + vs:ValueSpecification[1]|$vs, + a:Any[1]| + ^InstanceValue( + genericType = $newSfe.parametersValues->at(0).genericType, + multiplicity = PureOne, + values = $v->toOne() + ) + ])); + + let lambda = $newSfe.parametersValues->at(1)->cast(@InstanceValue).values->toOne()->cast(@FunctionDefinition);//->evaluateAndDeactivate(); + //TODO: This should really be pre-checked as part of the If condition to get here (so that the assertion never fails) + assert($lambda->instanceOf(FunctionDefinition) && $lambda->cast(@FunctionDefinition).expressionSequence->size() == 1); + + let intialAccumlatorVal = $newSfe.parametersValues->at(2); + + let resultVal = $inputVals->fold({v,a| + let newState = $state->addToScope($lambda, [], $v->concatenate($a.value->cast(@ValueSpecification))->evaluateAndDeactivate()); + + let subSfe1 = $lambda.expressionSequence->toOne(); + let subSfe = ^$subSfe1(genericType = $sfe.genericType->resolveGenericType($newState).value); + + $subSfe->prevalInternal($newState, $extensions); + }, $intialAccumlatorVal->prevalInternal($state, $extensions)); + + $resultVal->markModified(); + }), + pair( + {theSfe:SimpleFunctionExpression[1]| + ($newSfe.func == concatenate_T_MANY__T_MANY__T_MANY_) + && ($newSfe.parametersValues.multiplicity->forAll(m|$m->in([PureOne, PureZero]) || $m->isMultiplicityConcrete() && $m->hasUpperBound() && $m->hasLowerBound() && ($m.lowerBound.value == $m.upperBound.value))) + /* + This last condition should not be needed, but there are circumstances where the inferred type of some lambdas is incorrect. + So this is guard to avoid trying to pre-optimise in those situations (where in the example below, we'd optimised to the lambda + simply return $index because accumulator is detected as a PureZero) + + let input = {| + [1,2]->fold({index, accumulator| + $accumulator->concatenate($index); + }, []) + }; + + let foldLambda = $input->evaluateAndDeactivate().expressionSequence->cast(@SimpleFunctionExpression).parametersValues->at(1)->cast(@InstanceValue).values->cast(@FunctionDefinition)->toOne(); + let foldLambdaAccumulatorParam = $foldLambda->functionType().parameters->at(1); + + assertEquals(ZeroMany, $input->functionReturnMultiplicity(), 'Original lambda return multiplicty expected (zero many)'); + assertEquals($foldLambda->functionReturnMultiplicity(), $input->functionReturnMultiplicity(), | 'Fold lambda return multiplicity matches the original function multiplicity'); + //fails + assertEquals($foldLambda->functionReturnMultiplicity(), $foldLambdaAccumulatorParam.multiplicity, 'Accumulator parameter multiplicity matches the return multiplicty of the fold lambda: %r vs %r', [$foldLambda->functionReturnMultiplicity(), $foldLambdaAccumulatorParam.multiplicity]); + + assertEquals(Integer, $input->functionReturnType().rawType, | 'Original lambda return type expected (integer)'); + assertEquals($foldLambda->functionReturnType().rawType, $input->functionReturnType().rawType, | 'Fold lambda matchs original lambda return type'); + //fails + assertEquals($foldLambda->functionReturnType().rawType, $foldLambdaAccumulatorParam.genericType.rawType, 'Accumulator parameter matches lambda return type: %r vs %r', [$foldLambda->functionReturnType().rawType->toOne(), $foldLambdaAccumulatorParam.genericType.rawType->toOne()]); + + */ + && $newParamWrappers->filter(pw|$pw.value->match([v:VariableExpression[1]|$v.multiplicity == PureZero, a:Any[*]|false]))->areAllInScope($state.inScopeVars) + }, + {theSfe:SimpleFunctionExpression[1]| + $state->printDebugWithDepth(|'Handling expand: ' + $newSfe.func->elementToPath()); + $state->printDebugWithDepth(|'Parameter multiplicities: ' + $newSfe.parametersValues.multiplicity->map(x|$x->makeString())->joinStrings('[',',',']')); + + let r = if($newSfe.parametersValues->at(0).multiplicity == PureZero, + | $newSfe.parametersValues->at(1);, + | if($newSfe.parametersValues->at(1).multiplicity == PureZero, + | $newSfe.parametersValues->at(0);, + | let parametersValues = $newSfe.parametersValues + ->map(x|$x->match([iv:InstanceValue[*]| $iv.values;, a:Any[*]| $a])); + + ^InstanceValue( + genericType = $newSfe.genericType->resolveGenericType($state).value, + multiplicity = $parametersValues->size()->toMultiplicity(), + values = $parametersValues + )->evaluateAndDeactivate() + ->map(x|^$x(values = $parametersValues)); + ) + ); + + ^PrevalWrapper( + value = $r, + canPreval = $newParamWrappers->canPreval(), + openVars = $newParamWrappers->openVars($state.inScopeVars), + modified = true + ); + }), + pair( + {theSfe:SimpleFunctionExpression[1]|$newSfe.func == meta::pure::functions::lang::cast_Any_m__T_1__T_m_ && $newParamWrappers.value->at(0)->map(x|$x->instanceOf(InstanceValue) && $x->cast(@InstanceValue).values->isEmpty())}, + {theSfe:SimpleFunctionExpression[1]| + //This special handling should not be required but []->cast(@MyType) seems to fail in compiled mode + + $state->printDebugWithDepth(|'Handling cast of empty collection: ' + $newSfe.func->elementToPath()); + + + let r = ^InstanceValue( + genericType = $newSfe.genericType->resolveGenericType($state).value, + multiplicity = $newSfe.multiplicity, + values = [] + )->evaluateAndDeactivate(); + + ^PrevalWrapper( + value = $r, + canPreval = true, + openVars = [], + modified = true + ); + }), + pair( + {theSfe:SimpleFunctionExpression[1] | + //TODO getAll check is only needed as router does not route FunctionDefinition wth constant result. + isFilterFunctionReturningConstant($theSfe, false) && !isGetAll($theSfe.parametersValues->at(0))}, + {theSfe:SimpleFunctionExpression[1] | + $state->printDebugWithDepth(|'Handling filter which returns false: ' + $newSfe.func->elementToPath()); + + let r = ^InstanceValue( + genericType = ^GenericType(rawType = Nil), + multiplicity = PureZero + )->evaluateAndDeactivate(); + + ^PrevalWrapper( + value = $r, + canPreval = true, + openVars = [], + modified = true + ); + } + ), + pair( + {theSfe:SimpleFunctionExpression[1] | isFilterFunctionReturningConstant($theSfe, true)}, + {theSfe:SimpleFunctionExpression[1] | + $state->printDebugWithDepth(|'Handling filter which returns true: ' + $newSfe.func->elementToPath()); + + ^PrevalWrapper( + value = $theSfe.parametersValues->at(0), + canPreval = true, + openVars = [], + modified = true + ); + } + ), + pair( + {theSfe:SimpleFunctionExpression[1] | ($theSfe.func == toOneMany_T_MANY__T_$1_MANY$_) && + $theSfe.parametersValues->match([ + vs:ValueSpecification[1]|$vs.multiplicity->isMultiplicityConcrete() && $vs.multiplicity->hasLowerBound() + && ($vs.multiplicity->getLowerBound() > 0), + a:Any[*]|false + ])}, + {theSfe:SimpleFunctionExpression[1] | + $state->printDebugWithDepth(|'Handling toOneMany'); + + $newParamWrappers->toOne()->map(pw|^$pw(modified=true)); + } + ), + pair( + {theSfe:SimpleFunctionExpression[1] | ($theSfe.func == toOne_T_MANY__T_1_) && + $theSfe.parametersValues->match([ + vs:VariableExpression[1]|$vs.multiplicity->isMultiplicityConcrete() + && $vs.multiplicity == PureOne, + a:Any[*]|false + ])}, + {theSfe:SimpleFunctionExpression[1] | + $state->printDebugWithDepth(|'Handling toOne'); + + $newParamWrappers->toOne()->map(pw|^$pw(modified=true)); + } + ) + ]); + + let handler = $handlers->filter(p|$p.first->eval($newSfe))->first(); + + if($handler->isNotEmpty(), + | $handler->toOne().second->eval($newSfe), + | $state->printDebugWithDepth(|'Not prevalling: ' + $newSfe.func->elementToPath() + ' (' + $notPrevalReason->toOne() + ')'); + + ^PrevalWrapper( + value = $newSfe, + canPreval = $newParamWrappers->canPreval(), + openVars = $newParamWrappers->openVars($state.inScopeVars), + modified = $newSfeModified + ); + );, + | + $state->printDebugWithDepth(|'Performing preval: ' + $newSfe.func->elementToPath() + ' (can recativate dynamically: '+ $newSfe->canReactivateDynamically()->makeString() + ')'); + + let valX = $newSfe->reactivate($state.inScopeVars)->cast(@Any); + + let val = $valX->map(x|$x->match([ + {bcs : BasicColumnSpecification[1]| prevalInternal($bcs, ^$state(inScopeTypeParams = ^Map(), inScopeVars = ^Map>()), $extensions).value}, + {av : meta::pure::functions::collection::AggregateValue[1]| prevalInternal($av, ^$state(inScopeTypeParams = ^Map(), inScopeVars = ^Map>()), $extensions).value}, + {av : meta::pure::tds::AggregateValue[1]| prevalInternal($av, ^$state(inScopeTypeParams = ^Map(), inScopeVars = ^Map>()), $extensions).value}, + {a:Any[1]|$a} + ])); + + let iv = $val->match([ + x : InstanceValue[1]| $x, + a : Any[*]| ^InstanceValue(genericType = $newSfe.genericType,multiplicity = $val->size()->toMultiplicity(), values = $val); + ])->evaluateAndDeactivate(); + ^PrevalWrapper(value = $iv, canPreval = true, modified = true); + ); + ); + ); + ); +} + +function meta::pure::router::preeval::isGetAll(v:ValueSpecification[0..1]):Boolean[1] +{ + $v->match([ + s:SimpleFunctionExpression[1] | meta::pure::router::routing::isGetAllFunction($s.func) || $s.parametersValues->first()->isGetAll(), + a:Any[*] | false; + ]); +} + +function <> meta::pure::router::preeval::isFilterFunctionReturningConstant(sfe:SimpleFunctionExpression[1], value:Boolean[1]) : Boolean[1] +{ + let isFilter = $sfe.func == filter_T_MANY__Function_1__T_MANY_; + + if ($isFilter, + | + let es = $sfe.parametersValues->last()->cast(@InstanceValue).values->cast(@LambdaFunction).expressionSequence->evaluateAndDeactivate(); + $es->last()->toOne()->instanceOf(InstanceValue) && $es->last()->toOne()->cast(@InstanceValue).values == $value;, + | false); +} + +// ==================================================================================================================================================================== +// State Building Functions +// ==================================================================================================================================================================== + +function <> meta::pure::router::preeval::defaultFunctionInlineStrategy(extensions:Extension[*]) : FunctionDefinition<{Function[1]->Boolean[1]}>[1] +{ + {f:Function[1] | $f->in([col_Function_1__String_1__BasicColumnSpecification_1_, + col_Function_1__String_1__String_1__BasicColumnSpecification_1_, + // TODO: We should confirm presence of below 2 functions + meta::pure::functions::date::mostRecentDayOfWeek_DayOfWeek_1__Date_1_, + meta::pure::functions::date::previousDayOfWeek_DayOfWeek_1__Date_1_]) + || (!meta::pure::router::routing::shouldStop($f, $extensions) && !($f->instanceOf(NativeFunction)))} +} + +function <> meta::pure::router::preeval::defaultPreevalStopStrategy(extensions:Extension[*]) : FunctionDefinition<{Any[1]->Boolean[1]}>[1] +{ + {a:Any[1] | stopPreeval($a, $extensions)}; +} + +function meta::pure::router::preeval::getPreevalStateWithAdditionalStopInlineFunc(inScopeVars:Map>[1], extensions:meta::pure::extension::Extension[*], stopInlineFunctions:Function[*]):meta::pure::router::preeval::State[1] +{ + ^meta::pure::router::preeval::State(inScopeVars = $inScopeVars, + shouldInlineFxn = {f:Function[1] | meta::pure::router::preeval::defaultFunctionInlineStrategy($extensions)->eval($f) + && !$f->in($stopInlineFunctions)}, + stopPreeval = meta::pure::router::preeval::defaultPreevalStopStrategy($extensions), + rollingInScopeVars = $inScopeVars, + debug = noDebug(), + inScopeTypeParams = ^Map()); +} + +function <> meta::pure::router::preeval::stopPreeval(value : Any[*], extensions:Extension[*]) : Boolean[1] +{ + $extensions.routerExtensions().shouldStopPreeval->exists(p | $p->eval($value)) || + $value->match([ + p : String[*]| true, + p : Float[*]| true, + p : Decimal[*] | true, + p : Enum[*]| true, + p : StrictDate[*]| true, + p : LatestDate[*]| true, + p : Integer[*]| true, + p : Boolean[*]| true, + p : DateTime[*]| true, + p : SortInformation[*]| true, + p : Enumeration[*]| true, + p : Class[*]| true, + p : Pair[*]| true, + p : Property[*]| true, + p : Type[*]| true, + p : Path[*]|true, + p : meta::pure::mapping::Mapping[1]| true, + p : meta::pure::runtime::PackageableRuntime[1]| true, + p : meta::pure::runtime::Runtime[1]| true, + p : meta::pure::runtime::Connection[1] | true, + p : meta::pure::runtime::ExecutionContext[1]| true, + p : meta::pure::tds::TDSColumn[*]| true, + p : TdsOlapRank[*]| true, + p : TdsOlapAggregation[*]| true, + p : meta::pure::graphFetch::execution::AlloySerializationConfig[1]| true, + f : SimpleFunctionExpression[1]| $f.func->in([letFunction_String_1__T_m__T_m_]), + a : Any[*]| $a->type() + ->match([ + c:Class[1]|$c->isSimpleType(), + o:Any[1]|false + ]) + ]); +} + +function <> meta::pure::router::preeval::isSimpleType(c : Class[1]) : Boolean[1] +{ + $c->isSimpleType([]); +} + +function <> meta::pure::router::preeval::isSimpleType(c : Class[1], visitedClasses : Class[*]) : Boolean[1] +{ + let types = $c.properties->map(p|$p->functionReturnType().rawType) + ->distinct() + ->removeAll([String, Integer, Date, DateTime, StrictDate, LatestDate,Float]) + ->removeAll($visitedClasses) + ->filter(x|!$x->instanceOf(Enumeration)); + + let newClassTypes = $types->filter(x|$x->instanceOf(Class))->cast(@Class); + let unsupporedBasicTypes = $types->removeAll($newClassTypes); + + $unsupporedBasicTypes->isEmpty() && $newClassTypes->forAll(newC|$newC->isSimpleType($visitedClasses->concatenate($newClassTypes))); +} + + +// ==================================================================================================================================================================== +// Utility Functions +// ==================================================================================================================================================================== + +function <> meta::pure::router::preeval::resolveGenericType(in : GenericType[1], state : meta::pure::router::preeval::State[1]):PrevalWrapper[1] +{ + if($in.typeParameter->isEmpty(), + | + let newTypeArgsWrappers = $in.typeArguments->map(ta|$ta->resolveGenericType($state)); + if(!$newTypeArgsWrappers->anyModified(), + | ^PrevalWrapper(value = $in, canPreval=true, modified = false), + | let value = ^$in(typeArguments = $newTypeArgsWrappers.value); + ^PrevalWrapper(value = $value, canPreval=$newTypeArgsWrappers->canPreval(), modified = $newTypeArgsWrappers->anyModified()); + );, + | + let value = $state.inScopeTypeParams->get($in.typeParameter->toOne().name); + if($value->isEmpty(), + | ^PrevalWrapper(value = $in, canPreval=true, modified = false), + | ^PrevalWrapper(value = $value->toOne(), canPreval=true, modified = true) + ); + ); +} + +function <> meta::pure::router::preeval::shouldInline(f:Function[1], state : meta::pure::router::preeval::State[1]):Boolean[1] +{ + ($f->instanceOf(FunctionDefinition) && ($f->cast(@FunctionDefinition).expressionSequence->size() == 1 )) + && !($f->instanceOf(QualifiedProperty) && $f->cast(@QualifiedProperty).owner == TDSRow) + && !($f->instanceOf(AbstractProperty) && $f->cast(@AbstractProperty)->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype()) + && ($state.shouldInlineFxn->eval($f)) + && ($state.path->filter(x|$x == $f)->isEmpty()) +} + +function <> meta::pure::router::preeval::canInlineEvalFunctionExpression(fe:SimpleFunctionExpression[1]):Boolean[1] +{ + $fe.func->in([eval_Function_1__V_m_, + eval_Function_1__T_n__V_m_, + eval_Function_1__T_n__U_p__V_m_, + eval_Function_1__T_n__U_p__W_q__V_m_, + eval_Function_1__T_n__U_p__W_q__X_r__V_m_, + eval_Function_1__T_n__U_p__W_q__X_r__Y_s__V_m_, + eval_Function_1__T_n__U_p__W_q__X_r__Y_s__Z_t__V_m_, + eval_Function_1__S_n__T_o__U_p__W_q__X_r__Y_s__Z_t__V_m_]) + && $fe.parametersValues->at(0)->match([ + iv:InstanceValue[1]|$iv.values->toOne(), + vs:ValueSpecification[1]|$vs + ])->instanceOf(FunctionDefinition) + && $fe.parametersValues->at(0)->match([ + iv:InstanceValue[1]|$iv.values->toOne(), + vs:ValueSpecification[1]|$vs + ])->cast(@FunctionDefinition).expressionSequence->size() == 1 +} + +function <> meta::pure::router::preeval::toMultiplicity(size : Integer[1]) : Multiplicity[1] +{ + if($size == 0, + | PureZero, + | if($size == 1, + | PureOne, + | ^Multiplicity(lowerBound = ^MultiplicityValue(value=$size), upperBound = ^MultiplicityValue(value=$size)) + ); + ); +} + +function <> meta::pure::router::preeval::addToScope(sfe : SimpleFunctionExpression[1], state : meta::pure::router::preeval::State[1]):meta::pure::router::preeval::State[1] +{ + addToScope($state, $sfe.func, $sfe.resolvedTypeParameters, $sfe.parametersValues, true); +} + +function <> meta::pure::router::preeval::addToScope(state : meta::pure::router::preeval::State[1], func : Function[1],resolvedTypeParameters : GenericType[*], parametersValues : ValueSpecification[*]):meta::pure::router::preeval::State[1] +{ + addToScope($state, $func, $resolvedTypeParameters, $parametersValues, false) +} + +function <> meta::pure::router::preeval::addToScope(state : meta::pure::router::preeval::State[1], func : Function[1],resolvedTypeParameters : GenericType[*], parametersValues : ValueSpecification[*], cleanupInScopeVarsAndTypeParams:Boolean[1]):meta::pure::router::preeval::State[1] +{ + let typeParams = $func->functionType().typeParameters.name->zip($resolvedTypeParameters->map(x|$x->resolveGenericType($state).value)); + + let newParams = $parametersValues; + + let funcParamNames = $func->functionType().parameters.name; + let additionalInScopeVars = $funcParamNames + ->zip($newParams->map(pv|list($pv))) + ->map({p| let paramName = $p.first; + let paramValue = $p.second; + + $paramValue.values->match([ + ve:VariableExpression[1]| + if($ve.name == $paramName, + | [], //We drop easy "circular" / self-reference parameter variables + | $p + ), + vs:ValueSpecification[*]| + $p; + ]); + }); + + if($cleanupInScopeVarsAndTypeParams, |^$state(inScopeVars=newMap([]), inScopeTypeParams=newMap([])), |$state)->addToScope($additionalInScopeVars, $typeParams); +} + +function <> meta::pure::router::preeval::addToScope(state : meta::pure::router::preeval::State[1], additionalInScopeVars : Pair>[*], typeParams : Pair[*]):meta::pure::router::preeval::State[1] +{ + $state->printDebugWithDepth(|'Adding variables to scope: ' + $additionalInScopeVars.first->joinStrings('[', ',', ']')); + $state->printDebugWithDepth(|'Adding type params to scope: ' + $typeParams.first->joinStrings('[', ',', ']')); + + let newInScopeVars = $state.inScopeVars->putAll($additionalInScopeVars); + let newInScopeTypeParams = $state.inScopeTypeParams->putAll($typeParams); + + ^$state(inScopeVars = $newInScopeVars, inScopeTypeParams = $newInScopeTypeParams); +} + +function <> meta::pure::router::preeval::resolveVariable(name : String[1], state : meta::pure::router::preeval::State[1]):String[1] +{ + $state.inScopeVars->get($name).values->match([ + ve:VariableExpression[1]|assert($name != $ve.name, 'Circular variable reference: ' + $name); $ve.name->resolveVariable($state);, + a:Any[*]|$name + ]); +} + +function <> meta::pure::router::preeval::canPreval(items : PrevalWrapper[*]):Boolean[1] +{ + $items->forAll(p | $p.canPreval); +} + +function <> meta::pure::router::preeval::markModified(item : PrevalWrapper[1]):PrevalWrapper[1] +{ + if($item.modified, + | $item, + | ^$item(modified = true) + ); +} + +function <> meta::pure::router::preeval::anyModified(items : PrevalWrapper[*]):Boolean[1] +{ + $items.modified->contains(true); +} + +function <> meta::pure::router::preeval::openVars(items : PrevalWrapper[*], inScopeVars:Map>[1]):String[*] +{ + $items.openVars->openVars($inScopeVars); +} + +function <> meta::pure::router::preeval::openVars(vars : String[*], inScopeVars:Map>[1]):String[*] +{ + $vars->distinct()->filter(v|!$v->areAllInScope($inScopeVars)) +} + +function <> meta::pure::router::preeval::areAllInScope(items : PrevalWrapper[*], inScopeVars:Map>[1]):Boolean[1] +{ + $items.openVars->distinct()->areAllInScope($inScopeVars); +} + +function <> meta::pure::router::preeval::areAllInScope(vars : String[*], inScopeVars:Map>[1]):Boolean[1] +{ + if($vars->isEmpty(), + | true, + | let unresolvedVars = $vars->map(v|let varVal = $inScopeVars->get($v); + + if($varVal->isEmpty(), + | $v, + | $varVal.values->match([ + varExp : VariableExpression[1]|$varExp.name, + a : Any[*]|[] + ]) + ); + )->distinct(); + + let newVars = $unresolvedVars->removeAll($vars); + !$unresolvedVars->containsAny($vars) && $newVars->areAllInScope($inScopeVars); + ); +} + +function <> meta::pure::router::preeval::isInstanceValue(v: Any[1], inScopeVars:Map>[1]):Boolean[1] +{ + $v->match([ + iv : InstanceValue[1]| + $iv.values->forAll(subV|$subV->match([ + subVS : ValueSpecification[1]|$subVS->isInstanceValue($inScopeVars), + a : Any[1]| true; + ]));, + ve : VariableExpression[1]|$inScopeVars->get($ve.name)->isNotEmpty(), + sfe : SimpleFunctionExpression[1]|false; + ]) +} + +function <> meta::pure::router::preeval::printDebugWithDepth(state: meta::pure::router::preeval::State[1], func:Function<{->String[1]}>[1]):Nil[0] +{ + printDebug($state, | ('[' + toString($state.depth->orElse(''))->map(s|'0000'->substring(0, max(0, 3-$s->length())) + $s) + ']' + range($state.depth->orElse(0) + 1)->map(d|'')->joinStrings('', '-', ' ') + $func->eval())); +} + +function <> meta::pure::router::preeval::printDebug(state: meta::pure::router::preeval::State[1], func:Function<{->Any[*]}>[1]):Nil[0] +{ + printDebug($state.debug, $func); +} + +function <> meta::pure::router::preeval::printDebug(debugContext : DebugContext[1], func:Function<{->Any[*]}>[1]):Nil[0] +{ + if(!$debugContext.debug, + | print(''), + | printDebug($debugContext, $func->eval()); + ); +} + +function <> meta::pure::router::preeval::printDebug(state: meta::pure::router::preeval::State[1], param:Any[*]):Nil[0] +{ + printDebug($state.debug, $param); +} + +function <> meta::pure::router::preeval::printDebug(debugContext : DebugContext[1], param:Any[*]):Nil[0] +{ + if(!$debugContext.debug, + | print(''), + | println($param); + ); } \ No newline at end of file diff --git a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/preeval/tests.pure b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/preeval/tests.pure index 22b16d67d19..39515a0b3e7 100644 --- a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/preeval/tests.pure +++ b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/preeval/tests.pure @@ -1,1597 +1,1603 @@ -// Copyright 2023 Goldman Sachs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Press F9 to execute the 'go' function... -// Press F10 to run the full test suite - -import meta::external::format::shared::binding::*; -import meta::external::format::shared::functions::*; -import meta::json::*; -import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::*; -import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::toPureGrammar::*; -import meta::pure::extension::*; -import meta::pure::graphFetch::execution::*; -import meta::pure::mapping::*; -import meta::pure::mapping::modelToModel::test::simple::*; -import meta::pure::mapping::modelToModel::test::shared::dest::*; -import meta::pure::mapping::modelToModel::test::shared::src::*; -import meta::pure::router::preeval::*; -import meta::pure::router::preeval::tests::*; -import meta::pure::runtime::*; - -Class meta::pure::router::preeval::tests::Person -{ - firstName : String[1]; - lastName : String[1]; - otherNames : String[*]; - - name(){$this.firstName+' '+$this.lastName}:String[1]; - nameWithTitle(title:String[1]){$title+' '+$this.firstName+' '+$this.lastName}:String[1]; - nameWithPrefixAndSuffix(prefix:String[0..1], suffixes:String[*]) - { - if($prefix->isEmpty(), - | if($suffixes->isEmpty(), - | $this.firstName + ' ' + $this.lastName, - | $this.firstName + ' ' + $this.lastName + ', ' + $suffixes->joinStrings(', ')), - | if($suffixes->isEmpty(), - | $prefix->toOne() + ' ' + $this.firstName + ' ' + $this.lastName, - | $prefix->toOne() + ' ' + $this.firstName + ' ' + $this.lastName + ', ' + $suffixes->joinStrings(', '))) - }:String[1]; - - fullName(lastNameFirst:Boolean[1]) - { - if($lastNameFirst, | $this.lastName + ', ' + $this.firstName, | $this.firstName + ' ' + $this.lastName) - }:String[1]; - extraInformation : String[0..1]; - manager : meta::pure::router::preeval::tests::Person[0..1]; - age : Integer[0..1]; - constant() { 'constant' } : String[1]; - nickName : String[0..1]; - activeEmployment: Boolean[0..1]; -} - -Class meta::pure::router::preeval::tests::FinancialInstrument -{ - id : Integer[1]; - price : Decimal[1]; -} - -Class <> meta::pure::router::preeval::tests::BaseAccount -{ - id: Integer[1]; - accountId(){ - $this.id - }:Integer[1]; -} - -Class <> meta::pure::router::preeval::tests::Position -{ - account:BaseAccount[1]; - id: Integer[1]; - quantity: Float[1]; -} - -Class <> meta::pure::router::preeval::tests::BaseOrganizationalEntity -{ - name: String[1]; - id: Integer[1]; -} - -Class <> meta::pure::router::preeval::tests::OrganizationalEntity extends BaseOrganizationalEntity -{ - isActive: Boolean[1]; - parent: OrganizationalEntity[1]; - parentAtDate(date:Date[1]) - { - $this.parent($date); - }:OrganizationalEntity[1]; - ceo: meta::pure::router::preeval::tests::Person[1]; - - latestPositions() - { - $this.positions(now()); - }:Position[*]; -} - -Association meta::pure::router::preeval::tests::PositionClient -{ - positions:Position[*]; - client:OrganizationalEntity[1]; -} - -Class <> meta::pure::router::preeval::tests::ClassA -{ - myPropertyB : meta::pure::router::preeval::tests::ClassB[*]; -} - -Class <> meta::pure::router::preeval::tests::ClassB -{ - myLeafProperty : String[1]; -} - - -function <> meta::pure::router::preeval::tests::testPrerouting1():Boolean[1] -{ - let input = {| let x = 'hello' + ' world'; $x + (' ' + (5->toString()));}; - - let expected = {| 'hello world 5';}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting2():Boolean[1] -{ - let input = {| let x = today()->adjust(1, DurationUnit.DAYS); let y = %2017-01-01->adjust(1, DurationUnit.DAYS); }; - - let expected = {| let x = today()->adjust(1, DurationUnit.DAYS); %2017-01-02; }; - - assertRoundTrip($input, $expected); -} - - -function <> meta::pure::router::preeval::tests::testPrerouting2b():Boolean[1] -{ - let input = {| - let x = firstDayOfThisYear()->adjust(1, DurationUnit.DAYS); - let y = firstDayOfThisMonth(); - let z = firstDayOfThisQuarter()->adjust(2, DurationUnit.DAYS); - - let a = mostRecentDayOfWeek(DayOfWeek.Monday); - let b= previousDayOfWeek(DayOfWeek.Monday); - - true; - }; - - let expected = {| - let x = today()->firstDayOfYear()->adjust(1, DurationUnit.DAYS); - let y = today()->firstDayOfMonth(); - let z = today()->firstDayOfQuarter()->adjust(2, DurationUnit.DAYS); - - let a = today()->mostRecentDayOfWeek(DayOfWeek.Monday); - let b = today()->previousDayOfWeek(DayOfWeek.Monday); - - true; - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting3():Boolean[1] -{ - let input = {| let y = 1; $y + (6 + 10);}; - - let expected = {| 17;}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting4():Boolean[1] -{ - let value = 'hello world'; - - let input = {| let y = $value->substring(0, 5); }; - - let expected = {| 'hello'}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting5():Boolean[1] -{ - let input = {| currentUserId() + '_user' ;}; - - let expected = $input; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting6():Boolean[1] -{ - let value = 10; - - let input = {| range(5)->map(x|$x+$value); }; - - let expected = {| [10, 11, 12,13,14]}; - - assertRoundTrip($input, $expected); -} - - -function <> meta::pure::router::preeval::tests::testPrerouting7():Boolean[1] -{ - let input = {| %2018-01-02->toString()->myExpandableFunc(5) ;}; - - let expected = {| '2018-01-02|2018-01-02|2018-01-02|2018-01-02|2018-01-02'}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting8():Boolean[1] -{ - let input = {| today()->toString()->myNonExpandableMultiExpressionFunc(5) ;}; - - let expected = $input; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting9():Boolean[1] -{ - let input = {| {x:String[1]| $x + 'hello'} ;}; - - let expected = $input; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting10():Boolean[1] -{ - let input = {| range(3)->map(x|$x->toString());}; - - let expected = {| ['0','1','2']}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting11():Boolean[1] -{ - let input = {| range(3)->map(x| $x->map(index|$index->toString()));}; - - let expected = {| ['0','1','2']}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting12():Boolean[1] -{ - let input = {| range(3)->map(x| $x->toString()->map(index|$index));}; - - let expected = {| ['0','1','2']}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting13():Boolean[1] -{ - let input = {| range(4)->map(x| [1,2,3,4,5]->take($x)->map(index|$index));}; - - let expected = {| [1,1,2,1,2,3]}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting14():Boolean[1] -{ - let input = {| range(3)->map(x| [1,2,3,4,5]->take($x + 1)->map(index|$index));}; - - let expected = {| [1,1,2,1,2,3]}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting15():Boolean[1] -{ - // ->cast(@Integer) added to help with compile mode java generation, this should not be needed but java compiler fails on java generated code - let input = {| range(3)->map(x| range(5)->map(k|$k+1)->cast(@Integer)->take($x + 1)->map(index|$index->cast(@Integer)));}; - - let expected = {| [1,1,2,1,2,3]}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting16():Boolean[1] -{ - let input = {| range(3)->map(x|$x+1)->map(index|'i' + $index->toString()) ;}; - - let expected = {|['i1', 'i2', 'i3'] }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting17():Boolean[1] -{ - let c = 3; - - let input = {| range($c)->map(x|$x+1)->map(index|'i' + $index->toString()) ;}; - - let expected = {|['i1', 'i2', 'i3'] }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting18():Boolean[1] -{ - let c = 3; - - let input = {| range($c)->map(x|$x+1)->map(index|('i' + $c->makeString()) + $index->toString()) ;}; - - let expected = {|['i31', 'i32', 'i33'] }; - - assertRoundTrip($input, $expected); -} - - -function <> meta::pure::router::preeval::tests::testPrerouting19():Boolean[1] -{ - let input = {|['a']->fold({p,x|$x->concatenate(range(2)->map(index|$p)->joinStrings('|'))}, [])}; - - let expected = {|['a|a']}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting20():Boolean[1] -{ - let input = {|['hello']->map(a|$a->f1())}; - - let expected = {|'hello'}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::f1(zzz : String[1]):String[1] -{ - $zzz->map(k|$zzz->substring(0,5)); -} - -function <> {test.excludePlatform = 'Java compiled'} meta::pure::router::preeval::tests::testPrerouting20a():Boolean[1] -{ - let input = {|'hello'->map(x|$x->myFirst1())}; - - let expected = {|'hello'}; - - assertRoundTrip($input, $expected); -} - -function meta::pure::router::preeval::tests::myFirst1(theFirstSet:T[*]):T[0..1] -{ - myFirst2($theFirstSet); -} - -function meta::pure::router::preeval::tests::myFirst2(theSecondSet:T[*]):T[0..1] -{ - $theSecondSet->first(); -} - -function <> meta::pure::router::preeval::tests::testPrerouting21():Boolean[1] -{ - let input = {|['a']->fold({p,x|$x->concatenate(myExpandableFunc2($p, 3))}, [])}; - - let expected = {|'a|a|a' }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::myExpandableFunc2(val : String[1], count : Integer[1]):String[*] -{ - range(0, $count, 1)->map(index|$val)->joinStrings('|'); -// $val->myFakeJoin('|'); -} - - -function <> meta::pure::router::preeval::tests::testPrerouting_mapOnInstanceValuesExpanded():Boolean[1] -{ - let c = 3; - - let input = {| range(1, $c+1, 1)->map(index|$index->toString() + currentUserId()->toString()) ;}; - - let expected = {|[ - ('1' + meta::pure::runtime::currentUserId()->toString()), - ('2' + meta::pure::runtime::currentUserId()->toString()), - ('3' + meta::pure::runtime::currentUserId()->toString()) - ] }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_foldOnInstanceValuesExpanded():Boolean[1] -{ - let c = 2; - - let input = {| - range(1, $c+1, 1)->fold({index, r| - $r + ($index->toString() + currentUserId() + ';'); - }, 'Values: ') - }; - - let expected = {| - 'Values: ' + '1' + meta::pure::runtime::currentUserId() + ';' + '2' + meta::pure::runtime::currentUserId() + ';' - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_foldOnInstanceValuesExpanded2():Boolean[1] -{ - let c = 2; - - let input = {| - range(1, $c+1, 1)->fold({index, r| - $r->concatenate(($index->toString() + currentUserId())); - }, []) - }; - - let expected = {| - [('1' + currentUserId()), ('2' + currentUserId())] - ///[('1' + meta::pure::runtime::currentUserId())] ->concatenate('2' + meta::pure::runtime::currentUserId()) - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_concatenateInstanceValuesExpanded_Basic():Boolean[1] -{ - let c = 2; - - let input = {| - ['hello']->concatenate(currentUserId()); - }; - - let expected = {| - ['hello', currentUserId()]; - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_concatenateInstanceValuesExpanded_Complex():Boolean[1] -{ - let x = 2; - - let input = {| - let a = []->concatenate(currentUserId()); - let b = []->concatenate(['hello', currentUserId()]); - let c = []->concatenate(['hello', currentUserId(), (currentUserId() + $x->toString())]); - - let d = currentUserId()->concatenate([]); - let e = ['hello', currentUserId()]->concatenate([]); - let f = ['hello', currentUserId(), (currentUserId() + $x->toString())]->concatenate([]); - - let g = currentUserId()->concatenate(currentUserId()); - let h = ['hello', currentUserId()]->concatenate(['hello', currentUserId()]); - let i = ['hello', currentUserId(), (currentUserId() + $x->toString())]->concatenate(['hello', currentUserId(), (currentUserId() + $x->toString())]); - - let j = myCollectionFunc($x->toString())->concatenate(currentUserId()); - - let k = {a:Integer[1], b:Integer[1]|$a->concatenate($b)}; - let l = {a:Integer[*], b:Integer[1]|$a->concatenate($b)}; - let m = {a:Integer[1], b:Integer[*]|$a->concatenate($b)}; - let n = {a:Integer[*], b:Integer[*]|$a->concatenate($b)}; - - true; - }; - - let expected = {| - let a = currentUserId(); - let b = ['hello', currentUserId()]; - let c = ['hello',meta::pure::runtime::currentUserId(),(meta::pure::runtime::currentUserId() + '2')]; - - let d = currentUserId(); - let e = ['hello', currentUserId()]; - let f = ['hello',meta::pure::runtime::currentUserId(),(meta::pure::runtime::currentUserId() + '2')]; - - let g = [meta::pure::runtime::currentUserId(),meta::pure::runtime::currentUserId()]; - let h = ['hello',meta::pure::runtime::currentUserId(),'hello',meta::pure::runtime::currentUserId()]; - let i = ['hello',meta::pure::runtime::currentUserId(),(meta::pure::runtime::currentUserId() + '2'),'hello',meta::pure::runtime::currentUserId(),(meta::pure::runtime::currentUserId() + '2')]; - - let j= ['2', currentUserId()]; - - let k = {a:Integer[1], b:Integer[1]|[$a,$b]}; - let l = {a:Integer[*], b:Integer[1]|$a->concatenate($b)}; - let m = {a:Integer[1], b:Integer[*]|$a->concatenate($b)}; - let n = {a:Integer[*], b:Integer[*]|$a->concatenate($b)}; - - true; - }; - - assertRoundTrip($input, $expected); -} - -function meta::pure::router::preeval::tests::myCollectionFunc(values:T[m]):T[m] -{ - $values; -} - -function <> meta::pure::router::preeval::tests::testPrerouting23a():Boolean[1] -{ - let input = {| if(false, | []->toOne(), | 'hello ' + 'world') ;}; - - let expected = {| 'hello world' }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting23b():Boolean[1] -{ - let input = {| if(true, | 'hello ' + 'world', | []->toOne() ) ;}; - - let expected = {| 'hello world' }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting24a():Boolean[1] -{ - let x = []; - - let input = {| - $x->isEmpty() || ($x->at(0) == 'hello'); - }; - - let expected = {| true }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting24b():Boolean[1] -{ - let x = []; - - let input = {| - $x->isNotEmpty() && ($x->at(0) == 'hello'); - }; - - let expected = {| false }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting24c():Boolean[1] -{ - let x = []; - - let input = {| - (today() > %2018-01-01) && (1 == 1); - }; - - let expected = {| today() > %2018-01-01 }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting24d():Boolean[1] -{ - let x = []; - - let input = {| - (today() > %2018-01-01) || (1 == 1); - }; - - let expected = {| true }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting24e():Boolean[1] -{ - let x = []; - - let input = {| - (1 == 1) && (today() > %2018-01-01); - }; - - let expected = {| today() > %2018-01-01 }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting24f():Boolean[1] -{ - let x = []; - - let input = {| - (1 == 1) || (today() > %2018-01-01); - }; - - let expected = {| true }; - - assertRoundTrip($input, $expected); -} - - -function <> meta::pure::router::preeval::tests::testPrerouting25a():Boolean[1] -{ - let input = {|meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName'), - col({p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}, 'lastName'), - col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age'), - col({p:meta::pure::router::preeval::tests::Person[1]|today()}, 'today') - ]) - ->extend(^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}))} ; - - let expected = {| meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}), - ^BasicColumnSpecification(name = 'lastName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}), - ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age}), - ^BasicColumnSpecification(name = 'today', func = {p:meta::pure::router::preeval::tests::Person[1]|today()}) - ]) - ->extend(^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age') ->toString()}))}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting25b():Boolean[1] -{ - let input = {|meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName + ('hello' + '_world')}, 'firstName'), - col({p:meta::pure::router::preeval::tests::Person[1]|'world' + ' blue'}, 'const'), - col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age') - ]) - ->extend(['e']->map(x|col({row:TDSRow[1]|$row.getInteger('ag' + $x) ->toString()}, 'ageString')))}; - - let expected = {| meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName + 'hello_world'}), - ^BasicColumnSpecification(name = 'const', func = {p:meta::pure::router::preeval::tests::Person[1]|'world blue'}), - ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age})]) - ->extend(^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}))}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting25c():Boolean[1] -{ - let blue = 'blue'; - - let cols = [ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName + ('hello' + '_world')}, 'firstName'), - col({p:meta::pure::router::preeval::tests::Person[1]|'world' + ' ' + $blue}, 'const'), - col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age') - ]; - - let input = {|meta::pure::router::preeval::tests::Person.all() - ->project($cols) - ->extend(['e']->map(x|col({row:TDSRow[1]|$row.getInteger('ag' + $x) ->toString()}, 'ageString'))) - }; - - let expected = {| meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName + 'hello_world'}), - ^BasicColumnSpecification(name = 'const', func = {p:meta::pure::router::preeval::tests::Person[1]|'world blue'}), - ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age})]) - ->extend(^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}))}; - - assertRoundTrip($input, $expected); -} - - - -function <> meta::pure::router::preeval::tests::testPrerouting26():Boolean[1] -{ - let input = {| - pair('hello', today()); - }; - - let expected = {| ^Pair(first='hello', second = today()) }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting27a():Boolean[1] -{ - let input = {| - [1,2,3]->map(v| ^Pair(first = 'hello', second = $v)); - }; - - let expected = {| [^Pair(first = 'hello', second = 1),^Pair(first = 'hello', second = 2),^Pair(first = 'hello', second = 3)] }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting27b():Boolean[1] -{ - let input = {| - [1,2,3]->map(v| pair('hello', $v)); - }; - - let expected = {| [^Pair(first = 'hello', second = 1),^Pair(first = 'hello', second = 2),^Pair(first = 'hello', second = 3)] }; - - assertRoundTrip($input, $expected); -} - - -// function <> meta::pure::router::preeval::tests::testPrerouting27c():Boolean[1] -// { -// let input = {| -// [today(),today(),today()]->map(v| pair('hello', $v)); -// }; - -// let expected = {| [today(),today(),today()]->map(v|^Pair(first = 'hello', second = $v)) }; - -// assertRoundTrip($input, $expected); -// } - -function <> meta::pure::router::preeval::tests::testPrerouting28():Boolean[1] -{ - let input = {| - [1,2,3]->map(v| $v->map(x|pair('hello' + '*' + $x->makeString(), $v))); - }; - - let expected = {| [^Pair(first = 'hello*1', second = 1),^Pair(first = 'hello*2', second = 2),^Pair(first = 'hello*3', second = 3)] }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting29a():Boolean[1] -{ - let input = {r:String[1]| ['a', 'b', 'c']->map(v| $r + ('_' + $v))}; - - let expected = {r:String[1]|[ ($r + '_a'),($r + '_b'),($r + '_c')]}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::myPair(firstVal : String[1], secondVal : StrictDate[1]):Pair[1] -{ - ^Pair(first = $firstVal, second=$secondVal); -} - -function <> meta::pure::router::preeval::tests::testPrerouting29b():Boolean[1] -{ - let input = {| range(3)->map(index|today()) ;}; - - let expected = {|[today(),today(),today()] }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting29c():Boolean[1] -{ - let input = {| myNonFullyExpandableDateFunc('a', 5) ;}; - - let expected = {|[ - ('a' + (today()->toString())), - ('a' + (today()->toString())), - ('a' + (today()->toString())), - ('a' + (today()->toString())), - ('a' + (today()->toString())) - ]->joinStrings('|'); - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::myExpandableFunc(val : String[1], count : Integer[1]):String[1] -{ - range($count)->map(index|$val)->joinStrings('|'); -} - -function <> meta::pure::router::preeval::tests::testPrerouting30():Boolean[1] -{ - let input = {| - range(3)->map(index|[$index, today()]); - }; - - let expected = {| - [0,today(), 1,today(), 2,today()] - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting31a():Boolean[1] -{ - let input = {| - range(2)->map(a|^BasicColumnSpecification(name = 'age_' + $a->makeString(), func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + ($a * 10)})) - }; - - let expected = {|[^BasicColumnSpecification(name = 'age_0', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 0}),^BasicColumnSpecification(name = 'age_1', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 10})] }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting31b():Boolean[1] -{ - let input = {| meta::pure::router::preeval::tests::Person.all() - ->project( - range(2)->map(a| - [col({p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + ($a * 10)}, 'age_' + makeString($a*10)),col({p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + (($a*10) + 1)}, 'age_' + makeString(($a*10) + 1))] - ) - ) - }; - - let expected = {|meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'age_0', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 0}), - ^BasicColumnSpecification(name = 'age_1', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 1}), - ^BasicColumnSpecification(name = 'age_10', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 10}), - ^BasicColumnSpecification(name = 'age_11', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 11}) - ]) - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting32a():Boolean[1] -{ - let input = {| ['e']->map(x|col({row:TDSRow[1]|$row.getInteger('ag' + $x)->toString()}, 'ageString'));}; - - let expected = {|^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}) }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting32b():Boolean[1] -{ - let input = {| let x = 'e'; col({row:TDSRow[1]|$row.getInteger('ag' + $x)->toString()}, 'ageString');}; - - let expected = {|^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}); }; - - assertRoundTrip($input, $expected); -} - - - -function <> meta::pure::router::preeval::tests::myFakeJoin(vals : String[*], char : String[1]):String[1] -{ - let x = 'hello'; - $vals->drop(1)->fold({x,n| $x + $char + $n}, $vals->at(0)); - //$vals->at(0); -} - -function <> meta::pure::router::preeval::tests::myComplexExpandableFunc(vals : String[*]):String[1] -{ - $vals->map(x|$x->myExpandableFunc(1))->joinStrings('hello'); -} - - - -function meta::pure::router::preeval::tests::myNonExpandableMultiExpressionFunc(val : String[1], count : Integer[1]):String[1] -{ - let x = range($count); - $x->map(index|$val)->joinStrings('|'); -} - -function <> meta::pure::router::preeval::tests::myNonFullyExpandableDateFunc(val : String[1], count : Integer[1]):String[1] -{ - range($count)->map(index|$val + today()->toString())->joinStrings('|'); -} - - - -function <> meta::pure::router::preeval::tests::testPrerouting33():Boolean[1] -{ - let input = {| - meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName'), - col({p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}, 'lastName'), - col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age'), - col({p:meta::pure::router::preeval::tests::Person[1]|today()}, 'today') - ]) - ->filterToStringColumns() - }; - - let expected = {| - meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}), - ^BasicColumnSpecification(name = 'lastName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}), - ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age}), - ^BasicColumnSpecification(name = 'today', func = {p:meta::pure::router::preeval::tests::Person[1]|today()}) - ]) - ->restrict(['firstName','lastName']) - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::filterToStringColumns(input : TabularDataSet[1]):TabularDataSet[1] -{ - $input->restrict($input.columns->filter(c|$c.type == String).name); -} - -function <> meta::pure::router::preeval::tests::extendColumns(input : TabularDataSet[1], cols : String[*]):TabularDataSet[1] -{ - $input->extend($input.columns->filter(c|$c.name->in($cols))->sortBy(c|$cols->indexOf($c.name))->map(c| - if($c.type == String, - | col({row:TDSRow[1]|$row.getString($c.name)}, $c.name + '_ext'), - | if($c.type == Integer, - | col({row:TDSRow[1]|$row.getInteger($c.name)}, $c.name + '_ext'), - | col({row:TDSRow[1]|$row.getDate($c.name)}, $c.name + '_ext') - ) - ) - )); -} - -function <> meta::pure::router::preeval::tests::testPrerouting34():Boolean[1] -{ - let input = {| - let businessDate = now(); - let processingDate = today()->adjust(-1, DurationUnit.DAYS); - - meta::pure::router::preeval::tests::Position.all($processingDate) - ->project([ - x | $x.quantity, - x | $x.client($businessDate).name], - ['Quantity', - 'Client/Name'] - ) - ->extendColumns('Quantity'); - }; - - let expected = {| - let businessDate = now(); - let processingDate = today()->adjust(-1, DurationUnit.DAYS); - - meta::pure::router::preeval::tests::Position - ->getAll($processingDate) - ->project([{x|$x.quantity},{x|$x.client($businessDate).name}], ['Quantity','Client/Name']) - ->extend(^BasicColumnSpecification(name = 'Quantity_ext', func = {row:TDSRow[1]|$row.getDate('Quantity')})); - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting35():Boolean[1] -{ - let input = {| - let x = 'world'; - - {|('hello' + ' ') + $x}->eval(); - }; - - let expected = {| - 'hello world'; - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_parameterAssignedToVariable():Boolean[1] -{ - let input = {item:Pair[1]| - let other = $item; - $other.first->isNotEmpty(); - }; - - let expected = {item:Pair[1]| - $item.first->isNotEmpty(); - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting36():Boolean[1] -{ - let input = {a:meta::pure::router::preeval::tests::ClassA[1]| - $a.myPropertyB.myLeafProperty->map(x|now())->map(d|$d->adjust(-1, DurationUnit.DAYS)) - }; - - assertRoundTrip($input, $input); -} - -function <> meta::pure::router::preeval::tests::testPrerouting37():Boolean[1] -{ - let input = {x:Integer[*]| - $x->otherFunc2(y|$y+1) - }; - - let expected = {x:Integer[*]| - $x->map({y|$y + 1}) - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::otherFunc2(x : Integer[*], l : FunctionDefinition<{Integer[1]->Integer[1]}>[1]):Integer[*] -{ - $x->map($l); -} - -function <> meta::pure::router::preeval::tests::testPrerouting38():Boolean[1] -{ - let input = {| - let x = myPair('hello', 'world'); - let y = myPair(1, $x.first + ' ' + $x.second); - }; - - let expected = {| - ^Pair(first = 1, second = 'hello world'); - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::myPair(v1 : X[1], v2 : Y[1]) : Pair[1] -{ - pair($v1, $v2); -} - -function <> meta::pure::router::preeval::tests::myPair2(v1 : U[1], v2 : V[1]) : Pair[1] -{ - pair($v1, $v2); -} - -function <> meta::pure::router::preeval::tests::testPrerouting39():Boolean[1] -{ - let input = {| - let x = 123; - $x->cast(@Integer); - }; - - let expected = {| - 123 - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting40a():Boolean[1] -{ - let input = {| - let x = 123; - $x->toOne()->myToOne(); - }; - - let expected = {| - 123; - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting40b():Boolean[1] -{ - let input = {| - let x = today(); - $x->toOne()->myToOne(); - }; - - let expected = {| - let x = today(); - $x; - }; - - assertRoundTrip($input, $expected); -} - - -function <> meta::pure::router::preeval::tests::myToOne(x : T[1]) : T[1] -{ - $x->toOne(); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_PropertyValue():Boolean[1] -{ - let prop = meta::pure::router::preeval::tests::Person.properties->at(0); - let name = $prop.name; - - let input = {| $prop.name;}; - - let expected = {| $name}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_OptionalLimit1():Boolean[1] -{ - let limit = 5; - - let input = {| - meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName') - ]) - ->meta::pure::router::preeval::tests::optionalLimit($limit) - }; - - let expected = {| - meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}) - ]) - ->limit(5); - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_OptionalLimit2():Boolean[1] -{ - let limit = []; - - let input = {| - meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName') - ]) - ->meta::pure::router::preeval::tests::optionalLimit($limit) - }; - - let expected = {| - meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}) - ]); - }; - - assertRoundTrip($input, $expected); -} - -function meta::pure::router::preeval::tests::optionalLimit(t: TabularDataSet[1], limit:Integer[0..1]) : TabularDataSet[1] -{ - if($limit->isEmpty() || ($limit->toOne()<=0), | $t, | $t->limit($limit)); -} - - -function <> meta::pure::router::preeval::tests::testPrerouting_mappedTdsAgg():Boolean[1] -{ - let v = [pair('name', '6')]; - - let input = {|meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age') - ]) - ->groupBy([], $v->map(pv|agg('sumAgg_' + $pv.first->toString(), {row:TDSRow[1]|if($row.getInteger('age')->toString() == $pv.second, | 1, | 0)}, y|$y->sum()))) - }; - - let expected = {| meta::pure::router::preeval::tests::Person.all() - ->project(^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age})) - ->groupBy([], agg('sumAgg_name', {row:TDSRow[1]|if($row.getInteger('age')->toString() == '6', |1, |0)}, {y|$y->sum()})) - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_mappedModelAgg():Boolean[1] -{ - let v = [pair('name', '6')]; - - let input = {|meta::pure::router::preeval::tests::Person.all() - ->groupBy([], - $v->map(pv|agg({p:meta::pure::router::preeval::tests::Person[1]|if($p.age->toOne()->toString() == $pv.second, | 1, | 0)}, y|$y->sum())), - $v->map(pv|'sumAgg_' + $pv.first) - ) - }; - - let expected = {|meta::pure::router::preeval::tests::Person.all() - ->groupBy([], agg({p:meta::pure::router::preeval::tests::Person[1]|if($p.age->toOne()->toString() == '6', |1, |0)}, {y|$y->sum()}), 'sumAgg_name') - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_constantInAgg():Boolean[1] -{ - let lambda = {| - let businessDate = %2023-01-01; - let x = 'a'; - meta::pure::router::preeval::tests::Position.all($businessDate)->groupBy( - [], - agg(x|$x.client($businessDate).name + 'b', y|$y->count()), - 'ID Count' - ); - }; - - //we re-write the lambda to inline an AggregateValue with businessDate variable - let aggregateValue = ^meta::pure::functions::collection::AggregateValue(mapFn={x:meta::pure::router::preeval::tests::Position[1] | $x.client(%2023-01-01).name + $x->toString()},aggregateFn={y:String[*] | $y->count()}); - - let expressionSequence = $lambda.expressionSequence->evaluateAndDeactivate(); - let groupBy = $expressionSequence->at(2)->cast(@SimpleFunctionExpression); - - let mapFn = $aggregateValue.mapFn; - let mapFnExpressionSequnce = $mapFn.expressionSequence->evaluateAndDeactivate()->cast(@SimpleFunctionExpression)->toOne(); - - let plus = $mapFnExpressionSequnce.parametersValues->at(0)->cast(@InstanceValue); - let plusParams = $plus.values; - let plusLeft = $plusParams->at(0)->cast(@SimpleFunctionExpression); - let client = $plusLeft.parametersValues->at(0)->cast(@SimpleFunctionExpression); - let newClient = ^$client(parametersValues = [$client.parametersValues->at(0), ^VariableExpression(name='businessDate',genericType=^GenericType(rawType = StrictDate),multiplicity=PureOne)]); - - let newPlusLeft = ^$plusLeft(parametersValues = $newClient); - let newPlus = ^$plus(values = [$newPlusLeft,$plus.values->at(1)]); - - let newAggregateValue = ^$aggregateValue(mapFn = ^$mapFn(expressionSequence = ^$mapFnExpressionSequnce(parametersValues = [$newPlus]))); - let newAggregateValueInstance = ^InstanceValue(genericType=^GenericType(rawType = meta::pure::functions::collection::AggregateValue),multiplicity=PureOne, values = $newAggregateValue); - let newGroupBy = ^$groupBy(parametersValues = [$groupBy.parametersValues->at(0), $groupBy.parametersValues->at(1), $newAggregateValueInstance, $groupBy.parametersValues->at(3)]); - let newLambda = ^$lambda(expressionSequence = [$expressionSequence->at(0), $expressionSequence->at(1), $newGroupBy]); - - - let expected = {| - meta::pure::router::preeval::tests::Position.all(%2023-01-01)->groupBy([], ^meta::pure::functions::collection::AggregateValue(mapFn={x:meta::pure::router::preeval::tests::Position[1] | $x.client(%2023-01-01).name + $x->toString()},aggregateFn={y:String[*]|$y->count()}), ['ID Count']) - }; - - assertRoundTrip($newLambda, $expected); -} - -function <> meta::pure::router::preeval::tests::testDecimalType():Boolean[1] -{ - let input = {|meta::pure::router::preeval::tests::FinancialInstrument.all() - ->project([ - col(f:meta::pure::router::preeval::tests::FinancialInstrument[1]|$f.price, 'price'), - col(f:meta::pure::router::preeval::tests::FinancialInstrument[1]|parseDecimal('3.14D'), 'decimal_constant_col') - ]) - }; - - let expected = {|meta::pure::router::preeval::tests::FinancialInstrument.all() - ->project([ - ^BasicColumnSpecification(name = 'price', func={f:meta::pure::router::preeval::tests::FinancialInstrument[1]| $f.price }), - ^BasicColumnSpecification(name = 'decimal_constant_col', func={f:meta::pure::router::preeval::tests::FinancialInstrument[1]| 3.14}) - ]) - }; - - assertRoundTrip($input,$expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_castEmptyCollection():Boolean[1] -{ - let input = {b:Integer[*]|$b->uniqueValueOnly()}; - - let expected = {b:Integer[*]|if($b->distinct()->size() == 1, {|$b->max()}, {|[]})}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testEvalWithArgs1():Boolean[1] -{ - let input = {| - {x:Integer[1]|$x+1}->eval(5); - }; - - let expected = {|6}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testEvalWithArgs2():Boolean[1] -{ - let input = {|{t:TabularDataSet[1]|$t->filter(r:TDSRow[1]|$r.getString('firstName') == 'firstName')} - ->eval({|meta::pure::router::preeval::tests::Person.all()->project(col(p:meta::pure::router::preeval::tests::Person[1]|$p.firstName, 'firstName'))}->eval())}; - - let expected = {|meta::pure::router::preeval::tests::Person.all() - ->project(^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName})) - ->filter(r:TDSRow[1]|$r.getString('firstName') == 'firstName') - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testEvalWithArgs3():Boolean[1] -{ - let input = {a:Integer[1]|{b:Integer[1]|$b+1}->eval($a)}; - - let expected = {a:Integer[1]| $a+1 }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testProjectWithInferredParameterType():Boolean[1] -{ - let input = {|meta::pure::router::preeval::tests::myFunc888()}; - - let expected = {| meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age}) - ]) - }; - - assertRoundTrip($input, $expected); -} - -function meta::pure::router::preeval::tests::myFunc888() : TabularDataSet[1] -{ - meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age') - ]) -} - - - - -function <> meta::pure::router::preeval::tests::testPreroutingRemoveUnnecessaryStatements():Boolean[1] -{ - let input = {| - range(5)->map(x|$x+1)->map(index|'i' + $index->toString()); - 123; - }; - - let expected = {| - 123; - }; - - assertRoundTrip($input, $expected); -} - - -function meta::pure::router::preeval::tests::myStopFunction(b: Integer[1]) : Integer[1] -{ - $b+1; -} - -function <> meta::pure::router::preeval::tests::testAdditionalStopFunction():Boolean[1] -{ - let input = {a:Integer[1]|myStopFunction($a)}; - - let expected = {a:Integer[1]| $a+1 }; - - assertRoundTrip($input, $expected); - assertRoundTrip($input, $input, myStopFunction_Integer_1__Integer_1_, [], noDebug()); -} - -function <> meta::pure::router::preeval::tests::testFilterFalseSimplification():Boolean[1] -{ - let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | false).id}; - - let expected = {a:OrganizationalEntity[1]| []}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testFilterFalseSimplificationAll():Boolean[1] -{ - let input = {s:String[0..1]| OrganizationalEntity.all(%2023-01-01)->filter(c | 1 == 2)->project(c | $c.name, 'name')}; - - let expected = {s:String[0..1]| OrganizationalEntity.all(%2023-01-01)->filter(c | false)->project(c | $c.name, 'name')}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testFilterFalseSimplification2():Boolean[1] -{ - let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | 1 == 2).id}; - - let expected = {o:OrganizationalEntity[1]| []}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testFilterFalseConstantSimplification():Boolean[1] -{ - let input = {o:OrganizationalEntity[1]| let bool = false; let date = %2023-01-01; $o.positions($date)->filter(p | $bool).id;}; - - let expected = {o:OrganizationalEntity[1]| []}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testFilterFalseSimplificationReturnType():Boolean[1] -{ - let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | false).id;}; - let result = $input->preval([]); - - assertEquals(Integer, $result->functionReturnType().rawType); -} - -function <> meta::pure::router::preeval::tests::testFilterTrueSimplification():Boolean[1] -{ - let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | false; true;).id}; - - let expected = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01).id}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testFilterTrueSimplification2():Boolean[1] -{ - let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | 1 == 1).id}; - - let expected = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01).id}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testFilterTrueConstantSimplification():Boolean[1] -{ - let input = {o:OrganizationalEntity[1]| let bool = true; let date = %2023-01-01; $o.positions($date)->filter(p | $bool).id;}; - - let expected = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01).id}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testFilterNoSimplification():Boolean[1] -{ - let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | $p.id == true).id}; - - let expected = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | $p.id == true).id}; - - assertRoundTrip($input, $expected); -} - - -function <> meta::pure::router::preeval::tests::testToOneManyElimination1():Boolean[1] -{ - let input = {| 'hello'->toOneMany()}; - - let expected = {| 'hello'}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testToOneManyElimination2():Boolean[1] -{ - let input = {x:String[*]| $x->toOneMany()}; - - let expected = $input; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testToOneManyElimination3():Boolean[1] -{ - let input = {x:String[1..*]| $x->toOneMany()}; - - let expected = {x:String[1..*]| $x}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testToOneElimination1():Boolean[1] -{ - let input = {| 'hello'->toOne()}; - - let expected = {| 'hello'}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testToOneElimination2():Boolean[1] -{ - let input = {x:String[*]| $x->toOne()}; - - let expected = $input; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testToOneElimination3():Boolean[1] -{ - let input = {x:String[1]| $x->toOne()}; - - let expected = {x:String[1]| $x}; - - assertRoundTrip($input, $expected); -} - - -function <> meta::pure::router::preeval::tests::testRecursiveSimpleConcreteFunctionDefinition():Boolean[1] -{ - // This is specially selected to be a single function that is recursive on itself - // The test is to validate that we don't keep trying to inline ad infinitum - - let input = {s:Class[1]|meta::pure::functions::meta::hierarchicalProperties($s)}; - - let expected = {s:Class[1]| - if($s == Any, {|[]}, {|$s.properties->concatenate($s.generalizations->map({g|$g.general.rawType->cast(Any) - ->toOne() - ->hierarchicalProperties()})) - ->removeDuplicates([], [])}) - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testLetOnlyStatement():Boolean[1] -{ - let input = {|myFunc2()}; - - let expected = {| 'hello'}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testLambdaParamOverride():Boolean[1] -{ - let input = { | let val = 1; - {val:Integer[1]| $val};}; - - let expected = { | {val:Integer[1]| $val};}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testLambdaParamOverride2():Boolean[1] -{ - let val = 1; - let input = { | let x = 1; - {p:Integer[1]|$p->map(x|$x+$val)};}; - let expected = { | {p:Integer[1]|$p->map(x|$x+1)}}; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::myFunc2():String[1] -{ - let x = 'hello'; -} - -function meta::pure::router::preeval::tests::getTestRuntimeWithModelConnection(sourceClass:Class[1], inputs:Any[*]):Runtime[1] -{ - // Need element as string to allow roundtrip to pass. When using ^ModelStore(), preeval evaluates it to a instance, however expected as it as a new function - ^Runtime(connections=^meta::pure::mapping::modelToModel::ModelConnection(element = 'DummyStore', instances=newMap(pair($sourceClass, list($inputs))))); -} - -function <> meta::pure::router::preeval::tests::assertRoundTrip(input : FunctionDefinition[1], expectedOrig : FunctionDefinition[1]):Boolean[1] -{ - assertRoundTrip($input, $expectedOrig, noDebug()) -} - -function <> meta::pure::router::preeval::tests::assertRoundTrip(input : FunctionDefinition[1], expectedOrig : FunctionDefinition[1], debug:meta::pure::tools::DebugContext[1]):Boolean[1] -{ - meta::pure::router::preeval::tests::assertRoundTrip($input, $expectedOrig, [], [], $debug); -} - -function <> meta::pure::router::preeval::tests::assertRoundTrip(input : FunctionDefinition[1], expectedOrig : FunctionDefinition[1], stopInlineFunctions:Function[*], extensions:Extension[*], debug:meta::pure::tools::DebugContext[1]):Boolean[1] -{ - let expected = $expectedOrig; - - let result = if($stopInlineFunctions->isEmpty(), - | $input->preval(newMap([]->cast(@Pair)), $input->openVariableValues(), [], $debug).value->toOne(); , - | let inScopeVars = $input->openVariableValues(); - let state = meta::pure::router::preeval::getPreevalStateWithAdditionalStopInlineFunc($inScopeVars, $extensions, $stopInlineFunctions); - $input->preval($state, $extensions).value->toOne();); - - let tExpected = $expected->transformFunctionBody($extensions); - let tResult = $result->transformFunctionBody($extensions); - - let jExpected = $tExpected->toJSON(50000); - let jResult = $tResult->toJSON(50000); - - let pjExpected = $jExpected->parseJSON()->toPrettyJSONString(); - let pjResult = $jResult->parseJSON()->toPrettyJSONString(); - - if($pjExpected == $pjResult, - | true, - | - println($pjExpected); - println($pjResult); - - let pExpected = $tExpected->toPure($extensions); - let pResult = $tResult->toPure($extensions); - - assertEquals($pExpected, $pResult, 'Mismatch between expected and actual result content.\nexpected pure:\n%r\n\nactual pure:\n%r', [$pExpected->toOne(), $pResult->toOne()]); - ); - - // Open variables on lambdas should have been inlined / eliminated - if (!$result->instanceOf(LambdaFunction), - | true, - | assertEmpty($result->cast(@LambdaFunction).openVariables) - ); - - if($input->cast(@Function)->functionType().parameters->isNotEmpty(), - | true, - | - let inputEvalResult = $input->evaluate([]); - let expectedEvalResult = $expected->evaluate([]); - let resultEvalResult = $result->evaluate([]); - - let open = $input->openVariableValues(); - - //println($input->cast(@Function)->functionReturnMultiplicity()); - - let mInput = $input->cast(@Function)->functionReturnMultiplicity(); - let mExpected = $expected->cast(@Function)->functionReturnMultiplicity(); - let mResult = $result->cast(@Function)->functionReturnMultiplicity(); - - let jInputEvalResult = $inputEvalResult->transformAny([], $open, $mExpected, $extensions)->toJSON(50000); - let jExpectedEvalResult = $expectedEvalResult->transformAny([], $open, $mExpected, $extensions)->toJSON(50000); - println($jExpectedEvalResult); - let jResultEvalResult = $resultEvalResult->transformAny([], $open, $mExpected, $extensions)->toJSON(50000); - - - assertEquals($jExpectedEvalResult, $jResultEvalResult, '\nexpected eval result: %r\nactual eval result: %r', [$jExpectedEvalResult->toOne(), $jResultEvalResult->toOne()]); - - if(!$inputEvalResult->exists(x|$x->match([b:BasicColumnSpecification[*]|true, a:Any[*]|false])), - | assertEquals($jInputEvalResult, $jExpectedEvalResult, '\nexpected input eval: %r\nactual expected eval: %r', [$jInputEvalResult->toOne(), $jExpectedEvalResult->toOne()]), - | true //The lambda on the BCS can get confused for assertion checking (as it can be "un-optimised") - ); - ); - - true; +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Press F9 to execute the 'go' function... +// Press F10 to run the full test suite + +import meta::json::*; +import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::*; +import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::toPureGrammar::*; +import meta::pure::extension::*; +import meta::pure::graphFetch::execution::*; +import meta::pure::mapping::*; +import meta::pure::mapping::modelToModel::test::simple::*; +import meta::pure::mapping::modelToModel::test::shared::dest::*; +import meta::pure::mapping::modelToModel::test::shared::src::*; +import meta::pure::router::preeval::*; +import meta::pure::router::preeval::tests::*; +import meta::pure::runtime::*; + +Class meta::pure::router::preeval::tests::Person +{ + firstName : String[1]; + lastName : String[1]; + otherNames : String[*]; + + name(){$this.firstName+' '+$this.lastName}:String[1]; + nameWithTitle(title:String[1]){$title+' '+$this.firstName+' '+$this.lastName}:String[1]; + nameWithPrefixAndSuffix(prefix:String[0..1], suffixes:String[*]) + { + if($prefix->isEmpty(), + | if($suffixes->isEmpty(), + | $this.firstName + ' ' + $this.lastName, + | $this.firstName + ' ' + $this.lastName + ', ' + $suffixes->joinStrings(', ')), + | if($suffixes->isEmpty(), + | $prefix->toOne() + ' ' + $this.firstName + ' ' + $this.lastName, + | $prefix->toOne() + ' ' + $this.firstName + ' ' + $this.lastName + ', ' + $suffixes->joinStrings(', '))) + }:String[1]; + + fullName(lastNameFirst:Boolean[1]) + { + if($lastNameFirst, | $this.lastName + ', ' + $this.firstName, | $this.firstName + ' ' + $this.lastName) + }:String[1]; + extraInformation : String[0..1]; + manager : meta::pure::router::preeval::tests::Person[0..1]; + age : Integer[0..1]; + constant() { 'constant' } : String[1]; + nickName : String[0..1]; + activeEmployment: Boolean[0..1]; +} + +Class meta::pure::router::preeval::tests::FinancialInstrument +{ + id : Integer[1]; + price : Decimal[1]; +} + +Class <> meta::pure::router::preeval::tests::BaseAccount +{ + id: Integer[1]; + accountId(){ + $this.id + }:Integer[1]; +} + +Class <> meta::pure::router::preeval::tests::Position +{ + account:BaseAccount[1]; + id: Integer[1]; + quantity: Float[1]; +} + +Class <> meta::pure::router::preeval::tests::BaseOrganizationalEntity +{ + name: String[1]; + id: Integer[1]; +} + +Class <> meta::pure::router::preeval::tests::OrganizationalEntity extends BaseOrganizationalEntity +{ + isActive: Boolean[1]; + parent: OrganizationalEntity[1]; + parentAtDate(date:Date[1]) + { + $this.parent($date); + }:OrganizationalEntity[1]; + ceo: meta::pure::router::preeval::tests::Person[1]; + + latestPositions() + { + $this.positions(now()); + }:Position[*]; +} + +Association meta::pure::router::preeval::tests::PositionClient +{ + positions:Position[*]; + client:OrganizationalEntity[1]; +} + +Class <> meta::pure::router::preeval::tests::ClassA +{ + myPropertyB : meta::pure::router::preeval::tests::ClassB[*]; +} + +Class <> meta::pure::router::preeval::tests::ClassB +{ + myLeafProperty : String[1]; +} + + +function <> meta::pure::router::preeval::tests::testPrerouting1():Boolean[1] +{ + let input = {| let x = 'hello' + ' world'; $x + (' ' + (5->toString()));}; + + let expected = {| 'hello world 5';}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting2():Boolean[1] +{ + let input = {| let x = today()->adjust(1, DurationUnit.DAYS); let y = %2017-01-01->adjust(1, DurationUnit.DAYS); }; + + let expected = {| let x = today()->adjust(1, DurationUnit.DAYS); %2017-01-02; }; + + assertRoundTrip($input, $expected); +} + + +function <> meta::pure::router::preeval::tests::testPrerouting2b():Boolean[1] +{ + let input = {| + let x = firstDayOfThisYear()->adjust(1, DurationUnit.DAYS); + let y = firstDayOfThisMonth(); + let z = firstDayOfThisQuarter()->adjust(2, DurationUnit.DAYS); + + let a = mostRecentDayOfWeek(DayOfWeek.Monday); + let b= previousDayOfWeek(DayOfWeek.Monday); + + true; + }; + + let expected = {| + let x = today()->firstDayOfYear()->adjust(1, DurationUnit.DAYS); + let y = today()->firstDayOfMonth(); + let z = today()->firstDayOfQuarter()->adjust(2, DurationUnit.DAYS); + + let a = today()->mostRecentDayOfWeek(DayOfWeek.Monday); + let b = today()->previousDayOfWeek(DayOfWeek.Monday); + + true; + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting3():Boolean[1] +{ + let input = {| let y = 1; $y + (6 + 10);}; + + let expected = {| 17;}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting4():Boolean[1] +{ + let value = 'hello world'; + + let input = {| let y = $value->substring(0, 5); }; + + let expected = {| 'hello'}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting5():Boolean[1] +{ + let input = {| currentUserId() + '_user' ;}; + + let expected = $input; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting6():Boolean[1] +{ + let value = 10; + + let input = {| range(5)->map(x|$x+$value); }; + + let expected = {| [10, 11, 12,13,14]}; + + assertRoundTrip($input, $expected); +} + + +function <> meta::pure::router::preeval::tests::testPrerouting7():Boolean[1] +{ + let input = {| %2018-01-02->toString()->myExpandableFunc(5) ;}; + + let expected = {| '2018-01-02|2018-01-02|2018-01-02|2018-01-02|2018-01-02'}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting8():Boolean[1] +{ + let input = {| today()->toString()->myNonExpandableMultiExpressionFunc(5) ;}; + + let expected = $input; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting9():Boolean[1] +{ + let input = {| {x:String[1]| $x + 'hello'} ;}; + + let expected = $input; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting10():Boolean[1] +{ + let input = {| range(3)->map(x|$x->toString());}; + + let expected = {| ['0','1','2']}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting11():Boolean[1] +{ + let input = {| range(3)->map(x| $x->map(index|$index->toString()));}; + + let expected = {| ['0','1','2']}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting12():Boolean[1] +{ + let input = {| range(3)->map(x| $x->toString()->map(index|$index));}; + + let expected = {| ['0','1','2']}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting13():Boolean[1] +{ + let input = {| range(4)->map(x| [1,2,3,4,5]->take($x)->map(index|$index));}; + + let expected = {| [1,1,2,1,2,3]}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting14():Boolean[1] +{ + let input = {| range(3)->map(x| [1,2,3,4,5]->take($x + 1)->map(index|$index));}; + + let expected = {| [1,1,2,1,2,3]}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting15():Boolean[1] +{ + // ->cast(@Integer) added to help with compile mode java generation, this should not be needed but java compiler fails on java generated code + let input = {| range(3)->map(x| range(5)->map(k|$k+1)->cast(@Integer)->take($x + 1)->map(index|$index->cast(@Integer)));}; + + let expected = {| [1,1,2,1,2,3]}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting16():Boolean[1] +{ + let input = {| range(3)->map(x|$x+1)->map(index|'i' + $index->toString()) ;}; + + let expected = {|['i1', 'i2', 'i3'] }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting17():Boolean[1] +{ + let c = 3; + + let input = {| range($c)->map(x|$x+1)->map(index|'i' + $index->toString()) ;}; + + let expected = {|['i1', 'i2', 'i3'] }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting18():Boolean[1] +{ + let c = 3; + + let input = {| range($c)->map(x|$x+1)->map(index|('i' + $c->makeString()) + $index->toString()) ;}; + + let expected = {|['i31', 'i32', 'i33'] }; + + assertRoundTrip($input, $expected); +} + + +function <> meta::pure::router::preeval::tests::testPrerouting19():Boolean[1] +{ + let input = {|['a']->fold({p,x|$x->concatenate(range(2)->map(index|$p)->joinStrings('|'))}, [])}; + + let expected = {|['a|a']}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting20():Boolean[1] +{ + let input = {|['hello']->map(a|$a->f1())}; + + let expected = {|'hello'}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::f1(zzz : String[1]):String[1] +{ + $zzz->map(k|$zzz->substring(0,5)); +} + +function <> {test.excludePlatform = 'Java compiled'} meta::pure::router::preeval::tests::testPrerouting20a():Boolean[1] +{ + let input = {|'hello'->map(x|$x->myFirst1())}; + + let expected = {|'hello'}; + + assertRoundTrip($input, $expected); +} + +function meta::pure::router::preeval::tests::myFirst1(theFirstSet:T[*]):T[0..1] +{ + myFirst2($theFirstSet); +} + +function meta::pure::router::preeval::tests::myFirst2(theSecondSet:T[*]):T[0..1] +{ + $theSecondSet->first(); +} + +function <> meta::pure::router::preeval::tests::testPrerouting21():Boolean[1] +{ + let input = {|['a']->fold({p,x|$x->concatenate(myExpandableFunc2($p, 3))}, [])}; + + let expected = {|'a|a|a' }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::myExpandableFunc2(val : String[1], count : Integer[1]):String[*] +{ + range(0, $count, 1)->map(index|$val)->joinStrings('|'); +// $val->myFakeJoin('|'); +} + + +function <> meta::pure::router::preeval::tests::testPrerouting_mapOnInstanceValuesExpanded():Boolean[1] +{ + let c = 3; + + let input = {| range(1, $c+1, 1)->map(index|$index->toString() + currentUserId()->toString()) ;}; + + let expected = {|[ + ('1' + meta::pure::runtime::currentUserId()->toString()), + ('2' + meta::pure::runtime::currentUserId()->toString()), + ('3' + meta::pure::runtime::currentUserId()->toString()) + ] }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_foldOnInstanceValuesExpanded():Boolean[1] +{ + let c = 2; + + let input = {| + range(1, $c+1, 1)->fold({index, r| + $r + ($index->toString() + currentUserId() + ';'); + }, 'Values: ') + }; + + let expected = {| + 'Values: ' + '1' + meta::pure::runtime::currentUserId() + ';' + '2' + meta::pure::runtime::currentUserId() + ';' + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_foldOnInstanceValuesExpanded2():Boolean[1] +{ + let c = 2; + + let input = {| + range(1, $c+1, 1)->fold({index, r| + $r->concatenate(($index->toString() + currentUserId())); + }, []) + }; + + let expected = {| + [('1' + currentUserId()), ('2' + currentUserId())] + ///[('1' + meta::pure::runtime::currentUserId())] ->concatenate('2' + meta::pure::runtime::currentUserId()) + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_concatenateInstanceValuesExpanded_Basic():Boolean[1] +{ + let c = 2; + + let input = {| + ['hello']->concatenate(currentUserId()); + }; + + let expected = {| + ['hello', currentUserId()]; + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_concatenateInstanceValuesExpanded_Complex():Boolean[1] +{ + let x = 2; + + let input = {| + let a = []->concatenate(currentUserId()); + let b = []->concatenate(['hello', currentUserId()]); + let c = []->concatenate(['hello', currentUserId(), (currentUserId() + $x->toString())]); + + let d = currentUserId()->concatenate([]); + let e = ['hello', currentUserId()]->concatenate([]); + let f = ['hello', currentUserId(), (currentUserId() + $x->toString())]->concatenate([]); + + let g = currentUserId()->concatenate(currentUserId()); + let h = ['hello', currentUserId()]->concatenate(['hello', currentUserId()]); + let i = ['hello', currentUserId(), (currentUserId() + $x->toString())]->concatenate(['hello', currentUserId(), (currentUserId() + $x->toString())]); + + let j = myCollectionFunc($x->toString())->concatenate(currentUserId()); + + let k = {a:Integer[1], b:Integer[1]|$a->concatenate($b)}; + let l = {a:Integer[*], b:Integer[1]|$a->concatenate($b)}; + let m = {a:Integer[1], b:Integer[*]|$a->concatenate($b)}; + let n = {a:Integer[*], b:Integer[*]|$a->concatenate($b)}; + + true; + }; + + let expected = {| + let a = currentUserId(); + let b = ['hello', currentUserId()]; + let c = ['hello',meta::pure::runtime::currentUserId(),(meta::pure::runtime::currentUserId() + '2')]; + + let d = currentUserId(); + let e = ['hello', currentUserId()]; + let f = ['hello',meta::pure::runtime::currentUserId(),(meta::pure::runtime::currentUserId() + '2')]; + + let g = [meta::pure::runtime::currentUserId(),meta::pure::runtime::currentUserId()]; + let h = ['hello',meta::pure::runtime::currentUserId(),'hello',meta::pure::runtime::currentUserId()]; + let i = ['hello',meta::pure::runtime::currentUserId(),(meta::pure::runtime::currentUserId() + '2'),'hello',meta::pure::runtime::currentUserId(),(meta::pure::runtime::currentUserId() + '2')]; + + let j= ['2', currentUserId()]; + + let k = {a:Integer[1], b:Integer[1]|[$a,$b]}; + let l = {a:Integer[*], b:Integer[1]|$a->concatenate($b)}; + let m = {a:Integer[1], b:Integer[*]|$a->concatenate($b)}; + let n = {a:Integer[*], b:Integer[*]|$a->concatenate($b)}; + + true; + }; + + assertRoundTrip($input, $expected); +} + +function meta::pure::router::preeval::tests::myCollectionFunc(values:T[m]):T[m] +{ + $values; +} + +function <> meta::pure::router::preeval::tests::testPrerouting23a():Boolean[1] +{ + let input = {| if(false, | []->toOne(), | 'hello ' + 'world') ;}; + + let expected = {| 'hello world' }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting23b():Boolean[1] +{ + let input = {| if(true, | 'hello ' + 'world', | []->toOne() ) ;}; + + let expected = {| 'hello world' }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting24a():Boolean[1] +{ + let x = []; + + let input = {| + $x->isEmpty() || ($x->at(0) == 'hello'); + }; + + let expected = {| true }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting24b():Boolean[1] +{ + let x = []; + + let input = {| + $x->isNotEmpty() && ($x->at(0) == 'hello'); + }; + + let expected = {| false }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting24c():Boolean[1] +{ + let x = []; + + let input = {| + (today() > %2018-01-01) && (1 == 1); + }; + + let expected = {| today() > %2018-01-01 }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting24d():Boolean[1] +{ + let x = []; + + let input = {| + (today() > %2018-01-01) || (1 == 1); + }; + + let expected = {| true }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting24e():Boolean[1] +{ + let x = []; + + let input = {| + (1 == 1) && (today() > %2018-01-01); + }; + + let expected = {| today() > %2018-01-01 }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting24f():Boolean[1] +{ + let x = []; + + let input = {| + (1 == 1) || (today() > %2018-01-01); + }; + + let expected = {| true }; + + assertRoundTrip($input, $expected); +} + + +function <> meta::pure::router::preeval::tests::testPrerouting25a():Boolean[1] +{ + let input = {|meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName'), + col({p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}, 'lastName'), + col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age'), + col({p:meta::pure::router::preeval::tests::Person[1]|today()}, 'today') + ]) + ->extend(^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}))} ; + + let expected = {| meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}), + ^BasicColumnSpecification(name = 'lastName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}), + ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age}), + ^BasicColumnSpecification(name = 'today', func = {p:meta::pure::router::preeval::tests::Person[1]|today()}) + ]) + ->extend(^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age') ->toString()}))}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting25b():Boolean[1] +{ + let input = {|meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName + ('hello' + '_world')}, 'firstName'), + col({p:meta::pure::router::preeval::tests::Person[1]|'world' + ' blue'}, 'const'), + col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age') + ]) + ->extend(['e']->map(x|col({row:TDSRow[1]|$row.getInteger('ag' + $x) ->toString()}, 'ageString')))}; + + let expected = {| meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName + 'hello_world'}), + ^BasicColumnSpecification(name = 'const', func = {p:meta::pure::router::preeval::tests::Person[1]|'world blue'}), + ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age})]) + ->extend(^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}))}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting25c():Boolean[1] +{ + let blue = 'blue'; + + let cols = [ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName + ('hello' + '_world')}, 'firstName'), + col({p:meta::pure::router::preeval::tests::Person[1]|'world' + ' ' + $blue}, 'const'), + col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age') + ]; + + let input = {|meta::pure::router::preeval::tests::Person.all() + ->project($cols) + ->extend(['e']->map(x|col({row:TDSRow[1]|$row.getInteger('ag' + $x) ->toString()}, 'ageString'))) + }; + + let expected = {| meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName + 'hello_world'}), + ^BasicColumnSpecification(name = 'const', func = {p:meta::pure::router::preeval::tests::Person[1]|'world blue'}), + ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age})]) + ->extend(^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}))}; + + assertRoundTrip($input, $expected); +} + + + +function <> meta::pure::router::preeval::tests::testPrerouting26():Boolean[1] +{ + let input = {| + pair('hello', today()); + }; + + let expected = {| ^Pair(first='hello', second = today()) }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting27a():Boolean[1] +{ + let input = {| + [1,2,3]->map(v| ^Pair(first = 'hello', second = $v)); + }; + + let expected = {| [^Pair(first = 'hello', second = 1),^Pair(first = 'hello', second = 2),^Pair(first = 'hello', second = 3)] }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting27b():Boolean[1] +{ + let input = {| + [1,2,3]->map(v| pair('hello', $v)); + }; + + let expected = {| [^Pair(first = 'hello', second = 1),^Pair(first = 'hello', second = 2),^Pair(first = 'hello', second = 3)] }; + + assertRoundTrip($input, $expected); +} + + +// function <> meta::pure::router::preeval::tests::testPrerouting27c():Boolean[1] +// { +// let input = {| +// [today(),today(),today()]->map(v| pair('hello', $v)); +// }; + +// let expected = {| [today(),today(),today()]->map(v|^Pair(first = 'hello', second = $v)) }; + +// assertRoundTrip($input, $expected); +// } + +function <> meta::pure::router::preeval::tests::testPrerouting28():Boolean[1] +{ + let input = {| + [1,2,3]->map(v| $v->map(x|pair('hello' + '*' + $x->makeString(), $v))); + }; + + let expected = {| [^Pair(first = 'hello*1', second = 1),^Pair(first = 'hello*2', second = 2),^Pair(first = 'hello*3', second = 3)] }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting29a():Boolean[1] +{ + let input = {r:String[1]| ['a', 'b', 'c']->map(v| $r + ('_' + $v))}; + + let expected = {r:String[1]|[ ($r + '_a'),($r + '_b'),($r + '_c')]}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::myPair(firstVal : String[1], secondVal : StrictDate[1]):Pair[1] +{ + ^Pair(first = $firstVal, second=$secondVal); +} + +function <> meta::pure::router::preeval::tests::testPrerouting29b():Boolean[1] +{ + let input = {| range(3)->map(index|today()) ;}; + + let expected = {|[today(),today(),today()] }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting29c():Boolean[1] +{ + let input = {| myNonFullyExpandableDateFunc('a', 5) ;}; + + let expected = {|[ + ('a' + (today()->toString())), + ('a' + (today()->toString())), + ('a' + (today()->toString())), + ('a' + (today()->toString())), + ('a' + (today()->toString())) + ]->joinStrings('|'); + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::myExpandableFunc(val : String[1], count : Integer[1]):String[1] +{ + range($count)->map(index|$val)->joinStrings('|'); +} + +function <> meta::pure::router::preeval::tests::testPrerouting30():Boolean[1] +{ + let input = {| + range(3)->map(index|[$index, today()]); + }; + + let expected = {| + [0,today(), 1,today(), 2,today()] + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting31a():Boolean[1] +{ + let input = {| + range(2)->map(a|^BasicColumnSpecification(name = 'age_' + $a->makeString(), func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + ($a * 10)})) + }; + + let expected = {|[^BasicColumnSpecification(name = 'age_0', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 0}),^BasicColumnSpecification(name = 'age_1', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 10})] }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting31b():Boolean[1] +{ + let input = {| meta::pure::router::preeval::tests::Person.all() + ->project( + range(2)->map(a| + [col({p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + ($a * 10)}, 'age_' + makeString($a*10)),col({p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + (($a*10) + 1)}, 'age_' + makeString(($a*10) + 1))] + ) + ) + }; + + let expected = {|meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'age_0', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 0}), + ^BasicColumnSpecification(name = 'age_1', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 1}), + ^BasicColumnSpecification(name = 'age_10', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 10}), + ^BasicColumnSpecification(name = 'age_11', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age->toOne() + 11}) + ]) + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting32a():Boolean[1] +{ + let input = {| ['e']->map(x|col({row:TDSRow[1]|$row.getInteger('ag' + $x)->toString()}, 'ageString'));}; + + let expected = {|^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}) }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting32b():Boolean[1] +{ + let input = {| let x = 'e'; col({row:TDSRow[1]|$row.getInteger('ag' + $x)->toString()}, 'ageString');}; + + let expected = {|^BasicColumnSpecification(name = 'ageString', func = {row:TDSRow[1]|$row.getInteger('age')->toString()}); }; + + assertRoundTrip($input, $expected); +} + + + +function <> meta::pure::router::preeval::tests::myFakeJoin(vals : String[*], char : String[1]):String[1] +{ + let x = 'hello'; + $vals->drop(1)->fold({x,n| $x + $char + $n}, $vals->at(0)); + //$vals->at(0); +} + +function <> meta::pure::router::preeval::tests::myComplexExpandableFunc(vals : String[*]):String[1] +{ + $vals->map(x|$x->myExpandableFunc(1))->joinStrings('hello'); +} + + + +function meta::pure::router::preeval::tests::myNonExpandableMultiExpressionFunc(val : String[1], count : Integer[1]):String[1] +{ + let x = range($count); + $x->map(index|$val)->joinStrings('|'); +} + +function <> meta::pure::router::preeval::tests::myNonFullyExpandableDateFunc(val : String[1], count : Integer[1]):String[1] +{ + range($count)->map(index|$val + today()->toString())->joinStrings('|'); +} + + + +function <> meta::pure::router::preeval::tests::testPrerouting33():Boolean[1] +{ + let input = {| + meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName'), + col({p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}, 'lastName'), + col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age'), + col({p:meta::pure::router::preeval::tests::Person[1]|today()}, 'today') + ]) + ->filterToStringColumns() + }; + + let expected = {| + meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}), + ^BasicColumnSpecification(name = 'lastName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}), + ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age}), + ^BasicColumnSpecification(name = 'today', func = {p:meta::pure::router::preeval::tests::Person[1]|today()}) + ]) + ->restrict(['firstName','lastName']) + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::filterToStringColumns(input : TabularDataSet[1]):TabularDataSet[1] +{ + $input->restrict($input.columns->filter(c|$c.type == String).name); +} + +function <> meta::pure::router::preeval::tests::extendColumns(input : TabularDataSet[1], cols : String[*]):TabularDataSet[1] +{ + $input->extend($input.columns->filter(c|$c.name->in($cols))->sortBy(c|$cols->indexOf($c.name))->map(c| + if($c.type == String, + | col({row:TDSRow[1]|$row.getString($c.name)}, $c.name + '_ext'), + | if($c.type == Integer, + | col({row:TDSRow[1]|$row.getInteger($c.name)}, $c.name + '_ext'), + | col({row:TDSRow[1]|$row.getDate($c.name)}, $c.name + '_ext') + ) + ) + )); +} + +function <> meta::pure::router::preeval::tests::testPrerouting34():Boolean[1] +{ + let input = {| + let businessDate = now(); + let processingDate = today()->adjust(-1, DurationUnit.DAYS); + + meta::pure::router::preeval::tests::Position.all($processingDate) + ->project([ + x | $x.quantity, + x | $x.client($businessDate).name], + ['Quantity', + 'Client/Name'] + ) + ->extendColumns('Quantity'); + }; + + let expected = {| + let businessDate = now(); + let processingDate = today()->adjust(-1, DurationUnit.DAYS); + + meta::pure::router::preeval::tests::Position + ->getAll($processingDate) + ->project([{x|$x.quantity},{x|$x.client($businessDate).name}], ['Quantity','Client/Name']) + ->extend(^BasicColumnSpecification(name = 'Quantity_ext', func = {row:TDSRow[1]|$row.getDate('Quantity')})); + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting35():Boolean[1] +{ + let input = {| + let x = 'world'; + + {|('hello' + ' ') + $x}->eval(); + }; + + let expected = {| + 'hello world'; + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_parameterAssignedToVariable():Boolean[1] +{ + let input = {item:Pair[1]| + let other = $item; + $other.first->isNotEmpty(); + }; + + let expected = {item:Pair[1]| + $item.first->isNotEmpty(); + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting36():Boolean[1] +{ + let input = {a:meta::pure::router::preeval::tests::ClassA[1]| + $a.myPropertyB.myLeafProperty->map(x|now())->map(d|$d->adjust(-1, DurationUnit.DAYS)) + }; + + assertRoundTrip($input, $input); +} + +function <> meta::pure::router::preeval::tests::testPrerouting37():Boolean[1] +{ + let input = {x:Integer[*]| + $x->otherFunc2(y|$y+1) + }; + + let expected = {x:Integer[*]| + $x->map({y|$y + 1}) + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::otherFunc2(x : Integer[*], l : FunctionDefinition<{Integer[1]->Integer[1]}>[1]):Integer[*] +{ + $x->map($l); +} + +function <> meta::pure::router::preeval::tests::testPrerouting38():Boolean[1] +{ + let input = {| + let x = myPair('hello', 'world'); + let y = myPair(1, $x.first + ' ' + $x.second); + }; + + let expected = {| + ^Pair(first = 1, second = 'hello world'); + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::myPair(v1 : X[1], v2 : Y[1]) : Pair[1] +{ + pair($v1, $v2); +} + +function <> meta::pure::router::preeval::tests::myPair2(v1 : U[1], v2 : V[1]) : Pair[1] +{ + pair($v1, $v2); +} + +function <> meta::pure::router::preeval::tests::testPrerouting39():Boolean[1] +{ + let input = {| + let x = 123; + $x->cast(@Integer); + }; + + let expected = {| + 123 + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting40a():Boolean[1] +{ + let input = {| + let x = 123; + $x->toOne()->myToOne(); + }; + + let expected = {| + 123; + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting40b():Boolean[1] +{ + let input = {| + let x = today(); + $x->toOne()->myToOne(); + }; + + let expected = {| + let x = today(); + $x; + }; + + assertRoundTrip($input, $expected); +} + + +function <> meta::pure::router::preeval::tests::myToOne(x : T[1]) : T[1] +{ + $x->toOne(); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_PropertyValue():Boolean[1] +{ + let prop = meta::pure::router::preeval::tests::Person.properties->at(0); + let name = $prop.name; + + let input = {| $prop.name;}; + + let expected = {| $name}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_OptionalLimit1():Boolean[1] +{ + let limit = 5; + + let input = {| + meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName') + ]) + ->meta::pure::router::preeval::tests::optionalLimit($limit) + }; + + let expected = {| + meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}) + ]) + ->limit(5); + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_OptionalLimit2():Boolean[1] +{ + let limit = []; + + let input = {| + meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName') + ]) + ->meta::pure::router::preeval::tests::optionalLimit($limit) + }; + + let expected = {| + meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}) + ]); + }; + + assertRoundTrip($input, $expected); +} + +function meta::pure::router::preeval::tests::optionalLimit(t: TabularDataSet[1], limit:Integer[0..1]) : TabularDataSet[1] +{ + if($limit->isEmpty() || ($limit->toOne()<=0), | $t, | $t->limit($limit)); +} + + +function <> meta::pure::router::preeval::tests::testPrerouting_mappedTdsAgg():Boolean[1] +{ + let v = [pair('name', '6')]; + + let input = {|meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age') + ]) + ->groupBy([], $v->map(pv|agg('sumAgg_' + $pv.first->toString(), {row:TDSRow[1]|if($row.getInteger('age')->toString() == $pv.second, | 1, | 0)}, y|$y->sum()))) + }; + + let expected = {| meta::pure::router::preeval::tests::Person.all() + ->project(^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age})) + ->groupBy([], agg('sumAgg_name', {row:TDSRow[1]|if($row.getInteger('age')->toString() == '6', |1, |0)}, {y|$y->sum()})) + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_mappedModelAgg():Boolean[1] +{ + let v = [pair('name', '6')]; + + let input = {|meta::pure::router::preeval::tests::Person.all() + ->groupBy([], + $v->map(pv|agg({p:meta::pure::router::preeval::tests::Person[1]|if($p.age->toOne()->toString() == $pv.second, | 1, | 0)}, y|$y->sum())), + $v->map(pv|'sumAgg_' + $pv.first) + ) + }; + + let expected = {|meta::pure::router::preeval::tests::Person.all() + ->groupBy([], agg({p:meta::pure::router::preeval::tests::Person[1]|if($p.age->toOne()->toString() == '6', |1, |0)}, {y|$y->sum()}), 'sumAgg_name') + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_constantInAgg():Boolean[1] +{ + let lambda = {| + let businessDate = %2023-01-01; + let x = 'a'; + meta::pure::router::preeval::tests::Position.all($businessDate)->groupBy( + [], + agg(x|$x.client($businessDate).name + 'b', y|$y->count()), + 'ID Count' + ); + }; + + //we re-write the lambda to inline an AggregateValue with businessDate variable + let aggregateValue = ^meta::pure::functions::collection::AggregateValue(mapFn={x:meta::pure::router::preeval::tests::Position[1] | $x.client(%2023-01-01).name + $x->toString()},aggregateFn={y:String[*] | $y->count()}); + + let expressionSequence = $lambda.expressionSequence->evaluateAndDeactivate(); + let groupBy = $expressionSequence->at(2)->cast(@SimpleFunctionExpression); + + let mapFn = $aggregateValue.mapFn; + let mapFnExpressionSequnce = $mapFn.expressionSequence->evaluateAndDeactivate()->cast(@SimpleFunctionExpression)->toOne(); + + let plus = $mapFnExpressionSequnce.parametersValues->at(0)->cast(@InstanceValue); + let plusParams = $plus.values; + let plusLeft = $plusParams->at(0)->cast(@SimpleFunctionExpression); + let client = $plusLeft.parametersValues->at(0)->cast(@SimpleFunctionExpression); + let newClient = ^$client(parametersValues = [$client.parametersValues->at(0), ^VariableExpression(name='businessDate',genericType=^GenericType(rawType = StrictDate),multiplicity=PureOne)]); + + let newPlusLeft = ^$plusLeft(parametersValues = $newClient); + let newPlus = ^$plus(values = [$newPlusLeft,$plus.values->at(1)]); + + let newAggregateValue = ^$aggregateValue(mapFn = ^$mapFn(expressionSequence = ^$mapFnExpressionSequnce(parametersValues = [$newPlus]))); + let newAggregateValueInstance = ^InstanceValue(genericType=^GenericType(rawType = meta::pure::functions::collection::AggregateValue),multiplicity=PureOne, values = $newAggregateValue); + let newGroupBy = ^$groupBy(parametersValues = [$groupBy.parametersValues->at(0), $groupBy.parametersValues->at(1), $newAggregateValueInstance, $groupBy.parametersValues->at(3)]); + let newLambda = ^$lambda(expressionSequence = [$expressionSequence->at(0), $expressionSequence->at(1), $newGroupBy]); + + + let expected = {| + meta::pure::router::preeval::tests::Position.all(%2023-01-01)->groupBy([], ^meta::pure::functions::collection::AggregateValue(mapFn={x:meta::pure::router::preeval::tests::Position[1] | $x.client(%2023-01-01).name + $x->toString()},aggregateFn={y:String[*]|$y->count()}), ['ID Count']) + }; + + assertRoundTrip($newLambda, $expected); +} + +function <> meta::pure::router::preeval::tests::testDecimalType():Boolean[1] +{ + let input = {|meta::pure::router::preeval::tests::FinancialInstrument.all() + ->project([ + col(f:meta::pure::router::preeval::tests::FinancialInstrument[1]|$f.price, 'price'), + col(f:meta::pure::router::preeval::tests::FinancialInstrument[1]|parseDecimal('3.14D'), 'decimal_constant_col') + ]) + }; + + let expected = {|meta::pure::router::preeval::tests::FinancialInstrument.all() + ->project([ + ^BasicColumnSpecification(name = 'price', func={f:meta::pure::router::preeval::tests::FinancialInstrument[1]| $f.price }), + ^BasicColumnSpecification(name = 'decimal_constant_col', func={f:meta::pure::router::preeval::tests::FinancialInstrument[1]| 3.14}) + ]) + }; + + assertRoundTrip($input,$expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_castEmptyCollection():Boolean[1] +{ + let input = {b:Integer[*]|$b->uniqueValueOnly()}; + + let expected = {b:Integer[*]|if($b->distinct()->size() == 1, {|$b->max()}, {|[]})}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testEvalWithArgs1():Boolean[1] +{ + let input = {| + {x:Integer[1]|$x+1}->eval(5); + }; + + let expected = {|6}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testEvalWithArgs2():Boolean[1] +{ + let input = {|{t:TabularDataSet[1]|$t->filter(r:TDSRow[1]|$r.getString('firstName') == 'firstName')} + ->eval({|meta::pure::router::preeval::tests::Person.all()->project(col(p:meta::pure::router::preeval::tests::Person[1]|$p.firstName, 'firstName'))}->eval())}; + + let expected = {|meta::pure::router::preeval::tests::Person.all() + ->project(^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName})) + ->filter(r:TDSRow[1]|$r.getString('firstName') == 'firstName') + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testEvalWithArgs3():Boolean[1] +{ + let input = {a:Integer[1]|{b:Integer[1]|$b+1}->eval($a)}; + + let expected = {a:Integer[1]| $a+1 }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testProjectWithInferredParameterType():Boolean[1] +{ + let input = {|meta::pure::router::preeval::tests::myFunc888()}; + + let expected = {| meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age}) + ]) + }; + + assertRoundTrip($input, $expected); +} + +function meta::pure::router::preeval::tests::myFunc888() : TabularDataSet[1] +{ + meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age') + ]) +} + + + + +function <> meta::pure::router::preeval::tests::testPreroutingRemoveUnnecessaryStatements():Boolean[1] +{ + let input = {| + range(5)->map(x|$x+1)->map(index|'i' + $index->toString()); + 123; + }; + + let expected = {| + 123; + }; + + assertRoundTrip($input, $expected); +} + + +function meta::pure::router::preeval::tests::myStopFunction(b: Integer[1]) : Integer[1] +{ + $b+1; +} + +function <> meta::pure::router::preeval::tests::testAdditionalStopFunction():Boolean[1] +{ + let input = {a:Integer[1]|myStopFunction($a)}; + + let expected = {a:Integer[1]| $a+1 }; + + assertRoundTrip($input, $expected); + assertRoundTrip($input, $input, myStopFunction_Integer_1__Integer_1_, [], noDebug()); +} + +function <> meta::pure::router::preeval::tests::testFilterFalseSimplification():Boolean[1] +{ + let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | false).id}; + + let expected = {a:OrganizationalEntity[1]| []}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testFilterFalseSimplificationAll():Boolean[1] +{ + let input = {s:String[0..1]| OrganizationalEntity.all(%2023-01-01)->filter(c | 1 == 2)->project(c | $c.name, 'name')}; + + let expected = {s:String[0..1]| OrganizationalEntity.all(%2023-01-01)->filter(c | false)->project(c | $c.name, 'name')}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testFilterFalseSimplification2():Boolean[1] +{ + let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | 1 == 2).id}; + + let expected = {o:OrganizationalEntity[1]| []}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testFilterFalseConstantSimplification():Boolean[1] +{ + let input = {o:OrganizationalEntity[1]| let bool = false; let date = %2023-01-01; $o.positions($date)->filter(p | $bool).id;}; + + let expected = {o:OrganizationalEntity[1]| []}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testFilterFalseSimplificationReturnType():Boolean[1] +{ + let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | false).id;}; + let result = $input->preval([]); + + assertEquals(Integer, $result->functionReturnType().rawType); +} + +function <> meta::pure::router::preeval::tests::testFilterTrueSimplification():Boolean[1] +{ + let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | false; true;).id}; + + let expected = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01).id}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testFilterTrueSimplification2():Boolean[1] +{ + let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | 1 == 1).id}; + + let expected = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01).id}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testFilterTrueConstantSimplification():Boolean[1] +{ + let input = {o:OrganizationalEntity[1]| let bool = true; let date = %2023-01-01; $o.positions($date)->filter(p | $bool).id;}; + + let expected = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01).id}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testFilterNoSimplification():Boolean[1] +{ + let input = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | $p.id == true).id}; + + let expected = {o:OrganizationalEntity[1]| $o.positions(%2023-01-01)->filter(p | $p.id == true).id}; + + assertRoundTrip($input, $expected); +} + + +function <> meta::pure::router::preeval::tests::testToOneManyElimination1():Boolean[1] +{ + let input = {| 'hello'->toOneMany()}; + + let expected = {| 'hello'}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testToOneManyElimination2():Boolean[1] +{ + let input = {x:String[*]| $x->toOneMany()}; + + let expected = $input; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testToOneManyElimination3():Boolean[1] +{ + let input = {x:String[1..*]| $x->toOneMany()}; + + let expected = {x:String[1..*]| $x}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testToOneElimination1():Boolean[1] +{ + let input = {| 'hello'->toOne()}; + + let expected = {| 'hello'}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testToOneElimination2():Boolean[1] +{ + let input = {x:String[*]| $x->toOne()}; + + let expected = $input; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testToOneElimination3():Boolean[1] +{ + let input = {x:String[1]| $x->toOne()}; + + let expected = {x:String[1]| $x}; + + assertRoundTrip($input, $expected); +} + + +function <> meta::pure::router::preeval::tests::testRecursiveSimpleConcreteFunctionDefinition():Boolean[1] +{ + // This is specially selected to be a single function that is recursive on itself + // The test is to validate that we don't keep trying to inline ad infinitum + + let input = {s:Class[1]|meta::pure::functions::meta::hierarchicalProperties($s)}; + + let expected = {s:Class[1]| + if($s == Any, {|[]}, {|$s.properties->concatenate($s.generalizations->map({g|$g.general.rawType->cast(Any) + ->toOne() + ->hierarchicalProperties()})) + ->removeDuplicates([], [])}) + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testLetOnlyStatement():Boolean[1] +{ + let input = {|myFunc2()}; + + let expected = {| 'hello'}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testLambdaParamOverride():Boolean[1] +{ + let input = { | let val = 1; + {val:Integer[1]| $val};}; + + let expected = { | {val:Integer[1]| $val};}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testSchemaStateUnfurl():Boolean[1] +{ + let state = ^meta::pure::tds::schema::SchemaState(columns=[^meta::pure::tds::TDSColumn(name='column1'), ^meta::pure::tds::TDSColumn(name='column2')]); + let input = {|$state.columns.name}; + let expected = {|['column1', 'column2']}; + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testLambdaParamOverride2():Boolean[1] +{ + let val = 1; + let input = { | let x = 1; + {p:Integer[1]|$p->map(x|$x+$val)};}; + let expected = { | {p:Integer[1]|$p->map(x|$x+1)}}; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::myFunc2():String[1] +{ + let x = 'hello'; +} + +function meta::pure::router::preeval::tests::getTestRuntimeWithModelConnection(sourceClass:Class[1], inputs:Any[*]):Runtime[1] +{ + // Need element as string to allow roundtrip to pass. When using ^ModelStore(), preeval evaluates it to a instance, however expected as it as a new function + ^Runtime(connections=^meta::pure::mapping::modelToModel::ModelConnection(element = 'DummyStore', instances=newMap(pair($sourceClass, list($inputs))))); +} + +function <> meta::pure::router::preeval::tests::assertRoundTrip(input : FunctionDefinition[1], expectedOrig : FunctionDefinition[1]):Boolean[1] +{ + assertRoundTrip($input, $expectedOrig, noDebug()) +} + +function <> meta::pure::router::preeval::tests::assertRoundTrip(input : FunctionDefinition[1], expectedOrig : FunctionDefinition[1], debug:meta::pure::tools::DebugContext[1]):Boolean[1] +{ + meta::pure::router::preeval::tests::assertRoundTrip($input, $expectedOrig, [], [], $debug); +} + +function <> meta::pure::router::preeval::tests::assertRoundTrip(input : FunctionDefinition[1], expectedOrig : FunctionDefinition[1], stopInlineFunctions:Function[*], extensions:Extension[*], debug:meta::pure::tools::DebugContext[1]):Boolean[1] +{ + let expected = $expectedOrig; + + let result = if($stopInlineFunctions->isEmpty(), + | $input->preval(newMap([]->cast(@Pair)), $input->openVariableValues(), [], $debug).value->toOne(); , + | let inScopeVars = $input->openVariableValues(); + let state = meta::pure::router::preeval::getPreevalStateWithAdditionalStopInlineFunc($inScopeVars, $extensions, $stopInlineFunctions); + $input->preval($state, $extensions).value->toOne();); + + let tExpected = $expected->transformFunctionBody($extensions); + let tResult = $result->transformFunctionBody($extensions); + + let jExpected = $tExpected->toJSON(50000); + let jResult = $tResult->toJSON(50000); + + let pjExpected = $jExpected->parseJSON()->toPrettyJSONString(); + let pjResult = $jResult->parseJSON()->toPrettyJSONString(); + + if($pjExpected == $pjResult, + | true, + | + println($pjExpected); + println($pjResult); + + let pExpected = $tExpected->toPure($extensions); + let pResult = $tResult->toPure($extensions); + + assertEquals($pExpected, $pResult, 'Mismatch between expected and actual result content.\nexpected pure:\n%r\n\nactual pure:\n%r', [$pExpected->toOne(), $pResult->toOne()]); + ); + + // Open variables on lambdas should have been inlined / eliminated + if (!$result->instanceOf(LambdaFunction), + | true, + | assertEmpty($result->cast(@LambdaFunction).openVariables) + ); + + if($input->cast(@Function)->functionType().parameters->isNotEmpty(), + | true, + | + let inputEvalResult = $input->evaluate([]); + let expectedEvalResult = $expected->evaluate([]); + let resultEvalResult = $result->evaluate([]); + + let open = $input->openVariableValues(); + + //println($input->cast(@Function)->functionReturnMultiplicity()); + + let mInput = $input->cast(@Function)->functionReturnMultiplicity(); + let mExpected = $expected->cast(@Function)->functionReturnMultiplicity(); + let mResult = $result->cast(@Function)->functionReturnMultiplicity(); + + let jInputEvalResult = $inputEvalResult->transformAny([], $open, $mExpected, $extensions)->toJSON(50000); + let jExpectedEvalResult = $expectedEvalResult->transformAny([], $open, $mExpected, $extensions)->toJSON(50000); + println($jExpectedEvalResult); + let jResultEvalResult = $resultEvalResult->transformAny([], $open, $mExpected, $extensions)->toJSON(50000); + + + assertEquals($jExpectedEvalResult, $jResultEvalResult, '\nexpected eval result: %r\nactual eval result: %r', [$jExpectedEvalResult->toOne(), $jResultEvalResult->toOne()]); + + if(!$inputEvalResult->exists(x|$x->match([b:BasicColumnSpecification[*]|true, a:Any[*]|false])), + | assertEquals($jInputEvalResult, $jExpectedEvalResult, '\nexpected input eval: %r\nactual expected eval: %r', [$jInputEvalResult->toOne(), $jExpectedEvalResult->toOne()]), + | true //The lambda on the BCS can get confused for assertion checking (as it can be "un-optimised") + ); + ); + + true; } \ No newline at end of file diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/router/tests/testPreeval.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/router/tests/testPreeval.pure index 53db20dcb43..97c41e40d88 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/router/tests/testPreeval.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/router/tests/testPreeval.pure @@ -1,134 +1,132 @@ -// Copyright 2023 Goldman Sachs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Press F9 to execute the 'go' function... -// Press F10 to run the full test suite - -import meta::pure::extension::*; -import meta::pure::router::preeval::*; -import meta::pure::router::preeval::tests::*; -import meta::external::format::shared::binding::*; -import meta::external::format::shared::functions::*; -import meta::json::*; -import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::*; -import meta::protocols::pure::vX_X_X::transformation::toPureGrammar::*; -import apps::protocols::tds::preeval::router::tests::prerouting::*; -import meta::pure::graphFetch::execution::*; -import meta::pure::mapping::*; -import meta::pure::mapping::modelToModel::test::simple::*; -import meta::pure::mapping::modelToModel::test::shared::dest::*; -import meta::pure::mapping::modelToModel::test::shared::src::*; -import meta::pure::runtime::*; - -function <> meta::pure::router::preeval::tests::testPrerouting41():Boolean[1] -{ - let runtime = ^Runtime(); - let context = ^ExecutionContext(); - - let input = {| - meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName') - ])->from(meta::relational::tests::simpleRelationalMapping, $runtime, $context)->join( - meta::pure::router::preeval::tests::Person.all() - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName') - ])->from(meta::relational::tests::simpleRelationalMapping, $runtime), - meta::relational::metamodel::join::JoinType.LEFT_OUTER, 'firstName') - }; - - let expected = {| - meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}) - ])->from(meta::relational::tests::simpleRelationalMapping, $runtime, $context)->join( - meta::pure::router::preeval::tests::Person.all() - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}) - ])->from(meta::relational::tests::simpleRelationalMapping, $runtime), - meta::relational::metamodel::join::JoinType.LEFT_OUTER, 'firstName', 'firstName') - }; - - assertRoundTrip($input, $expected); -} - -function <> meta::pure::router::preeval::tests::testPrerouting42():Boolean[1] -{ - let runtime = ^Runtime(); - - let input = {name:String[1]| - let person = meta::pure::router::preeval::tests::Person.all()->toOne()->graphFetch( - #{ - meta::pure::router::preeval::tests::Person{ - firstName - } - }# - ); - - let person2 = meta::pure::router::preeval::tests::Person.all()->toOne()->graphFetch( - #{ - meta::pure::router::preeval::tests::Person{ - firstName - } - }# - )->from(meta::relational::tests::simpleRelationalMapping, $runtime); - - meta::pure::router::preeval::tests::Person.all() - ->filter(x | ($x.firstName == $person.firstName) || ($x.firstName == $name) || $x.firstName == $person2.firstName) - ->project([ - col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName'), - col({p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}, 'lastName'), - col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age'), - col({p:meta::pure::router::preeval::tests::Person[1]|today()}, 'today') - ]); - }; - - let expected = {name:String[1]| - - let person = meta::pure::router::preeval::tests::Person.all()->toOne()->graphFetch( - #{ - meta::pure::router::preeval::tests::Person{ - firstName - } - }# - ); - - let person2 = meta::pure::router::preeval::tests::Person.all()->toOne()->graphFetch( - #{ - meta::pure::router::preeval::tests::Person{ - firstName - } - }# - )->from(meta::relational::tests::simpleRelationalMapping, $runtime); - - - meta::pure::router::preeval::tests::Person.all() - ->filter(x | ($x.firstName == $person.firstName) || ($x.firstName == $name) || $x.firstName == $person2.firstName) - ->project([ - ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}), - ^BasicColumnSpecification(name = 'lastName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}), - ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age}), - ^BasicColumnSpecification(name = 'today', func = {p:meta::pure::router::preeval::tests::Person[1]|today()}) - ]); - }; - - assertRoundTrip($input, $expected, noDebug()); -} - -function <> meta::pure::router::preeval::tests::testPrerouting_Store():Boolean[1] -{ - let input = {| meta::relational::tests::db}; - let expected = {| meta::relational::tests::db}; - assertRoundTrip($input, $expected); +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Press F9 to execute the 'go' function... +// Press F10 to run the full test suite + +import meta::pure::extension::*; +import meta::pure::router::preeval::*; +import meta::pure::router::preeval::tests::*; +import meta::json::*; +import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::*; +import meta::protocols::pure::vX_X_X::transformation::toPureGrammar::*; +import apps::protocols::tds::preeval::router::tests::prerouting::*; +import meta::pure::graphFetch::execution::*; +import meta::pure::mapping::*; +import meta::pure::mapping::modelToModel::test::simple::*; +import meta::pure::mapping::modelToModel::test::shared::dest::*; +import meta::pure::mapping::modelToModel::test::shared::src::*; +import meta::pure::runtime::*; + +function <> meta::pure::router::preeval::tests::testPrerouting41():Boolean[1] +{ + let runtime = ^Runtime(); + let context = ^ExecutionContext(); + + let input = {| + meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName') + ])->from(meta::relational::tests::simpleRelationalMapping, $runtime, $context)->join( + meta::pure::router::preeval::tests::Person.all() + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName') + ])->from(meta::relational::tests::simpleRelationalMapping, $runtime), + meta::relational::metamodel::join::JoinType.LEFT_OUTER, 'firstName') + }; + + let expected = {| + meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}) + ])->from(meta::relational::tests::simpleRelationalMapping, $runtime, $context)->join( + meta::pure::router::preeval::tests::Person.all() + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}) + ])->from(meta::relational::tests::simpleRelationalMapping, $runtime), + meta::relational::metamodel::join::JoinType.LEFT_OUTER, 'firstName', 'firstName') + }; + + assertRoundTrip($input, $expected); +} + +function <> meta::pure::router::preeval::tests::testPrerouting42():Boolean[1] +{ + let runtime = ^Runtime(); + + let input = {name:String[1]| + let person = meta::pure::router::preeval::tests::Person.all()->toOne()->graphFetch( + #{ + meta::pure::router::preeval::tests::Person{ + firstName + } + }# + ); + + let person2 = meta::pure::router::preeval::tests::Person.all()->toOne()->graphFetch( + #{ + meta::pure::router::preeval::tests::Person{ + firstName + } + }# + )->from(meta::relational::tests::simpleRelationalMapping, $runtime); + + meta::pure::router::preeval::tests::Person.all() + ->filter(x | ($x.firstName == $person.firstName) || ($x.firstName == $name) || $x.firstName == $person2.firstName) + ->project([ + col({p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}, 'firstName'), + col({p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}, 'lastName'), + col({p:meta::pure::router::preeval::tests::Person[1]|$p.age}, 'age'), + col({p:meta::pure::router::preeval::tests::Person[1]|today()}, 'today') + ]); + }; + + let expected = {name:String[1]| + + let person = meta::pure::router::preeval::tests::Person.all()->toOne()->graphFetch( + #{ + meta::pure::router::preeval::tests::Person{ + firstName + } + }# + ); + + let person2 = meta::pure::router::preeval::tests::Person.all()->toOne()->graphFetch( + #{ + meta::pure::router::preeval::tests::Person{ + firstName + } + }# + )->from(meta::relational::tests::simpleRelationalMapping, $runtime); + + + meta::pure::router::preeval::tests::Person.all() + ->filter(x | ($x.firstName == $person.firstName) || ($x.firstName == $name) || $x.firstName == $person2.firstName) + ->project([ + ^BasicColumnSpecification(name = 'firstName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.firstName}), + ^BasicColumnSpecification(name = 'lastName', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.lastName}), + ^BasicColumnSpecification(name = 'age', func = {p:meta::pure::router::preeval::tests::Person[1]|$p.age}), + ^BasicColumnSpecification(name = 'today', func = {p:meta::pure::router::preeval::tests::Person[1]|today()}) + ]); + }; + + assertRoundTrip($input, $expected, noDebug()); +} + +function <> meta::pure::router::preeval::tests::testPrerouting_Store():Boolean[1] +{ + let input = {| meta::relational::tests::db}; + let expected = {| meta::relational::tests::db}; + assertRoundTrip($input, $expected); } \ No newline at end of file