Skip to content

Commit

Permalink
First commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
timothee-haudebourg committed Jul 2, 2023
0 parents commit 7f00619
Show file tree
Hide file tree
Showing 19 changed files with 3,387 additions and 0 deletions.
71 changes: 71 additions & 0 deletions .github/workflows/ci.yml
@@ -0,0 +1,71 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
types:
- opened
- reopened
- synchronize
- ready_for_review

env:
CARGO_TERM_COLORS: always

jobs:
tests:
name: Tests
runs-on: ubuntu-latest
if: ${{ !github.event.pull_request.draft }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: "1.66.0"
profile: minimal
override: true
- name: Build
run: cargo build --all-features --verbose
- name: Run tests
run: cargo test --all-features --verbose
rustfmt:
name: Formatting
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: "1.66.0"
profile: minimal
override: true
components: rustfmt
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Warnings
runs-on: ubuntu-latest
if: ${{ !github.event.pull_request.draft }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: "1.66.0"
profile: minimal
override: true
components: clippy
- name: Clippy Check
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-features -- -D warnings
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
/target
/Cargo.lock
1 change: 1 addition & 0 deletions .rustfmt.toml
@@ -0,0 +1 @@
hard_tabs = true
23 changes: 23 additions & 0 deletions Cargo.toml
@@ -0,0 +1,23 @@
[package]
name = "static-regular-grammar"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
# btree-range-map = "0.5.2"
btree-range-map = { path = "/home/timothee/Projets/utils/data-structures/btree-range-map", features = ["serde"] }
proc-macro2 = "1.0.60"
proc-macro-error = "1.0.4"
syn = "2.0.18"
quote = "1.0.29"
thiserror = "1.0.40"
abnf = "0.13.0"
indoc = "2.0.1"
serde = { version = "1.0.164", features = ["derive"] }
# serde_json = "1.0.99"
ciborium = "0.2.1"
sha2 = "0.10.7"
hex_fmt = "0.3.0"
32 changes: 32 additions & 0 deletions README.md
@@ -0,0 +1,32 @@
# Static Regular Grammars

[![Build](https://img.shields.io/github/actions/workflow/status/timothee-haudebourg/static-regular-grammar/ci.yml?branch=main&style=flat-square)](https://github.com/timothee-haudebourg/static-regular-grammar/actions)
[![Crate informations](https://img.shields.io/crates/v/static-regular-grammar.svg?style=flat-square)](https://crates.io/crates/static-regular-grammar)
[![License](https://img.shields.io/crates/l/static-regular-grammar.svg?style=flat-square)](https://github.com/timothee-haudebourg/static-regular-grammar#license)
[![Documentation](https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square)](https://docs.rs/static-regular-grammar)

<!-- cargo-rdme start -->

This library provides the `RegularGrammar` macro that helps you create
unsized type wrapping byte or char strings validated by a regular grammar.
For now, only the ABNF grammar format is supported.

## Example

The grammar is specified by code blocks in the type documentation.

```rust
use static_regular_grammar::RegularGrammar;

/// Example grammar
///
/// ```abnf
/// foo = "f" 1*("oo") ; the first non-terminal is used as entry point.
/// ```
#[derive(RegularGrammar)]
pub struct Foo([u8]);

let foo = Foo::new(b"foooooo").unwrap();
```

<!-- cargo-rdme end -->
15 changes: 15 additions & 0 deletions examples/test.rs
@@ -0,0 +1,15 @@
use static_regular_grammar::RegularGrammar;

/// Test
///
/// # Grammar
///
/// ```abnf
/// test = "%" / "$"
/// ```
#[derive(RegularGrammar)]
pub struct Test([u8]);

fn main() {
Test::new(b"$").unwrap();
}
73 changes: 73 additions & 0 deletions examples/uri.rs
@@ -0,0 +1,73 @@
use static_regular_grammar::RegularGrammar;

/// URI Authority.
///
/// # Grammar
///
/// ```abnf
/// authority = [ userinfo "@" ] host [ ":" port ]
///
/// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
///
/// host = IP-literal / IPv4address / reg-name
///
/// port = *DIGIT
/// ```
///
/// ## Host rules
///
/// ```abnf
/// IP-literal = "[" ( IPv6address / IPvFuture ) "]"
///
/// IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
///
/// IPv6address = 6( h16 ":" ) ls32
/// / "::" 5( h16 ":" ) ls32
/// / [ h16 ] "::" 4( h16 ":" ) ls32
/// / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
/// / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
/// / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
/// / [ *4( h16 ":" ) h16 ] "::" ls32
/// / [ *5( h16 ":" ) h16 ] "::" h16
/// / [ *6( h16 ":" ) h16 ] "::"
///
/// ls32 = ( h16 ":" h16 ) / IPv4address
/// ; least-significant 32 bits of address
///
/// h16 = 1*4HEXDIG
/// ; 16 bits of address represented in hexadecimal
///
/// IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
///
/// dec-octet = DIGIT ; 0-9
/// / %x31-39 DIGIT ; 10-99
/// / "1" 2DIGIT ; 100-199
/// / "2" %x30-34 DIGIT ; 200-249
/// / "25" %x30-35 ; 250-255
///
/// reg-name = *( unreserved / pct-encoded / sub-delims )
/// ```
///
/// ## Misc rules
///
/// ```abnf
/// reserved = gen-delims / sub-delims
///
/// gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
///
/// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/// / "*" / "+" / "," / ";" / "="
///
/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
///
/// pct-encoded = "%" HEXDIG HEXDIG
/// ```
#[derive(RegularGrammar, PartialEq, Eq)]
#[title = "uri:authority"]
#[buffer(AuthorityBuf, derive(PartialEq, Eq))]
pub struct Authority([u8]);

fn main() {
Authority::new(b"timothee@example.org:12").unwrap();
AuthorityBuf::new(b"timothee@example.org:12".to_vec()).unwrap();
}
172 changes: 172 additions & 0 deletions src/byteset.rs
@@ -0,0 +1,172 @@
use btree_range_map::{AnyRange, RangeSet};
use std::{
fmt,
ops::{Deref, DerefMut, RangeInclusive},
};

use crate::utils::Sanitized;

pub struct DisplayByte(pub u8);

impl fmt::Display for DisplayByte {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Sanitized(self.0).fmt(f)
}
}

pub struct DisplayBytes<'a>(pub &'a str);

impl<'a> fmt::Display for DisplayBytes<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Sanitized(self.0).fmt(f)
}
}

pub struct DisplayByteRange<'a>(pub &'a AnyRange<u8>);

impl<'a> fmt::Display for DisplayByteRange<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(first) = self.0.first() {
let last = self.0.last().unwrap();

if first == last {
fmt::Display::fmt(&DisplayByte(first), f)
} else if first as u32 + 1 == last as u32 {
// Note: no risk of overflowing here with `u8`.
write!(f, "{}{}", DisplayByte(first), DisplayByte(last))
} else {
write!(f, "{}-{}", DisplayByte(first), DisplayByte(last))
}
} else {
Ok(())
}
}
}

#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ByteSet(RangeSet<u8>);

impl ByteSet {
pub fn new() -> ByteSet {
ByteSet(RangeSet::new())
}

pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

pub fn len(&self) -> u16 {
self.0.len()
}

pub fn from_u8(c: u8, case_sensitive: bool) -> ByteSet {
let mut set = ByteSet::new();

if case_sensitive {
set.insert(c)
} else {
set.insert(c.to_ascii_uppercase());
set.insert(c.to_ascii_lowercase())
}

set
}

pub fn from_ranges(ranges: impl IntoIterator<Item = RangeInclusive<u8>>) -> Self {
let mut set = RangeSet::new();

for range in ranges {
set.insert(range)
}

ByteSet(set)
}

pub fn from_u8s(u8s: impl IntoIterator<Item = u8>) -> Self {
let mut set = RangeSet::new();

for c in u8s {
set.insert(c)
}

ByteSet(set)
}

pub fn ranges(&self) -> Ranges {
Ranges(self.0.iter())
}

pub fn first(&self) -> Option<u8> {
self.0.iter().next().and_then(|range| range.first())
}
}

pub struct Ranges<'a>(
btree_range_map::generic::set::Iter<'a, u8, btree_range_map::DefaultSetContainer<u8>>,
);

