Metadata-Version: 2.4
Name: takeskip
Version: 0.1.0
Summary: Do bit manipulations with take/skip commands
Project-URL: Homepage, https://github.com/jolsten/takeskip
Project-URL: Repository, https://github.com/jolsten/takeskip
Project-URL: Issues, https://github.com/jolsten/takeskip/issues
Project-URL: Changelog, https://github.com/jolsten/takeskip/blob/main/CHANGELOG.md
Author-email: Jonathan Olsten <jolsten@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: lark>=1.2
Requires-Dist: numpy>=2
Description-Content-Type: text/markdown

# TakeSkip

A Python library for declarative bit manipulation using an intuitive command syntax.

## Overview

TakeSkip provides a domain-specific language for selecting, rearranging, and manipulating bits in binary arrays. Instead of writing complex indexing logic, you can express operations using simple commands like `t8s4r8` (take 8 bits, skip 4, reverse 8).

## Installation

```bash
pip install takeskip
```

## Basic Usage

```python
import numpy as np
from takeskip import takeskip

# Create a binary array (8 bits)
bits = np.array([1, 0, 1, 1, 0, 0, 1, 0], dtype=np.uint8)

# Skip first 2 bits, take next 4
result = takeskip("s2t4", bits)
print(result)  # [1, 1, 0, 0]

# Take 4 bits, reverse next 4
result = takeskip("t4r4", bits)
print(result)  # [1, 0, 1, 1, 0, 1, 0, 0]
```

## Command Reference

### Basic Operations

| Command | Description | Example |
|---------|-------------|---------|
| `t<n>` | Take n bits | `t8` - take 8 bits |
| `s<n>` | Skip n bits | `s4` - skip 4 bits |
| `r<n>` | Reverse n bits | `r8` - reverse 8 bits |
| `i<n>` | Invert n bits (0↔1) | `i4` - invert 4 bits |
| `ri<n>` | Reverse and invert n bits | `ri8` - reverse then invert 8 bits |
| `b<n>` | Backup pointer n positions | `b4` - move back 4 positions |

### Padding Operations

| Command | Description | Example |
|---------|-------------|---------|
| `z<n>` | Insert n zeros | `z4` - add 4 zeros |
| `n<n>` | Insert n ones | `n4` - add 4 ones |
| `d<binary>` | Insert literal binary data | `d101` - add bits 1,0,1 |

### Permutation

| Command | Description | Example |
|---------|-------------|---------|
| `p<indices>` | Permute using 1-based indices | `p1,3,5` - select bits 1, 3, 5 |
| `p<range>` | Permute using ranges | `p1-4` - select bits 1 through 4 |
| `p<mixed>` | Mix indices and ranges | `p1-4,8,6-5` - complex permutation |

**Note:** Permutation indices are 1-based for user convenience. Ranges are inclusive.

### Grouping and Repetition

```python
# Repeat a sequence
bits = np.array([1, 0] * 8, dtype=np.uint8)
result = takeskip("(t4s4)3", bits)  # Repeat "take 4, skip 4" three times

# Group complex operations
result = takeskip("(t2r2s1)4", bits)  # Repeat grouped operations
```

## Advanced Examples

### Nibble Swap (swap 4-bit chunks)
```python
bits = np.array([1, 1, 1, 1, 0, 0, 0, 0], dtype=np.uint8)
result = takeskip("s4t4b8t4", bits)  # [0, 0, 0, 0, 1, 1, 1, 1]
```

### Extract Every Other Bit
```python
bits = np.array([1, 0, 1, 0, 1, 0, 1, 0], dtype=np.uint8)
result = takeskip("(t1s1)4", bits)  # [1, 1, 1, 1]
```

### Deinterleave Nibbles
```python
bits = np.array([1, 0, 1, 0, 1, 0, 1, 0], dtype=np.uint8)
result = takeskip("(t1s1)4 b8 (s1t1)4", bits)  # [1, 1, 1, 1, 0, 0, 0, 0]
```

### Complex Permutation
```python
bits = np.array([1, 0, 1, 1, 0, 0, 1, 0], dtype=np.uint8)
# Take bits at positions 1,2,3,4 then 8,7,6,5 (1-based)
result = takeskip("p1-4,8-5", bits)  # [1, 0, 1, 1, 0, 1, 0, 0]
```

### Interleave Pattern with Padding
```python
bits = np.array([1, 1, 1, 1, 0, 0, 0, 0], dtype=np.uint8)
result = takeskip("(t1z1)4", bits)
# [1, 0, 1, 0, 1, 0, 1, 0] - interleaved with zeros
```

## Remnant Handling

Control what happens to remaining bits after commands execute:

```python
bits = np.array([1, 0, 1, 1, 0, 0, 1, 0], dtype=np.uint8)

# Default: discard remaining bits
result = takeskip("t4", bits, remnant="remove")  # [1, 0, 1, 1]

# Keep remaining bits
result = takeskip("t4", bits, remnant="keep")  # [1, 0, 1, 1, 0, 0, 1, 0]

# Pad with zeros to original length
result = takeskip("t4", bits, remnant="pad")  # [1, 0, 1, 1, 0, 0, 0, 0]
```

## Multi-dimensional Arrays

TakeSkip operates on the last axis of multi-dimensional arrays:

```python
# 2D array: 3 rows of 8 bits each
bits = np.array([
    [1, 0, 1, 1, 0, 0, 1, 0],
    [0, 1, 0, 1, 1, 1, 0, 1],
    [1, 1, 0, 0, 1, 0, 1, 1],
], dtype=np.uint8)

result = takeskip("s2t4", bits)
# [[1, 1, 0, 0],
#  [0, 1, 1, 1],
#  [0, 0, 1, 0]]
```

## Use Cases

- **Data packing/unpacking**: Extract fields from packed binary formats
- **Protocol parsing**: Parse binary protocols with field alignment
- **Bit manipulation**: Rearrange bits in encryption/encoding operations

## Command Chaining

Commands are executed left-to-right with a maintained pointer. The pointer must stay within `[0, array_length]` after each command; a `ValueError` is raised if `Backup` moves before the start or `Skip`/`Take` moves past the end.

```python
bits = np.array([1, 0, 1, 1, 0, 0, 1, 0], dtype=np.uint8)

# Pointer starts at 0
# t2: take bits 0-1 -> [1,0], pointer now at 2
# s2: skip bits 2-3, pointer now at 4  
# t4: take bits 4-7 -> [0,0,1,0], pointer now at 8
result = takeskip("t2s2t4", bits)  # [1, 0, 0, 0, 1, 0]
```

## Syntax Notes

- Commands are **case-insensitive**: `T4`, `t4`, and `T4` are equivalent
- **Whitespace is ignored**: `t4 s2 r8` = `t4s2r8`
- **Parentheses** create groups: `(t8s8)4` repeats the sequence 4 times
- **Permutation** uses 1-based indexing (bit 1 is the first bit)
- **Ranges** in permutation are inclusive: `1-4` includes bits 1, 2, 3, and 4


## API Documentation

### Main Function

```python
def takeskip(
    command: str,
    array: npt.NDArray[np.uint8],
    *,
    remnant: Literal["remove", "keep", "pad"] = "remove",
) -> np.ndarray
```

**Parameters:**
- `command`: Command string expressing the operation
- `array`: Target numpy array (dtype=uint8, values 0 or 1)
- `remnant`: How to handle remaining bits ("remove", "keep", or "pad")

**Returns:**
- Numpy array with same dtype, modified according to commands

**Raises:**
- `ValueError`: Invalid remnant argument or invalid command syntax

## Contributing

When adding new commands:
1. Define command class in `commands.py` inheriting from `Command`
2. Add grammar rule in `takeskip.lark`
3. Add transformer method in `parser.py`
4. Update documentation

## License

`takeskip` is licensed under the MIT License - see the LICENSE file for details
