diff --git a/cli/src/Morphir/Elm/CLI.elm b/cli/src/Morphir/Elm/CLI.elm index 3bfd6f1f7..eedd004db 100644 --- a/cli/src/Morphir/Elm/CLI.elm +++ b/cli/src/Morphir/Elm/CLI.elm @@ -37,7 +37,7 @@ import Morphir.IR.Type exposing (Type) import Morphir.IR.Value as Value import Morphir.SDK.ResultList as ResultList import Morphir.Type.Infer as Infer -import Morphir.Value.Interpreter exposing (evaluateFunctionValue) +import Morphir.Value.Interpreter exposing (complete, evaluateFunctionValue) port packageDefinitionFromSource : (( Decode.Value, Decode.Value, List SourceFile ) -> msg) -> Sub msg @@ -217,7 +217,7 @@ update msg model = testCaseList |> List.map (\testCase -> - case evaluateFunctionValue SDK.nativeFunctions ir functionName testCase.inputs of + case evaluateFunctionValue complete SDK.nativeFunctions ir functionName testCase.inputs of Ok rawValue -> if rawValue == testCase.expectedOutput then ( "PASS" diff --git a/cli/src/Morphir/Web/DevelopApp.elm b/cli/src/Morphir/Web/DevelopApp.elm index cb4fa4739..d4f935ad9 100644 --- a/cli/src/Morphir/Web/DevelopApp.elm +++ b/cli/src/Morphir/Web/DevelopApp.elm @@ -73,7 +73,7 @@ import Morphir.SDK.Dict as SDKDict import Morphir.TestCoverage.Backend exposing (getBranchCoverage, getValueBranchCoverage) import Morphir.Type.Infer as Infer import Morphir.Value.Error exposing (Error) -import Morphir.Value.Interpreter exposing (evaluateFunctionValue) +import Morphir.Value.Interpreter exposing (complete, evaluateFunctionValue) import Morphir.Visual.Common exposing (nameToText, nameToTitleText, pathToDisplayString, pathToFullUrl, pathToTitleText, pathToUrl, tooltip) import Morphir.Visual.Components.Card as Card import Morphir.Visual.Components.FieldList as FieldList @@ -2218,7 +2218,7 @@ viewDefinitionDetails model = evaluateOutput : Distribution -> List (Maybe RawValue) -> FQName -> Result Error RawValue evaluateOutput ir inputs fQName = - evaluateFunctionValue SDK.nativeFunctions ir fQName inputs + evaluateFunctionValue complete SDK.nativeFunctions ir fQName inputs viewRawValue : Morphir.Visual.Config.Config Msg -> Distribution -> RawValue -> Element Msg viewRawValue config ir rawValue = diff --git a/morphir.json b/morphir.json index 9d37d2444..c5196babb 100644 --- a/morphir.json +++ b/morphir.json @@ -11,6 +11,7 @@ "IR.Value", "IR.Module", "IR.Package", - "IR.Distribution" + "IR.Distribution", + "Value.Interpreter" ] } diff --git a/src/Morphir/Correctness/BranchCoverage.elm b/src/Morphir/Correctness/BranchCoverage.elm index 019d737e6..54615aaea 100644 --- a/src/Morphir/Correctness/BranchCoverage.elm +++ b/src/Morphir/Correctness/BranchCoverage.elm @@ -305,7 +305,7 @@ assignTestCasesToBranches ir valueDef testCases = rawCriterion = cond.criterion |> Value.mapValueAttributes (always ()) (always ()) in - case Interpreter.evaluateValue SDK.nativeFunctions ir variables [] rawCriterion of + case Interpreter.evaluateValue Interpreter.complete SDK.nativeFunctions ir variables [] rawCriterion of Ok (Value.Literal _ (BoolLiteral actualResult)) -> if cond.expectedValue == actualResult then matchesConditions restOfConditions @@ -324,7 +324,7 @@ assignTestCasesToBranches ir valueDef testCases = evaluatedSubject : RawValue evaluatedSubject = - case Interpreter.evaluateValue SDK.nativeFunctions ir variables [] rawSubject of + case Interpreter.evaluateValue Interpreter.complete SDK.nativeFunctions ir variables [] rawSubject of Ok result -> result diff --git a/src/Morphir/Value/Interpreter.elm b/src/Morphir/Value/Interpreter.elm index 1f5a02f7f..a293f6fca 100644 --- a/src/Morphir/Value/Interpreter.elm +++ b/src/Morphir/Value/Interpreter.elm @@ -1,10 +1,14 @@ -module Morphir.Value.Interpreter exposing (evaluate, evaluateValue, evaluateFunctionValue, matchPattern, Variables) +module Morphir.Value.Interpreter exposing + ( evaluate, evaluateValue, evaluateFunctionValue, matchPattern, Variables + , Config, complete, partial + ) {-| This module contains an interpreter for Morphir expressions. The interpreter takes a piece of logic as input, evaluates it and returns the resulting data. In Morphir both logic and data is captured as a `Value` so the interpreter takes a `Value` and returns a `Value` (or an error for invalid expressions): @docs evaluate, evaluateValue, evaluateFunctionValue, matchPattern, Variables +@docs Config, complete, partial -} @@ -26,16 +30,43 @@ type alias Variables = Dict Name RawValue +{-| Configuration for the interpreter + + - allowPartials: When turned on, allows partial evaluation of a Value. + +-} +type alias Config = + { allowPartial : Bool + } + + +{-| Configuration for complete evaluation +-} +complete : Config +complete = + { allowPartial = False + } + + +{-| Configuration for partial evaluation +-} +partial : Config +partial = + { allowPartial = True + } + + {-| -} -evaluateFunctionValue : Dict FQName Native.Function -> Distribution -> FQName -> List (Maybe RawValue) -> Result Error RawValue -evaluateFunctionValue nativeFunctions ir fQName variableValues = +evaluateFunctionValue : Config -> Dict FQName Native.Function -> Distribution -> FQName -> List (Maybe RawValue) -> Result Error RawValue +evaluateFunctionValue config nativeFunctions ir fQName variableValues = ir |> Distribution.lookupValueDefinition fQName -- If we cannot find the value in the IR we return an error. |> Result.fromMaybe (ReferenceNotFound fQName) |> Result.andThen (\valueDef -> - evaluateValue nativeFunctions + evaluateValue config + nativeFunctions ir (List.map2 Tuple.pair (valueDef.inputTypes @@ -63,14 +94,15 @@ by fully-qualified name that will be used for lookup if the expression contains -} evaluate : Dict FQName Native.Function -> Distribution -> RawValue -> Result Error RawValue evaluate nativeFunctions ir value = - evaluateValue nativeFunctions ir Dict.empty [] value + -- do not permit partial evaluation + evaluateValue complete nativeFunctions ir Dict.empty [] value {-| Evaluates a value expression recursively in a single pass while keeping track of variables and arguments along the evaluation. -} -evaluateValue : Dict FQName Native.Function -> Distribution -> Variables -> List RawValue -> RawValue -> Result Error RawValue -evaluateValue nativeFunctions ir variables arguments value = +evaluateValue : Config -> Dict FQName Native.Function -> Distribution -> Variables -> List RawValue -> RawValue -> Result Error RawValue +evaluateValue ({ allowPartial } as config) nativeFunctions ir variables arguments value = case value of Value.Literal _ _ -> -- Literals cannot be evaluated any further @@ -78,7 +110,7 @@ evaluateValue nativeFunctions ir variables arguments value = Value.Constructor _ fQName -> arguments - |> List.map (evaluateValue nativeFunctions ir variables []) + |> List.map (evaluateValue config nativeFunctions ir variables []) -- If any of those fails we return the first failure. |> ResultList.keepFirstError |> Result.andThen @@ -112,7 +144,7 @@ evaluateValue nativeFunctions ir variables arguments value = -- For a tuple we need to evaluate each element and return them wrapped back into a tuple elems -- We evaluate each element separately. - |> List.map (evaluateValue nativeFunctions ir variables []) + |> List.map (evaluateValue config nativeFunctions ir variables []) -- If any of those fails we return the first failure. |> ResultList.keepFirstError -- If nothing fails we wrap the result in a tuple. @@ -122,7 +154,7 @@ evaluateValue nativeFunctions ir variables arguments value = -- For a list we need to evaluate each element and return them wrapped back into a list items -- We evaluate each element separately. - |> List.map (evaluateValue nativeFunctions ir variables []) + |> List.map (evaluateValue config nativeFunctions ir variables []) -- If any of those fails we return the first failure. |> ResultList.keepFirstError -- If nothing fails we wrap the result in a list. @@ -135,7 +167,7 @@ evaluateValue nativeFunctions ir variables arguments value = -- We evaluate each field separately. |> List.map (\( fieldName, fieldValue ) -> - evaluateValue nativeFunctions ir variables [] fieldValue + evaluateValue config nativeFunctions ir variables [] fieldValue |> Result.map (Tuple.pair fieldName) ) -- If any of those fails we return the first failure. @@ -152,29 +184,47 @@ evaluateValue nativeFunctions ir variables arguments value = -- Wrap the error to make it easier to understand where it happened |> Result.mapError (ErrorWhileEvaluatingVariable varName) - Value.Reference _ (( packageName, moduleName, localName ) as fQName) -> + Value.Reference _ fQName -> -- We check if there is a native function first - case nativeFunctions |> Dict.get ( packageName, moduleName, localName ) of + case nativeFunctions |> Dict.get fQName of Just nativeFunction -> - nativeFunction - (evaluateValue - -- This is the state that will be used when native functions call "eval". - -- We need to retain most of the current state but clear out the argument since - -- the native function will evaluate completely new expressions. - nativeFunctions - ir - variables - [] - ) - -- Pass down the arguments we collected before we got here (if we are inside an apply). - arguments - -- Wrap the error to make it easier to understand where it happened - |> Result.mapError (ErrorWhileEvaluatingReference fQName) + let + result = + nativeFunction + (evaluateValue config + -- This is the state that will be used when native functions call "eval". + -- We need to retain most of the current state but clear out the argument since + -- the native function will evaluate completely new expressions. + nativeFunctions + ir + variables + [] + ) + -- Pass down the arguments we collected before we got here (if we are inside an apply). + arguments + -- Wrap the error to make it easier to understand where it happened + |> Result.mapError (ErrorWhileEvaluatingReference fQName) + in + case result of + Ok _ -> + result + + Err _ -> + -- We wrap the arguments to the native functions in an Apply + -- if the native function failed evaluation + if allowPartial then + arguments + |> List.map (evaluateValue config nativeFunctions ir variables []) + |> ResultList.keepFirstError + |> Result.map (List.foldl (\arg target -> Value.Apply () target arg) value) + + else + result Nothing -> arguments |> List.map - (evaluateValue + (evaluateValue config nativeFunctions ir variables @@ -183,12 +233,12 @@ evaluateValue nativeFunctions ir variables arguments value = |> ResultList.keepFirstError -- If this is a reference to another Morphir value we need to look it up and evaluate. |> Result.map (\resultList -> List.map (\result -> Just result) resultList) - |> Result.andThen (evaluateFunctionValue nativeFunctions ir fQName) + |> Result.andThen (evaluateFunctionValue config nativeFunctions ir fQName) Value.Field _ subjectValue fieldName -> -- Field selection is evaluated by evaluating the subject first then matching on the resulting record and -- getting the field with the specified name. - evaluateValue nativeFunctions ir variables [] subjectValue + evaluateValue config nativeFunctions ir variables [] subjectValue |> Result.andThen (\evaluatedSubjectValue -> case evaluatedSubjectValue of @@ -197,8 +247,13 @@ evaluateValue nativeFunctions ir variables arguments value = |> Dict.get fieldName |> Result.fromMaybe (FieldNotFound subjectValue fieldName) - _ -> - Err (RecordExpected subjectValue evaluatedSubjectValue) + simplified -> + if allowPartial then + -- if we allow partial evaluation, wrap the field simplified subject with a Field value + Ok (Value.Field () simplified fieldName) + + else + Err (RecordExpected subjectValue evaluatedSubjectValue) ) Value.FieldFunction _ fieldName -> @@ -206,7 +261,7 @@ evaluateValue nativeFunctions ir variables arguments value = -- it behaves exactly like a `Field` expression. case arguments of [ subjectValue ] -> - evaluateValue nativeFunctions ir variables [] subjectValue + evaluateValue config nativeFunctions ir variables [] subjectValue |> Result.andThen (\evaluatedSubjectValue -> case evaluatedSubjectValue of @@ -216,7 +271,12 @@ evaluateValue nativeFunctions ir variables arguments value = |> Result.fromMaybe (FieldNotFound subjectValue fieldName) _ -> - Err (RecordExpected subjectValue evaluatedSubjectValue) + if allowPartial then + -- if we allow partial evaluation, wrap the field simplified subject with a Field value + Ok (Value.FieldFunction () fieldName) + + else + Err (RecordExpected subjectValue evaluatedSubjectValue) ) other -> @@ -227,7 +287,7 @@ evaluateValue nativeFunctions ir variables arguments value = -- When there are multiple arguments there will be another Apply within the function so arguments will be -- repeatedly collected until we hit another node (lambda, reference or variable) where the arguments will -- be used to execute the calculation. - evaluateValue + evaluateValue config nativeFunctions ir variables @@ -242,21 +302,24 @@ evaluateValue nativeFunctions ir variables arguments value = -- If there are no arguments then our expression was invalid so we return an error. |> Result.fromMaybe NoArgumentToPassToLambda -- If the argument is available we first need to match it against the argument pattern. - -- In Morphir (just like in Elm) you can not pattern-match on the argument of a lambda. + -- In Morphir (just like in Elm) you can pattern-match on the argument of a lambda. |> Result.andThen (\argumentValue -> - -- To match the pattern we call a helper function that both matches and extracts variables out - -- of the pattern. - matchPattern argumentPattern argumentValue - -- If the pattern does not match we error out. This should never happen with valid - -- expressions as lambda argument patterns should only be used for decomposition not - -- filtering. - |> Result.mapError LambdaArgumentDidNotMatch + evaluateValue config nativeFunctions ir variables [] argumentValue + -- To match the pattern we call a helper function that both matches and extracts variables out + -- of the pattern. + |> Result.andThen + (matchPattern argumentPattern + -- If the pattern does not match we error out. This should never happen with valid + -- expressions as lambda argument patterns should only be used for decomposition not + -- filtering. + >> Result.mapError LambdaArgumentDidNotMatch + ) ) -- Finally we evaluate the body of the lambda using the variables extracted by the pattern. |> Result.andThen (\argumentVariables -> - evaluateValue + evaluateValue config nativeFunctions ir (Dict.union argumentVariables variables) @@ -267,10 +330,10 @@ evaluateValue nativeFunctions ir variables arguments value = Value.LetDefinition _ defName def inValue -> -- We evaluate a let definition by first evaluating the definition, then assigning it to the variable name -- given in `defName`. Finally we evaluate the `inValue` passing in the new variable in the state. - evaluateValue nativeFunctions ir variables [] (Value.definitionToValue def) + evaluateValue config nativeFunctions ir variables [] (Value.definitionToValue def) |> Result.andThen (\defValue -> - evaluateValue + evaluateValue config nativeFunctions ir (variables |> Dict.insert defName defValue) @@ -286,7 +349,7 @@ evaluateValue nativeFunctions ir variables arguments value = defVariables = defs |> Dict.map (\_ def -> Value.definitionToValue def) in - evaluateValue + evaluateValue config nativeFunctions ir (Dict.union defVariables variables) @@ -296,11 +359,11 @@ evaluateValue nativeFunctions ir variables arguments value = Value.Destructure _ bindPattern bindValue inValue -> -- A destructure can be evaluated by evaluating the bind value, matching it against the bind pattern and -- finally evaluating the in value using the variables from the bind pattern. - evaluateValue nativeFunctions ir variables [] bindValue + evaluateValue config nativeFunctions ir variables [] bindValue |> Result.andThen (matchPattern bindPattern >> Result.mapError (BindPatternDidNotMatch bindValue)) |> Result.andThen (\bindVariables -> - evaluateValue + evaluateValue config nativeFunctions ir (Dict.union bindVariables variables) @@ -308,40 +371,138 @@ evaluateValue nativeFunctions ir variables arguments value = inValue ) - Value.IfThenElse _ condition thenBranch elseBranch -> - -- If then else evaluation is trivial: you evaluate the condition and depending on the result you evaluate - -- one of the branches - evaluateValue nativeFunctions ir variables [] condition - |> Result.andThen - (\conditionValue -> - case conditionValue of - Value.Literal _ (BoolLiteral conditionTrue) -> - let - branchToFollow : RawValue - branchToFollow = - if conditionTrue then - thenBranch - - else - elseBranch - in - evaluateValue nativeFunctions ir variables [] branchToFollow + (Value.IfThenElse _ condition thenBranch elseBranch) as ifThenElse -> + if allowPartial then + -- When allow partial is true, we first evaluate all the conditions. + -- We need to go through the evaluated conditions, looking for either a True + -- or a partially evaluated condition. If we find a True before a partially evaluated condition, we take + -- that branch. If we find a condition that was only partially evaluated first, then we evaluate that and + -- all other branches and preserve the IfThenElse structure with the evaluated branches and conditions. + -- If neither was found, we take the else branch. + let + flatten : RawValue -> List ( RawValue, RawValue ) -> ( List ( RawValue, RawValue ), RawValue ) + flatten v branchesSoFar = + case v of + Value.IfThenElse _ cond then_ else_ -> + ( cond, then_ ) + :: branchesSoFar + |> flatten else_ + + finalElse -> + ( branchesSoFar, finalElse ) + + ( flattened, finalElseBranch ) = + flatten ifThenElse [] + + chooseBranch : List ( RawValue, RawValue ) -> Result Error RawValue + chooseBranch conditionByBranches = + case conditionByBranches of + [] -> + -- We never encountered a partially evaluated condition or a True + -- evaluate the else branch + evaluateValue config nativeFunctions ir variables [] finalElseBranch + + ( Value.Literal _ (BoolLiteral True), branch ) :: _ -> + -- We met a True first, evaluate this branch + evaluateValue config nativeFunctions ir variables [] branch + + ( Value.Literal _ (BoolLiteral False), _ ) :: rest -> + -- Unreachable branch, skip entirely and continue looking + -- for True or a partially evaluated condition + chooseBranch rest _ -> - Err (IfThenElseConditionShouldEvaluateToBool condition conditionValue) - ) + -- We encountered a partially evaluated condition, + -- evaluate all branches and wrap with an IfThenElse + -- TODO we could remove all the unreachable branches + conditionByBranches + |> -- starting from the tail, chain the values into an IfThenElse + List.foldr + (\( cond, branch ) -> + Result.map2 (Value.IfThenElse () cond) + (evaluateValue config nativeFunctions ir variables [] branch) + ) + (evaluateValue config nativeFunctions ir variables [] finalElseBranch) + in + flattened + |> List.reverse + |> List.map + (\( cond, branch ) -> + -- evaluate all the conditions + evaluateValue config nativeFunctions ir variables [] cond + |> Result.map (\evaluatedCond -> ( evaluatedCond, branch )) + ) + -- If any of those fails we return the first failure. + |> ResultList.keepFirstError + |> Result.andThen chooseBranch + + else + -- When allow partial is false, If-then-else evaluation becomes trivial: you evaluate the condition + -- and depending on the result you evaluate one of the branches + evaluateValue config nativeFunctions ir variables [] condition + |> Result.andThen + (\conditionValue -> + case conditionValue of + Value.Literal _ (BoolLiteral conditionTrue) -> + let + branchToFollow : RawValue + branchToFollow = + if conditionTrue then + thenBranch + + else + elseBranch + in + evaluateValue config nativeFunctions ir variables [] branchToFollow + + _ -> + Err (IfThenElseConditionShouldEvaluateToBool condition conditionValue) + ) Value.PatternMatch _ subjectValue cases -> - -- For a pattern match we first need to evaluate the subject value then step through th cases, match - -- each pattern until we find a matching case and when we do evaluate the body + -- For a pattern match we first need to evaluate the subject value then step through the cases, match + -- each pattern until we find a matching case and when we do evaluate the body. + -- However, when partial evaluation is turned on and no case match, we evaluate the body of all branches + -- and wrap it back into a PatternMatch. let + evaluatedSubjectResult = + evaluateValue config nativeFunctions ir variables [] subjectValue + + collectNewVars : Pattern () -> Variables + collectNewVars pattern = + case pattern of + Value.WildcardPattern _ -> + variables + + Value.AsPattern _ patt name -> + Dict.insert name (Value.Variable () name) variables + |> Dict.union (collectNewVars patt) + + Value.TuplePattern _ patterns -> + List.foldl (\pat vars -> collectNewVars pat |> Dict.union vars) variables patterns + + Value.ConstructorPattern _ _ patterns -> + List.foldl (\pat vars -> collectNewVars pat |> Dict.union vars) variables patterns + + Value.EmptyListPattern _ -> + variables + + Value.HeadTailPattern _ patt1 patt2 -> + Dict.union (collectNewVars patt1) variables |> Dict.union (collectNewVars patt2) + + Value.LiteralPattern _ _ -> + variables + + Value.UnitPattern _ -> + variables + findMatch : List ( Pattern (), RawValue ) -> RawValue -> Result Error RawValue findMatch remainingCases evaluatedSubject = case remainingCases of ( nextPattern, nextBody ) :: restOfCases -> case matchPattern nextPattern evaluatedSubject of Ok patternVariables -> - evaluateValue + evaluateValue config nativeFunctions ir (Dict.union patternVariables variables) @@ -354,13 +515,31 @@ evaluateValue nativeFunctions ir variables arguments value = [] -> Err (NoPatternsMatch evaluatedSubject (cases |> List.map Tuple.first)) in - evaluateValue nativeFunctions ir variables [] subjectValue - |> Result.andThen (findMatch cases) + evaluatedSubjectResult + -- if the subject value was not evaluated further and we allow partial evaluation, + -- then we exit with the entire evaluation with the + |> Result.andThen + (\evalSubject -> + if allowPartial then + cases + |> List.foldr + (\( patt, body ) evaluatedBranchesSoFar -> + Result.map2 (\evaluatedBody lst -> ( patt, evaluatedBody ) :: lst) + -- evaluate each body with any new variables from it's pattern added to the state + (evaluateValue config nativeFunctions ir (collectNewVars patt) [] body) + evaluatedBranchesSoFar + ) + (Ok []) + |> Result.map (Value.PatternMatch () evalSubject) + + else + findMatch cases evalSubject + ) Value.UpdateRecord _ subjectValue fieldUpdates -> -- To update a record first we need to evaluate the subject value, then extract the record fields and -- finally replace all updated fields with the new values - evaluateValue nativeFunctions ir variables [] subjectValue + evaluateValue config nativeFunctions ir variables [] subjectValue |> Result.andThen (\evaluatedSubjectValue -> case evaluatedSubjectValue of @@ -383,7 +562,7 @@ evaluateValue nativeFunctions ir variables arguments value = (\_ -> -- Before we replace the field value we need to -- evaluate the updated value. - evaluateValue nativeFunctions ir variables [] newFieldValue + evaluateValue config nativeFunctions ir variables [] newFieldValue |> Result.map (\evaluatedNewFieldValue -> fieldsSoFar @@ -398,8 +577,22 @@ evaluateValue nativeFunctions ir variables arguments value = (Ok fields) |> Result.map (Value.Record ()) - _ -> - Err (RecordExpected subjectValue evaluatedSubjectValue) + partiallyEvaluated -> + if allowPartial then + -- Evaluate all updates and wrap it into an UpdateRecord along with the partially + -- evaluated record subject + fieldUpdates + |> Dict.foldl + (\k v -> + Result.map2 + (Dict.insert k) + (evaluateValue config nativeFunctions ir variables [] v) + ) + (Ok Dict.empty) + |> Result.map (Value.UpdateRecord () partiallyEvaluated) + + else + Err (RecordExpected subjectValue evaluatedSubjectValue) ) Value.Unit _ -> @@ -409,6 +602,10 @@ evaluateValue nativeFunctions ir variables arguments value = {-| Matches a value against a pattern recursively. It either returns an error if there is a mismatch or a dictionary of variable names to values extracted out of the pattern. + +During partial evaluation, we accept the possibility of values that may not match the pattern in which case, we try to +extract variables regardless of matches. + -} matchPattern : Pattern () -> RawValue -> Result PatternMismatch Variables matchPattern pattern value = diff --git a/src/Morphir/Value/Native.elm b/src/Morphir/Value/Native.elm index 4a681f75f..8969563ab 100644 --- a/src/Morphir/Value/Native.elm +++ b/src/Morphir/Value/Native.elm @@ -462,9 +462,9 @@ encodeLocalDate localDate = {-| -} decodeLocalDate : Decoder LocalDate -decodeLocalDate _ value = - case value of - Value.Apply () (Value.Reference () ( [ [ "morphir" ], [ "s", "d", "k" ] ], [ [ "local", "date" ] ], [ "from", "i", "s", "o" ] )) (Value.Literal () (StringLiteral str)) -> +decodeLocalDate eval value = + case eval value of + Ok (Value.Apply () (Value.Constructor () ( [ [ "morphir" ], [ "s", "d", "k" ] ], [ [ "maybe" ] ], [ "just" ] )) (Value.Apply () (Value.Reference () ( [ [ "morphir" ], [ "s", "d", "k" ] ], [ [ "local", "date" ] ], [ "from", "i", "s", "o" ] )) (Value.Literal () (StringLiteral str)))) -> case LocalDate.fromISO str of Just localDate -> Ok localDate diff --git a/src/Morphir/Visual/Config.elm b/src/Morphir/Visual/Config.elm index 70aaa5022..cf58601c0 100644 --- a/src/Morphir/Visual/Config.elm +++ b/src/Morphir/Visual/Config.elm @@ -77,7 +77,7 @@ fromIR ir visualState eventHandlers = evaluate : RawValue -> Config msg -> Result String RawValue evaluate value config = - Interpreter.evaluateValue config.nativeFunctions config.ir config.state.variables [] value + Interpreter.evaluateValue Interpreter.complete config.nativeFunctions config.ir config.state.variables [] value |> Result.mapError (\error -> error diff --git a/src/Morphir/Visual/ViewApply.elm b/src/Morphir/Visual/ViewApply.elm index 8bdda216a..258f81f75 100644 --- a/src/Morphir/Visual/ViewApply.elm +++ b/src/Morphir/Visual/ViewApply.elm @@ -14,7 +14,7 @@ import Morphir.IR.Type as Type exposing (Type) import Morphir.IR.Value as Value exposing (RawValue, Value(..), toRawValue) import Morphir.Type.Infer as Infer import Morphir.Value.Error exposing (Error(..)) -import Morphir.Value.Interpreter exposing (evaluateFunctionValue, evaluateValue) +import Morphir.Value.Interpreter exposing (complete, evaluateFunctionValue, evaluateValue) import Morphir.Visual.Common exposing (nameToText, tooltip) import Morphir.Visual.Components.DecisionTree as DecisionTree import Morphir.Visual.Components.DrillDownPanel as DrillDownPanel exposing (Depth) diff --git a/tests-integration/reference-model/src/Morphir/Reference/Model/Values.elm b/tests-integration/reference-model/src/Morphir/Reference/Model/Values.elm index 778bec884..24370f7a5 100644 --- a/tests-integration/reference-model/src/Morphir/Reference/Model/Values.elm +++ b/tests-integration/reference-model/src/Morphir/Reference/Model/Values.elm @@ -93,6 +93,17 @@ basicRecordMany = , record = basicRecordOne } +type alias Rec = + { foo: Int + , bar: String + } + +calcFoo: Rec -> Int +calcFoo rec = + rec.foo + +res = calcFoo { foo = 2, bar = "hello" } + basicField : { foo : String } -> String basicField rec = @@ -495,3 +506,8 @@ listFoldr2 list1 list2 = listCons : Int -> List Int -> List Int listCons value list = value :: list + + +plus2: Int -> Int +plus2 int = + (\int1 int2 int3 -> int1 + int2 + int3) 1 2 int \ No newline at end of file diff --git a/tests/Morphir/Value/InterpreterTests.elm b/tests/Morphir/Value/InterpreterTests.elm index a92873965..8818ce0e1 100644 --- a/tests/Morphir/Value/InterpreterTests.elm +++ b/tests/Morphir/Value/InterpreterTests.elm @@ -1,16 +1,17 @@ module Morphir.Value.InterpreterTests exposing (..) -import Dict +import Dict exposing (Dict) import Expect import Morphir.IR.Distribution exposing (Distribution(..)) import Morphir.IR.FQName exposing (FQName, fqn) import Morphir.IR.Literal exposing (Literal(..)) import Morphir.IR.Module exposing (ModuleName) +import Morphir.IR.Name as Name exposing (Name) import Morphir.IR.Package as Package import Morphir.IR.QName as QName exposing (QName(..)) import Morphir.IR.SDK as SDK -import Morphir.IR.Value as Value -import Morphir.Value.Interpreter exposing (evaluate) +import Morphir.IR.Value as Value exposing (RawValue, Value) +import Morphir.Value.Interpreter exposing (evaluate, evaluateValue, partial) import Test exposing (Test, describe, test) @@ -1442,3 +1443,219 @@ evaluateValueTests = ) (Value.List () [ Value.Literal () (WholeNumberLiteral -2), Value.Literal () (WholeNumberLiteral -4), Value.Literal () (WholeNumberLiteral -6) ]) ] + + +evaluateValuePartialTests : Test +evaluateValuePartialTests = + let + name = + Name.fromString + + var = + Value.Variable () (name "var") + + int1 = + Value.Literal () (WholeNumberLiteral 1) + + vars : Dict Name RawValue + vars = + Dict.fromList + [ ( name "var", var ) + , ( name "int1", int1 ) + ] + + positiveCheck : String -> Value.RawValue -> Value.RawValue -> Test + positiveCheck desc input expectedOutput = + test desc + (\_ -> + evaluateValue partial SDK.nativeFunctions (Library [ [ "empty" ] ] Dict.empty Package.emptyDefinition) vars [] input + |> Expect.equal + (Ok expectedOutput) + ) + in + describe "evaluateValue partial" + [ positiveCheck "(\\val1 val2 val3 -> val1 + val2 + val3) 1 2 var == 3 + var" + (Value.Apply () + (Value.Apply () + (Value.Apply () + (Value.Lambda () + (Value.AsPattern () (Value.WildcardPattern ()) [ "val", "1" ]) + (Value.Lambda () + (Value.AsPattern () (Value.WildcardPattern ()) [ "val", "2" ]) + (Value.Lambda () + (Value.AsPattern () (Value.WildcardPattern ()) [ "val", "3" ]) + (Value.Apply () + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "Basics" "add")) + (Value.Apply () + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "Basics" "add")) + (Value.Variable () [ "val", "1" ]) + ) + (Value.Variable () [ "val", "2" ]) + ) + ) + (Value.Variable () [ "val", "3" ]) + ) + ) + ) + ) + (Value.Literal () (WholeNumberLiteral 1)) + ) + (Value.Literal () (WholeNumberLiteral 2)) + ) + var + ) + (Value.Apply () (Value.Apply () (Value.Reference () ( [ [ "morphir" ], [ "s", "d", "k" ] ], [ [ "basics" ] ], [ "add" ] )) (Value.Literal () (WholeNumberLiteral 3))) var) + , {- Basics.always -} + positiveCheck "List.map (always 0) [1,2,var] = [0,0,0]" + (Value.Apply () + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "List" "map")) + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "Basics" "always")) + (Value.Literal () (WholeNumberLiteral 0)) + ) + ) + (Value.List () [ Value.Literal () (WholeNumberLiteral 1), Value.Literal () (WholeNumberLiteral 2), var ]) + ) + (Value.List () [ Value.Literal () (WholeNumberLiteral 0), Value.Literal () (WholeNumberLiteral 0), Value.Literal () (WholeNumberLiteral 0) ]) + , positiveCheck "List.isEmpty var = List.isEmpty var" + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "List" "isEmpty")) + var + ) + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "List" "isEmpty")) + var + ) + , positiveCheck "List.head var = List.head var" + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "List" "head")) + var + ) + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "List" "head")) + var + ) + , positiveCheck "List.head [var,[\"2\"]] == Just var" + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "List" "head")) + (Value.List () [ var, Value.List () [ Value.Literal () (StringLiteral "2") ] ]) + ) + (Value.Apply () (Value.Constructor () (fqn "Morphir.SDK" "Maybe" "Just")) var) + , positiveCheck "List.concat [[\"1\"],[\"2\"],var] = List.concat [[\"1\"],[\"2\"],var]" + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "List" "concat")) + (Value.List () [ Value.List () [ Value.Literal () (StringLiteral "1") ], Value.List () [ Value.Literal () (StringLiteral "2") ], var ]) + ) + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "List" "concat")) + (Value.List () [ Value.List () [ Value.Literal () (StringLiteral "1") ], Value.List () [ Value.Literal () (StringLiteral "2") ], var ]) + ) + + {- IfThenElse -} + , positiveCheck "if int1 == 1 then var else False = var" + (Value.IfThenElse () + (Value.Apply () + (Value.Apply () (Value.Reference () (fqn "Morphir.SDK" "Basics" "equal")) int1) + (Value.Literal () (WholeNumberLiteral 1)) + ) + var + (Value.Literal () (BoolLiteral False)) + ) + var + , positiveCheck "if int1 == 1 then True else if var then False else True = if var then False else True" + (Value.IfThenElse () + (Value.Apply () + (Value.Apply () (Value.Reference () (fqn "Morphir.SDK" "Basics" "equal")) int1) + (Value.Literal () (WholeNumberLiteral 1)) + ) + (Value.Literal () (BoolLiteral True)) + (Value.IfThenElse () var (Value.Literal () (BoolLiteral False)) (Value.Literal () (BoolLiteral True))) + ) + (Value.Literal () (BoolLiteral True)) + , positiveCheck "if int1 == 2 then var else if var then False else True = if var then False else True" + (Value.IfThenElse () + (Value.Apply () + (Value.Apply () (Value.Reference () (fqn "Morphir.SDK" "Basics" "equal")) int1) + (Value.Literal () (WholeNumberLiteral 2)) + ) + var + (Value.IfThenElse () var (Value.Literal () (BoolLiteral False)) (Value.Literal () (BoolLiteral True))) + ) + (Value.IfThenElse () var (Value.Literal () (BoolLiteral False)) (Value.Literal () (BoolLiteral True))) + , positiveCheck "if int1 == 2 then var else if int1 == 3 then False else True = True" + (Value.IfThenElse () + (Value.Apply () + (Value.Apply () (Value.Reference () (fqn "Morphir.SDK" "Basics" "equal")) int1) + (Value.Literal () (WholeNumberLiteral 2)) + ) + var + (Value.IfThenElse () + (Value.Apply () + (Value.Apply () (Value.Reference () (fqn "Morphir.SDK" "Basics" "equal")) int1) + (Value.Literal () (WholeNumberLiteral 3)) + ) + (Value.Literal () (BoolLiteral False)) + (Value.Literal () (BoolLiteral True)) + ) + ) + (Value.Literal () (BoolLiteral True)) + , {- Value.PatternMatch -} + positiveCheck "case var of \"Foo\" -> int1 == 1 \"Bar\" -> False = case var of \"Foo\" -> True _ -> False" + (Value.PatternMatch () + var + [ ( Value.LiteralPattern () (StringLiteral "Foo") + , Value.Apply () (Value.Apply () (Value.Reference () (fqn "Morphir.SDK" "Basics" "equal")) int1) (Value.Literal () (WholeNumberLiteral 1)) + ) + , ( Value.LiteralPattern () (StringLiteral "Bar") + , Value.Literal () (BoolLiteral False) + ) + ] + ) + (Value.PatternMatch () + var + [ ( Value.LiteralPattern () (StringLiteral "Foo") + , Value.Literal () (BoolLiteral True) + ) + , ( Value.LiteralPattern () (StringLiteral "Bar") + , Value.Literal () (BoolLiteral False) + ) + ] + ) + , positiveCheck "case var of \"Foo\" -> int1 == 1 _ -> False = case var of \"Foo\" -> True _ -> False" + (Value.PatternMatch () + var + [ ( Value.LiteralPattern () (StringLiteral "Foo") + , Value.Apply () (Value.Apply () (Value.Reference () (fqn "Morphir.SDK" "Basics" "equal")) int1) (Value.Literal () (WholeNumberLiteral 1)) + ) + , ( Value.WildcardPattern () + , Value.Literal () (BoolLiteral False) + ) + ] + ) + (Value.PatternMatch () + var + [ ( Value.LiteralPattern () (StringLiteral "Foo") + , Value.Literal () (BoolLiteral True) + ) + , ( Value.WildcardPattern () + , Value.Literal () (BoolLiteral False) + ) + ] + ) + , {- Basics.identity -} + positiveCheck "identity var = var" + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "Basics" "identity")) + var + ) + var + , positiveCheck "identity [var,\"b\",\"123\"] = [var,\"b\",\"123\"]" + (Value.Apply () + (Value.Reference () (fqn "Morphir.SDK" "Basics" "identity")) + (Value.List () [ var, Value.Literal () (StringLiteral "b"), Value.Literal () (StringLiteral "123") ]) + ) + (Value.List () [ var, Value.Literal () (StringLiteral "b"), Value.Literal () (StringLiteral "123") ]) + ]