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

dev.cel.common.CelSource Maven / Gradle / Ivy

// Copyright 2022 Google LLC
//
// 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
//
//      https://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.

package dev.cel.common;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.errorprone.annotations.Immutable;
import dev.cel.common.ast.CelExpr;
import dev.cel.common.internal.CelCodePointArray;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/** Represents the source content of an expression and related metadata. */
@Immutable
public final class CelSource {

  private static final Splitter LINE_SPLITTER = Splitter.on('\n');

  private final CelCodePointArray codePoints;
  private final String description;
  private final ImmutableList lineOffsets;
  private final ImmutableMap positions;
  private final ImmutableMap macroCalls;

  private CelSource(Builder builder) {
    codePoints = checkNotNull(builder.codePoints);
    description = checkNotNull(builder.description);
    positions = checkNotNull(builder.positions.buildOrThrow());
    lineOffsets = checkNotNull(ImmutableList.copyOf(builder.lineOffsets));
    macroCalls = checkNotNull(ImmutableMap.copyOf(builder.macroCalls));
  }

  public CelCodePointArray getContent() {
    return codePoints;
  }

  public String getDescription() {
    return description;
  }

  public ImmutableMap getPositionsMap() {
    return positions;
  }

  /**
   * Get the code point offsets (NOT code unit offsets) for new line characters '\n' within the
   * source expression text.
   *
   * 

NOTE: The indices point to the index just after the '\n' not the index of '\n' itself. */ public ImmutableList getLineOffsets() { return lineOffsets; } public ImmutableMap getMacroCalls() { return macroCalls; } /** See {@link #getLocationOffset(int, int)}. */ public Optional getLocationOffset(CelSourceLocation location) { checkNotNull(location); return getLocationOffset(location.getLine(), location.getColumn()); } /** * Get the code point offset within the source expression text that corresponds with the {@code * line} and {@code column}. * * @param line the line number starting from 1 * @param column the column number starting from 0 */ public Optional getLocationOffset(int line, int column) { return getLocationOffsetImpl(lineOffsets, line, column); } /** * Get the line and column in the source expression text for the given code point {@code offset}. */ public Optional getOffsetLocation(int offset) { return getOffsetLocationImpl(lineOffsets, offset); } /** * Get the text from the source expression that corresponds to {@code line}. * * @param line the line number starting from 1. */ public Optional getSnippet(int line) { checkArgument(line > 0); int start = findLineOffset(lineOffsets, line); if (start == -1) { return Optional.empty(); } int end = findLineOffset(lineOffsets, line + 1); if (end == -1) { end = codePoints.size(); } else { end--; } return Optional.of(end != start ? codePoints.slice(start, end).toString() : ""); } /** * Get the code point offset within the source expression text that corresponds with the {@code * line} and {@code column}. * * @param line the line number starting from 1 * @param column the column number starting from 0 */ private static Optional getLocationOffsetImpl( List lineOffsets, int line, int column) { checkArgument(line > 0); checkArgument(column >= 0); int offset = findLineOffset(lineOffsets, line); if (offset == -1) { return Optional.empty(); } return Optional.of(offset + column); } /** * Get the line and column in the source expression text for the given code point {@code offset}. */ public static Optional getOffsetLocationImpl( List lineOffsets, int offset) { checkArgument(offset >= 0); LineAndOffset lineAndOffset = findLine(lineOffsets, offset); return Optional.of(CelSourceLocation.of(lineAndOffset.line, offset - lineAndOffset.offset)); } private static int findLineOffset(List lineOffsets, int line) { if (line == 1) { return 0; } if (line > 1 && line <= lineOffsets.size()) { return lineOffsets.get(line - 2); } return -1; } private static LineAndOffset findLine(List lineOffsets, int offset) { int line = 1; for (int index = 0; index < lineOffsets.size(); index++) { if (lineOffsets.get(index) > offset) { break; } line++; } if (line == 1) { return new LineAndOffset(line, 0); } return new LineAndOffset(line, lineOffsets.get(line - 2)); } public Builder toBuilder() { return new Builder(codePoints, lineOffsets) .setDescription(description) .addPositionsMap(positions) .addAllMacroCalls(macroCalls); } public static Builder newBuilder() { return new Builder(); } public static Builder newBuilder(String text) { List lineOffsets = new ArrayList<>(); int lineOffset = 0; for (String line : LINE_SPLITTER.split(text)) { lineOffset += (int) (line.codePoints().count() + 1); lineOffsets.add(lineOffset); } return new Builder(CelCodePointArray.fromString(text), lineOffsets); } /** Builder for {@link CelSource}. */ public static final class Builder { private final CelCodePointArray codePoints; private final List lineOffsets; private final ImmutableMap.Builder positions; private final Map macroCalls; private String description; private Builder() { this(CelCodePointArray.fromString(""), new ArrayList<>()); } private Builder(CelCodePointArray codePoints, List lineOffsets) { this.codePoints = checkNotNull(codePoints); this.lineOffsets = checkNotNull(lineOffsets); this.positions = ImmutableMap.builder(); this.macroCalls = new HashMap<>(); description = ""; } @CanIgnoreReturnValue public Builder setDescription(String description) { this.description = checkNotNull(description); return this; } @CanIgnoreReturnValue public Builder addLineOffsets(int lineOffset) { checkArgument(lineOffset >= 0); lineOffsets.add(lineOffset); return this; } @CanIgnoreReturnValue public Builder addLineOffsets(int... lineOffsets) { // Purposefully not using Arrays.asList to avoid int boxing/unboxing. for (int index = 0; index != lineOffsets.length; index++) { addLineOffsets(lineOffsets[index]); } return this; } @CanIgnoreReturnValue public Builder addAllLineOffsets(Iterable lineOffsets) { for (int lineOffset : lineOffsets) { addLineOffsets(lineOffset); } return this; } @CanIgnoreReturnValue public Builder addPositionsMap(Map positionsMap) { checkNotNull(positionsMap); this.positions.putAll(positionsMap); return this; } @CanIgnoreReturnValue public Builder addPositions(long exprId, int position) { this.positions.put(exprId, position); return this; } @CanIgnoreReturnValue public Builder addMacroCalls(long exprId, CelExpr expr) { this.macroCalls.put(exprId, expr); return this; } @CanIgnoreReturnValue public Builder addAllMacroCalls(Map macroCalls) { this.macroCalls.putAll(macroCalls); return this; } @CanIgnoreReturnValue public Builder clearMacroCall(long exprId) { this.macroCalls.remove(exprId); return this; } /** See {@link #getLocationOffset(int, int)}. */ public Optional getLocationOffset(CelSourceLocation location) { checkNotNull(location); return getLocationOffset(location.getLine(), location.getColumn()); } /** * Get the code point offset within the source expression text that corresponds with the {@code * line} and {@code column}. * * @param line the line number starting from 1 * @param column the column number starting from 0 */ public Optional getLocationOffset(int line, int column) { return getLocationOffsetImpl(lineOffsets, line, column); } /** * Get the line and column in the source expression text for the given code point {@code * offset}. */ public Optional getOffsetLocation(int offset) { return getOffsetLocationImpl(lineOffsets, offset); } @CheckReturnValue public ImmutableMap getPositionsMap() { return this.positions.buildOrThrow(); } @CheckReturnValue public Map getMacroCalls() { return macroCalls; } @CheckReturnValue public boolean containsMacroCalls(long exprId) { return this.macroCalls.containsKey(exprId); } @CheckReturnValue public CelSource build() { return new CelSource(this); } } private static final class LineAndOffset { private LineAndOffset(int line, int offset) { this.line = line; this.offset = offset; } int line; int offset; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy