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

com.squarespace.compiler.text.Scanner Maven / Gradle / Ivy

/**
 * Copyright (c) 2017 Squarespace, Inc.
 *
 * 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.
 */

package com.squarespace.compiler.text;

import com.squarespace.compiler.match.Recognizers.Recognizer;


/**
 * Simple string scanner supporting multiple simultaneous "streams" over it.
 */
public class Scanner {

  private final CharSequence raw;

  public Scanner(CharSequence raw) {
    this.raw = raw;
  }

  public Stream stream() {
    return new Stream();
  }

  /**
   * Represents a single range of characters that can be traversed and
   * matched against.
   */
  public class Stream {

    public int pos;
    public int end;

    public Stream() {
      this(0, raw.length());
    }

    /**
     * Create a stream with the given bounds.
     */
    public Stream(int pos, int end) {
      set(pos, end);
    }

    /**
     * Create a copy of this stream.
     */
    public Stream copy() {
      return new Stream(pos, end);
    }

    /**
     * Return the characters this stream spans.
     */
    public CharSequence token() {
      return raw.subSequence(pos, end);
    }

    /**
     * Return reference to the raw underlying string.
     */
    public CharSequence raw() {
      return raw;
    }

    /**
     * Set the bounds of this stream.
     */
    public void set(int pos, int end) {
      this.pos = pos;
      this.end = end;
    }

    /**
     * Set the bounds of this stream to match the 'other' stream.
     */
    public void setFrom(Stream other) {
      this.pos = other.pos;
      this.end = other.end;
    }

    /**
     * Jump this state over 'other'.
     */
    public void jump(Stream other) {
      this.pos = other.end;
    }

    /**
     * Peek at the next character.
     */
    public char peek() {
      return pos < end ? raw.charAt(pos) : Chars.EOF;
    }

    /**
     * Seek to the expected character, returning its offset or -1.
     */
    public int find(char expected) {
      int s = pos;
      while (s < end) {
        if (raw.charAt(s) == expected) {
          return s;
        }
        s++;
      }
      return -1;
    }

    /**
     * Advance past the next character and return it.
     */
    public char seek() {
      char ch = peek();
      pos++;
      return ch;
    }

    /**
     * Match the given recognizer against the current stream, and if
     * matching set the bounds of the 'other' stream. Return a boolean
     * indicating whether the recognizer matched.
     */
    public boolean seek(Recognizer matcher, Stream other) {
      int r = matcher.match(raw, pos, end);
      if (r != -1) {
        other.set(pos, r);
        return true;
      }
      return false;
    }

    /**
     * Skip over any whitespace characters.
     */
    public void skipWs() {
      while (pos < end) {
        if (!DefaultCharClassifier.whitespace(raw.charAt(pos))) {
          break;
        }
        pos++;
      }
    }

    /**
     * Skip over characters matched by the given pattern.
     */
    public boolean skip(Recognizer pattern) {
      int e = pattern.match(raw, pos, end);
      if (e == -1) {
        return false;
      }
      pos = e;
      return true;
    }

    /**
     * Set the bounds of the other stream to the area enclosed by the left
     * and right delimiters. Also handles skipping over nested delimiters
     * to find the matching ones.
     */
    public boolean seekBounds(Stream other, char left, char right) {
      int depth = 0;
      int start = -1;
      while (pos < end) {
        char ch = raw.charAt(pos);
        if (ch == left) {
          if (depth == 0) {
            start = pos;
          }
          depth++;
        } else if (ch == right && start != -1) {
          depth--;
          if (depth == 0) {
            pos++;
            other.set(start, pos);
            return true;
          }
        }
        pos++;
      }
      return false;
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof Stream) {
        Stream other = (Stream) obj;
        return equalsCharacters(other.raw(), other.pos, other.end);
      }
      return false;
    }

    @Override
    public int hashCode() {
      throw new UnsupportedOperationException("hashCode() not supported");
    }

    @Override
    public String toString() {
      return raw.subSequence(pos, end).toString();
    }

    /**
     * Compares this stream's range of characters against another range, returning
     * true if they are equal, false otherwise.
     */
    public boolean equalsCharacters(CharSequence other, int bs, int be) {
      int as = pos;
      int ae = end;
      int len = ae - as;
      if (len != be - bs) {
        return false;
      }
      for (int i = 0; i < len; i++) {
        if (raw.charAt(as + i) != other.charAt(bs + i)) {
          return false;
        }
      }
      return true;
    }

  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy