Scanning (seek, search) for attribute start & end positions
## Driving scenarios

Normally, binary formats are well-defined and do not require parser to employ trial-and-error tactics to pinpoint exact locations of the data.

However, in practice, in quite a few cases, we need to go to location identified not by some sort of exact address, but by some arbitrary properties of what's around that location. The simplest case of that is "read the string until the null byte appears", which is currently taken care of `terminator` / `consume` / `include` semantics. Much more complicates use cases exist, for example:

* Reading Windows "wide strings", which are actually UTF-16LE strings, consisting of 2-byte characters, up until `00 00` group is encountered.
* Reverse engineering a certain format, when you don't have any good idea how exactly to address a new section, but you have a hypothesis that certain sequence of bytes serves as a magic which identifies a new section.
* JPEG/JFIF parsing requires you to either decode image contents completely, or, alternatively, scan until certain combination of bytes is encountered (#158).
* Even more complex cases like #410.
* etc

The proposal is to make a unified framework that could be used in both:

* As `pos: ...` substitute in instances, to be executed before parsing of the element to find the starting position
* As `terminator: ...` or `size: ...` substitute, to determine ending position for the element

## Terminology proposal

Let's call it "scanning", as this is actually what would be going on. "Seeking" is a bad choice of word, as typically `seek` is a API call which moves stream pointer to a certain pinpointed location and does not involve any scanning actions. "Searching" is a more generic term, which does not reflect the fact that this is a pretty expensive operation.

## KSY syntax proposal

* For `instances`, one would be able to specify `scan-start` key with "scan specification" described below. When this instance will be invoked, first thing it will be before doing actual reading, it will invoke scan algorithm to position the stream pointer properly.
* For all attributes where you already can specify `size` or `terminator`, one would be able to specify `scan-end` key with "scan specification" as well.

For contents of this "scan specification" would be akin to current "process" specifications, i.e. `scan_mode_name(param1, param2, ...)`. We can start with a few "scan modes" and add more of them later.

Initial ideas for scan modes to implement:

* `kaitai.byte(0)` — reimplement single-byte `terminator`, scan until a certain byte is encountered (0 in this example)
* `kaitai.bytes([0x45, 0x4e, 0x44])` — scan until sequence of 3 bytes is encounterd
* `kaitai.bytes_group(2, [0, 0])` — two-byte terminator working at groups of 2 bytes every time
* `kaitai.byte_reverse(0)` — same as "byte", but scans in reverse direction

## Examples of usage

Scans for `***` [42, 42, 42] 3-byte sequence, parses u4 after that:

```yaml
instances:
  foo:
    scan-start: kaitai.bytes([42, 42, 42])
    type: u4
```

The same as `terminator: 0`:

```yaml
seq:
  - id: my_strz
    scan-end: kaitai.byte(0)
```

Scans until `OrbShdr` is encountered, catches everything up to that byte sequence as substream and parses it using `my_custom_body` type:

```yaml
seq:
  - id: body
    type: my_custom_body
    scan-end: kaitai.bytes([0x4f, 0x72, 0x62, 0x53, 0x68, 0x64, 0x72])
```

The same, but includes `OrbShdr` into substream as well:

```
seq:
  - id: body
    type: my_custom_body
    scan-end: kaitai.bytes([0x4f, 0x72, 0x62, 0x53, 0x68, 0x64, 0x72])
    include: true
```

For instances, `scan-start` can be combined with `pos`. In this case, `pos` is executed first, scanning starts working after that. This scans from the end of file backwards, searching for first 0x42:

```
instances:
  foo:
    pos: _io.size
    scan-start: kaitai.byte_reverse(0x42)
```

## Implementation proposal

All scan specifications will internally invoke a certain implementation class, very similar to current custom processing classes implementation. Scan mode names will map to these class names. Special prefix `kaitai.` would be used to designate scan mode implementations bundled with runtime library.

For Java, for example, this could be:

```java
interface CustomScanner {
    void scanToStart(KaitaiStream io);
    void scanToEnd(KaitaiStream io);
}
```

Implementation for `kaitai.byte` might be:

```java
public class Byte implements CustomScanner {
    private int byteToScan;

    public Byte(int byteToScan) {
        this.byteToScan = byteToScan;
    }

    private void scan(KaitaiStream io) {
        while (true) {
            int c = io.readU1();
            if (c == byteToScan) {
                return;
            }
        }
    }

    @Override
    void scanToStart(KaitaiStream io) {
        scan(io);
        io.seek(io.pos() - 1);
    }

    @Override
    void scanToEnd(KaitaiStream io) {
        scan(io);
    }
}
```

`scan-start: byte(42)` + `type: u1` will be translated into something like:

```java
var scanner = new io.kaitai.struct.scanners.Byte(42);
scanner.scanToEnd(io);
this.foo = io.readU1();
```

`scan-end: byte(42)` + `include: true` for byte array will be something like:

```java
var pos1 = io.pos();
var scanner = new io.kaitai.struct.scanners.Byte(42);
scanner.scanToEnd(io); // due to include: true
var pos2 = io.pos();
io.seek(pos1);
this.foo = io.readBytes(pos2 - pos1);
```

Finally, `scan-end: byte(42)` + `include: false` (which is default) will be something like:

```java
var pos1 = io.pos();
var scanner = new io.kaitai.struct.scanners.Byte(42);
scanner.scanToStart(io); // due to include: false
var pos2 = io.pos();
io.seek(pos1);
this.foo = io.readBytes(pos2 - pos1);
```