All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nstream.adapter.common.relay.DslInterpret Maven / Gradle / Ivy

There is a newer version: 4.14.22
Show newest version
// Copyright 2015-2024 Nstream, inc.
//
// Licensed under the Redis Source Available License 2.0 (RSALv2) Agreement;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://redis.com/legal/rsalv2-agreement/
//
// 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.

package nstream.adapter.common.relay;

import java.util.Optional;
import nstream.adapter.common.LabeledLog;
import swim.api.agent.AgentContext;
import swim.structure.Attr;
import swim.structure.Bool;
import swim.structure.Data;
import swim.structure.Expression;
import swim.structure.Item;
import swim.structure.Num;
import swim.structure.Record;
import swim.structure.Text;
import swim.structure.Value;
import swim.util.Log;

public final class DslInterpret {

  private DslInterpret() {
  }

  public static void interpret(AgentContext context, Record schema, Item scope) {
    interpret(context, LabeledLog.forAgentContext(context), schema, scope);
  }

  public static void interpret(AgentContext context, Log log, Record schema, Item scope) {
    if (recordModelsOneDirective(schema)) {
      interpretOne(createDirective(context, log, schema, scope));
    } else {
      for (Item item : schema) {
        if (!interpretOne(createDirective(context, log, (Value) item, scope))) {
          return;
        }
      }
    }
  }

  private static boolean interpretOne(Directive directive) {
    try {
      evaluateOrErr(directive);
    } catch (InterpretException e) {
      return false;
    }
    return true;
  }

  /**
   * Utility method to evaluate a directive or log a helpful error if doing so
   * results in failure.
   *
   * @param directive  the {@code Directive} to evaluate
   *
   * @return  {@code directive.evaluate()}
   *
   * @throws InterpretException  if there is any issue in evaluating {@code
   * directive}
   */
  static Value evaluateOrErr(Directive directive) throws InterpretException {
    try {
      return directive.evaluate();
    } catch (InterpretException e) {
      directive.log.error(e.getMessage());
      directive.log.error("Could not interpret " + directive);
      throw e;
    }
  }

  static Directive createDirective(AgentContext context, Log log, Value schema, Item scope) {
    return createDirective(context, log, schema, scope, null);
  }

  static Directive createDirective(AgentContext context, Log log, Value schema, Item scope, Directive parent) {
    final Item head = schema.head();
    if (head instanceof Attr) {
      if (head.toValue() instanceof Expression) {
        return new Take<>(parent, context, log, scope, (Expression) head.toValue(), schema);
      } else if (head.toValue().isDistinct()) {
        throw new UnsupportedOperationException("Static Truncate directives not yet implemented");
      } else {
        switch (head.key().stringValue(null)) {
          case "command":
            return Command.fromSchema(parent, context, log, scope, schema);
          case "foreach":
          case "forEach":
            return ForEach.fromSchema(parent, context, log, scope, schema);
          case "if":
            return If.fromSchema(parent, context, log, scope, schema);
          case "substring":
            return Substring.fromSchema(parent, context, log, scope, schema);
          case "uripathencode":
          case "uriPathEncode":
            return UriPathEncode.from(parent, context, log, scope);
          default:
            log.error("schema " + schema + " does not model a known Directive");
            throw new UnsupportedOperationException("Directive " + head.key().stringValue(null) + " not implemented");
        }
      }
    } else {
      log.error("schema " + schema + " does not model a Directive (no Attr head)");
      throw new IllegalArgumentException("No Attr head present to indicate Directive type");
    }
  }

  /**
   * Utility method to evaluate a {@code Value} component into a {@code String}
   * during DSL interpretation according to a rigid (but usually sufficient)
   * ruleset.
   * 

* The ruleset is as follows: *

    *
  1. If {@code component} has a String literal representation and is not a * {@code Record}, then simply return the literal. *
  2. If {@code component} is an {@code Expression}, then return the * evaluation of the {@code Expression} against the calling {@code * Directive}'s scope. *
  3. If {@code component} is a {@code Record} whose {@code head()} is an * {@code Attr}, then parse {@code component} into a {@code Directive} and * return its evaluation. *
  4. If {@code component} is a {@code Record} containing a series of {@code * Values} (i.e. lacking any top-level {@code Fields}, then return the * concatenation of recursively invoking {@code evaluateStringComponent()} on * every such {@code Value}. *
* * @param source the current interpretation frame * @param component the value within the frame that requires evaluation * * @return the fully-evaluated {@code component} relative to {@code source} */ static String evaluateStringComponent(Directive source, Value component) throws InterpretException { final Optional basic = basicItemToString(source, component); // Checked exceptions are a mistake -_- return basic.isPresent() ? basic.get() : evaluateDynamicStringComponent(source, component); } private static String evaluateDynamicStringComponent(Directive source, Value component) throws InterpretException { if (component instanceof Expression) { return evaluateStringComponent(source, (Value) component.evaluate(source.scope)); } else if (component instanceof Record) { if (recordModelsOneDirective((Record) component)) { final Directive toEvaluate; try { toEvaluate = createDirective(source.context, source.log, component, source.scope, source); } catch (RuntimeException e) { throw new InterpretException(e.getMessage()); } final Value evaluated = evaluateOrErr(toEvaluate); return basicItemToString(source, evaluated).orElseThrow(() -> { source.log.error("Attempted to String-evaluate generated Record " + evaluated); return new InterpretException("Generative DSL interpretation not yet implemented"); }); } else { final StringBuilder sb = new StringBuilder(16 * component.length()); for (Item item : component) { if (item instanceof Value) { // TODO: perhaps Fields are safe to include in this check sb.append(evaluateStringComponent(source, (Value) item)); // recursive call } else { throw new InterpretException("Field evaluation currently not supported, encountered " + item); } } return sb.toString(); } } else { source.log.error("component " + component + " has no stringify rules"); throw new InterpretException("component " + component + " has no stringify rules"); } } static Optional basicItemToString(Directive source, Value component) { if (component == null || !component.isDistinct()) { return Optional.of(""); } else if (component instanceof Text || component instanceof Num || component instanceof Bool) { return Optional.of(component.stringValue()); } else if (component instanceof Data) { source.log.error("Attempted to directly stringify Data component " + component); throw new IllegalArgumentException("Data-type component " + component + " must be wrapped by @dataEncode"); } else { return Optional.empty(); } } static boolean recordModelsOneDirective(Record record) { return record.head() instanceof Attr; } static Value evaluateHintedValue(Directive directive, Value hint, Value literal, String tag) throws InterpretException { if (!hint.isDefined()) { return literal.isDefined() ? literal : directive.scope.toValue(); } else if (hint instanceof Expression) { try { return hint.evaluate(directive.scope).toValue(); } catch (RuntimeException e) { final String msg = "Failed to evaluate " + tag + " against " + directive.scope + " using " + directive; throw new InterpretException(msg, e); } } else if (hint instanceof Record) { return evaluateOrErr(createDirective(directive.context, directive.log, hint, directive.scope, directive)); } else { return hint; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy