Inedo Community Forums Forums
    • Recent
    • Tags
    • Popular
    • Login

    Welcome to the Inedo Forums! Check out the Forums Guide for help getting started.

    If you are experiencing any issues with the forum software, please visit the Contact Form on our website and let us know!

    Suggestion: allow for setting list or map elements by dynamic index or key (@ListSet, %MapSet)

    Scheduled Pinned Locked Moved Support
    6 Posts 2 Posters 17 Views 1 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • J Offline
      jimbobmcgee
      last edited by

      It does not appear to be possible to set the value of a list or map element, where the index or key is stored in a variable.

      The formal grammar suggests...

      <assign_variable_statement> ::=
         set [local | global] ( /variable_expression/ | /indexed_expression/ ) = /literal_expression/;
      

      variable_expression:
      A variable type identifier ($, @, or %) immediately followed by one of:

      • simple name - follows same rules as any_name
      • explicit name - a left curly brace ({), followed by of characters with the same rules as any_name but that also allow spaces, followed by a right curly brace (})

      indexed_expression:
      A variable_expression for a vector (@) or map (%) type, immediately followed one of:

      • left bracket ([), scalar_expression, then right bracket (])
      • dot (.) then scalar_expression

      ...which implies that both...

      set %Map.$k = something;
      set @Vec[$i] = something;
      

      ...should be possible, but these throw various errors at the execution engine level, e.g.:

      Unhandled exception: System.InvalidCastException: Unable to cast object of type &#39;System.ArrayEnumerator&#39; to type &#39;System.Collections.Generic.IEnumerator`1[Inedo.ExecutionEngine.RuntimeValue]&#39;.
        at Inedo.ExecutionEngine.RuntimeListValue.GetEnumerator()
        at Inedo.ExecutionEngine.Mapping.CoreScriptPropertyMapper.CoerceValue(RuntimeValue value, PropertyInfo property, Type type)
        at Inedo.Otter.Service.PlanExecuter.ExecutionVariableEvaluationContext.GetVariableFunctionInternal(RuntimeVariableName functionName, IList`1 arguments)
        at Inedo.Otter.Service.PlanExecuter.ExecutionVariableEvaluationContext.TryEvaluateFunctionAsync(RuntimeVariableName functionName, IList`1 arguments)
        at Inedo.ExecutionEngine.Variables.FunctionTextValue.EvaluateAsync(IVariableEvaluationContext context)
        at Inedo.ExecutionEngine.Variables.ProcessedString.EvaluateValueAsync(IVariableEvaluationContext context)
        at Inedo.ExecutionEngine.Executer.ExecuterThread.EvaluateAsync(EqualityPredicate equalityPredicate)
        at Inedo.ExecutionEngine.Executer.ExecuterThread.ExecuteAsync(PredicateStatement predicateStatement)
        at Inedo.ExecutionEngine.Executer.ExecuterThread.ExecuteNextAsync()
      

      In the absence of syntax support, the sanest workaround is probably a pair of variable functions -- e.g. @ListSet(@Vec, $i, value) and %MapSet(%Map, $k, value), which could perform this operation.

      My current workaround is to...

      set %Map = %MapAdd(%MapRemove(%Map, $key), $key, value);
      
      set @copy = @();
      foreach $j in @Range(0, $ListCount(@Vec)) {
          if ($i == $j) { set @copy = @ListInsert(@copy, value); }
          else { set @copy = @ListInsert(@copy, $($ListItem(@Vec, $j))); }
      }
      set @Vec = @copy;
      

      ...the latter of which is so tortuous I've probably got it wrong just typing it in here (and does not handle anything other than lists of scalars).

      Looking at the existing similar %MapAdd and @ListInsert functions, I expect the meat of these is probably fairly straightforward:

      ListSetVariableFunction

              protected override IEnumerable EvaluateVector(IVariableFunctionContext context)
              {
                  var list = this.List.ToList();
                  var index = this.Index;
      
                  // bounds checking
                  if (index >= list.Count)
                  {
                      // allow for growing the list to fit new index
                      list.AddRange(Enumerable.Range(0, 1+index-list.Count).Select(_ => string.Empty));
                  }
                  else if (index < 0)
                  {
                      // allow for negative indexing from end of array (but not growth)
                      if (-index >= list.Count) throw new ArgumentOutOfRangeException(nameof(this.Index));
                      index = list.Count + (index % list.Count);
                  }
      
                  list[index] = this.Value;
                  return list;
              }
      

      MapSetVariableFunction

              public override RuntimeValue Evaluate(IVariableFunctionContext context)
              {
                  if (String.IsNullOrEmpty(this.Key)) throw new ArgumentNullException(nameof(this.Key));
      
                  var map = new Dictionary<string, RuntimeValue>(this.Map);
                  map[this.Key] = this.Value;
                  return new RuntimeValue(map);
              }
      

      (Reposted from GitHub on request)

      dean-houstonD 1 Reply Last reply Reply Quote 0
      • dean-houstonD Offline
        dean-houston inedo-engineer @jimbobmcgee
        last edited by

        @jimbobmcgee thanks for reposting this here

        This is a long-standing behavior of Otter/OtterScript and it's most likely not a trivial fix and would involve updating the parser/execution engine (after remembering how it all works) - so not something we'll do in a maintenance release for a community/free user, as I'm sure you'll understand

        However now that it's here, I will link it to our internal roadmap planning and consdieration for Otter 2025.

        J 1 Reply Last reply Reply Quote 0
        • J Offline
          jimbobmcgee @dean-houston
          last edited by

          @dean-houston I'm sure that fixing the syntax would not be straightforward, but introducing @ListSet and %MapSet variable functions in lieu is probably a good enough workaround for nearly all use cases where someone would want to do this.

          dean-houstonD 1 Reply Last reply Reply Quote 1
          • dean-houstonD Offline
            dean-houston inedo-engineer @jimbobmcgee
            last edited by

            @jimbobmcgee that's a nice idea; we'd definitely be open to a pull request on those FYI

            Based on other list/map functions I think it'd be relatively straight-forward and an easy pattern to follow:
            https://github.com/Inedo/inedox-inedocore/blob/master/InedoCore/InedoExtension/VariableFunctions/Lists/ListRemoveVariableFunction.cs

            Just not something we can focus on now though

            J 1 Reply Last reply Reply Quote 0
            • J Offline
              jimbobmcgee @dean-houston
              last edited by

              @dean-houston Submitted as https://github.com/Inedo/inedox-inedocore/pull/180.

              dean-houstonD 1 Reply Last reply Reply Quote 0
              • dean-houstonD Offline
                dean-houston inedo-engineer @jimbobmcgee
                last edited by

                @jimbobmcgee fantastic, we'll review/merge soon! thanks much :)

                1 Reply Last reply Reply Quote 0

                Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                With your input, this post could be even better 💗

                Register Login
                • 1 / 1
                • First post
                  Last post
                Inedo Website Home • Support Home • Code of Conduct • Forums Guide • Documentation