impl<'a> Iterator for Ranges<'a> {
type Item = AnyRange<u8>;

fn next(&mut self) -> Option<Self::Item> {
self.0.next().copied()
}
}

impl From<AnyRange<u8>> for ByteSet {
fn from(value: AnyRange<u8>) -> Self {
let mut result = ByteSet::new();
result.insert(value);
result
}
}

impl From<u8> for ByteSet {
fn from(value: u8) -> Self {
let mut result = ByteSet::new();
result.insert(value);
result
}
}

impl<const N: usize> From<[u8; N]> for ByteSet {
fn from(value: [u8; N]) -> Self {
Self::from_u8s(value)
}
}

impl<const N: usize> From<[RangeInclusive<u8>; N]> for ByteSet {
fn from(value: [RangeInclusive<u8>; N]) -> Self {
Self::from_ranges(value)
}
}

impl fmt::Display for ByteSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for range in &self.0 {
DisplayByteRange(range).fmt(f)?;
}

Ok(())
}
}

impl fmt::Debug for ByteSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CharSet({})", self)
}
}

impl Deref for ByteSet {
type Target = RangeSet<u8>;

fn deref(&self) -> &RangeSet<u8> {
&self.0
}
}

impl DerefMut for ByteSet {
fn deref_mut(&mut self) -> &mut RangeSet<u8> {
&mut self.0
}
}

0 comments on commit 7f00619

Please sign in to comment.