Skip to content

Commit

Permalink
Merge be75e24 into 412dd26
Browse files Browse the repository at this point in the history
  • Loading branch information
ia0 committed Nov 16, 2017
2 parents 412dd26 + be75e24 commit 9e56afe
Show file tree
Hide file tree
Showing 16 changed files with 603 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[workspace]
members = ["lib", "bin", "cmp"]
members = ["lib", "lib/macro/internal", "lib/macro", "bin", "cmp"]
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ lazy_static! {
}
```

If you use a nightly compiler, you may use the [macro] library to define a
compile-time custom encoding:

```rust
const HEX: Encoding = new_encoding!{
symbols: "0123456789abcdef",
translate_from: "ABCDEF",
translate_to: "abcdef",
};
const BASE64: Encoding = new_encoding!{
symbols: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
padding: '=',
};
```

See the [documentation] or the [changelog] for more details.

## Performance
Expand Down Expand Up @@ -153,6 +168,7 @@ Examples:
[library]: https://crates.io/crates/data-encoding
[license]: https://github.com/ia0/data-encoding/blob/master/LICENSE
[license_badge]: https://img.shields.io/crates/l/data-encoding.svg
[macro]: https://crates.io/crates/data-encoding-macro
[travis]: https://travis-ci.org/ia0/data-encoding
[travis_badge]: https://travis-ci.org/ia0/data-encoding.svg?branch=master
[version_badge]: https://img.shields.io/crates/v/data-encoding.svg
3 changes: 3 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ test_script:
- cd lib
- cargo test --verbose
- if [%CHANNEL%]==[nightly] cargo bench --verbose
- if [%CHANNEL%]==[nightly] cd macro
- if [%CHANNEL%]==[nightly] cargo test --verbose
- if [%CHANNEL%]==[nightly] cd ..
- cd ..
- cd bin
- cargo build --verbose
Expand Down
1 change: 1 addition & 0 deletions lib/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Minor

- Accept duplicate but identical value specification
- Add `BASE32_DNSCURVE`
- Add `BASE32HEX_NOPAD` and `BASE32_DNSSEC`

Expand Down
16 changes: 16 additions & 0 deletions lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ lazy_static! {
}
```

If you use a nightly compiler, you may use the [macro] library to define a
compile-time custom encoding:

```rust
const HEX: Encoding = new_encoding!{
symbols: "0123456789abcdef",
translate_from: "ABCDEF",
translate_to: "abcdef",
};
const BASE64: Encoding = new_encoding!{
symbols: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
padding: '=',
};
```

See the [documentation] or the [changelog] for more details.

## Performance
Expand All @@ -84,3 +99,4 @@ see the installation instructions on [github].
[changelog]: https://github.com/ia0/data-encoding/blob/master/lib/CHANGELOG.md
[documentation]: https://docs.rs/data-encoding
[github]: https://github.com/ia0/data-encoding
[macro]: https://crates.io/crates/data-encoding-macro
15 changes: 15 additions & 0 deletions lib/macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "data-encoding-macro"
version = "0.1.0"
authors = ["Julien Cretin <cretin@google.com>"]
license = "MIT"
keywords = ["data-encoding", "macro", "static", "const", "compile-time"]
categories = ["encoding"]
readme = "README.md"
repository = "https://github.com/ia0/data-encoding"
documentation = "https://docs.rs/data-encoding-macro"
include = ["Cargo.toml", "LICENSE", "README.md", "src/lib.rs"]

[dependencies]
data-encoding = { version = "2.0.0", path = ".." }
data-encoding-macro-internal = { version = "0.1.0", path = "internal" }
1 change: 1 addition & 0 deletions lib/macro/LICENSE
40 changes: 40 additions & 0 deletions lib/macro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
This library requires a nightly compiler.

It provides macros to define compile-time byte arrays from encoded strings
(using common bases like base64, base32, or hexadecimal, and also custom bases).
It also provides a macro to define compile-time custom encodings to be used with
the [data-encoding] crate at run-time.

If you were familiar with the [binary_macros] crate, this library is actually
[inspired][binary_macros_issue] from it.

### Examples

You can define a compile-time byte array using the `hexlower` or `base64`
macros:

```rust
hexlower!("const HELLO" = "68656c6c6f");
base64!("const FOOBAR" = "Zm9vYmFy");
```

You can define a compile-time custom encoding using the `new_encoding` macro:

```rust
const HEX: Encoding = new_encoding!{
symbols: "0123456789abcdef",
translate_from: "ABCDEF",
translate_to: "abcdef",
};
const BASE64: Encoding = new_encoding!{
symbols: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
padding: '=',
};
```

See the [documentation] for more details.

[binary_macros]: https://crates.io/crates/binary_macros
[binary_macros_issue]: https://github.com/ia0/data-encoding/issues/7
[data-encoding]: https://crates.io/crates/data-encoding
[documentation]: https://docs.rs/data-encoding-macro
15 changes: 15 additions & 0 deletions lib/macro/internal/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "data-encoding-macro-internal"
version = "0.1.0"
authors = ["Julien Cretin <cretin@google.com>"]
license = "MIT"
readme = "README.md"
description = "Internal library for data-encoding-macro"
include = ["Cargo.toml", "LICENSE", "README.md", "src/lib.rs"]

[lib]
proc-macro = true

[dependencies]
data-encoding = { version = "2.0.0", path = "../.." }
syn = "0.11.11"
1 change: 1 addition & 0 deletions lib/macro/internal/LICENSE
6 changes: 6 additions & 0 deletions lib/macro/internal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Do **not** use this library. Use [data-encoding-macro] instead.

This library is for internal use by data-encoding-macro because procedural
macros require a separate crate.

[data-encoding-macro]: https://crates.io/crates/data-encoding-macro
178 changes: 178 additions & 0 deletions lib/macro/internal/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//! Internal library for data-encoding-macro
//!
//! Do **not** use this library. Use [data-encoding-macro] instead.
//!
//! This library is for internal use by data-encoding-macro because procedural
//! macros require a separate crate.
//!
//! [data-encoding-macro]: https://crates.io/crates/data-encoding-macro

#![feature(proc_macro)]
#![warn(unused_results)]

extern crate proc_macro;
extern crate syn;

extern crate data_encoding;

use proc_macro::{Spacing, TokenNode, TokenStream, TokenTree, TokenTreeIter};
use std::collections::HashMap;

use data_encoding::{BitOrder, Encoding, Specification, Translate, Wrap};

fn parse_op(tokens: &mut TokenTreeIter, op: char, key: &str) {
match tokens.next() {
Some(TokenTree { span: _, kind: TokenNode::Op(x, Spacing::Alone) })
if x == op => (),
_ => panic!("expected {:?} after {}", op, key),
}
}

fn parse_map(mut tokens: TokenTreeIter) -> HashMap<String, TokenNode> {
let mut map = HashMap::new();
while let Some(key) = tokens.next() {
let key = match key.kind {
TokenNode::Term(term) => term.as_str().to_string(),
_ => panic!("expected key got {}", key),
};
parse_op(&mut tokens, ':', &key);
let value = match tokens.next() {
None => panic!("expected value for {}", key),
Some(value) => value.kind,
};
parse_op(&mut tokens, ',', &key);
let _ = map.insert(key, value);
}
map
}

fn get_string(map: &mut HashMap<String, TokenNode>, key: &str) -> String {
let node = match map.remove(key) {
None => return String::new(),
Some(node) => node,
};
let literal = match node {
TokenNode::Literal(literal) => literal,
_ => panic!("expected literal for {}", key),
};
match syn::parse::string(&literal.to_string()) {
syn::parse::IResult::Done(_, result) => result.value,
_ => panic!("expected string for {}", key),
}
}

fn get_usize(map: &mut HashMap<String, TokenNode>, key: &str) -> usize {
let node = match map.remove(key) {
None => return 0,
Some(node) => node,
};
let literal = match node {
TokenNode::Literal(literal) => literal,
_ => panic!("expected literal for {}", key),
};
match literal.to_string().parse() {
Ok(result) => result,
Err(error) => panic!("expected usize for {}: {}", key, error),
}
}

fn get_padding(map: &mut HashMap<String, TokenNode>) -> Option<char> {
let node = match map.remove("padding") {
None => return None,
Some(node) => node,
};
let literal = match node {
TokenNode::Term(term) if term.as_str() == "None" => return None,
TokenNode::Literal(literal) => literal,
_ => panic!("expected literal for padding"),
};
Some(syn::parse::character(&literal.to_string()).expect(
"expected char for padding",
))
}

fn get_bool(map: &mut HashMap<String, TokenNode>, key: &str) -> Option<bool> {
let node = match map.remove(key) {
None => return None,
Some(node) => node,
};
let term = match node {
TokenNode::Term(term) => term,
_ => panic!("expected literal for padding"),
};
Some(syn::parse::boolean(term.as_str()).expect(
"expected bool for padding",
))
}

fn get_bit_order(map: &mut HashMap<String, TokenNode>) -> BitOrder {
let node = match map.remove("bit_order") {
None => return BitOrder::MostSignificantFirst,
Some(node) => node,
};
let msb = "MostSignificantFirst";
let lsb = "LeastSignificantFirst";
match node {
TokenNode::Term(term) if term.as_str() == msb => {
BitOrder::MostSignificantFirst
}
TokenNode::Term(term) if term.as_str() == lsb => {
BitOrder::LeastSignificantFirst
}
_ => panic!("expected {} or {} for bit_order", msb, lsb),
}
}

fn check_present(hash_map: &HashMap<String, TokenNode>, key: &str) {
if !hash_map.contains_key(key) {
panic!("{} is required", key);
}
}

fn get_encoding(mut hash_map: &mut HashMap<String, TokenNode>) -> Encoding {
check_present(&hash_map, "symbols");
let spec = Specification {
symbols: get_string(&mut hash_map, "symbols"),
bit_order: get_bit_order(&mut hash_map),
check_trailing_bits: get_bool(&mut hash_map, "check_trailing_bits")
.unwrap_or(true),
padding: get_padding(&mut hash_map),
ignore: get_string(&mut hash_map, "ignore"),
wrap: Wrap {
width: get_usize(&mut hash_map, "wrap_width"),
separator: get_string(&mut hash_map, "wrap_separator"),
},
translate: Translate {
from: get_string(&mut hash_map, "translate_from"),
to: get_string(&mut hash_map, "translate_to"),
},
};
spec.encoding().unwrap()
}

fn check_empty(hash_map: HashMap<String, TokenNode>) {
if !hash_map.is_empty() {
panic!("Unexpected keys {:?}", hash_map.keys());
}
}

#[proc_macro]
pub fn internal_new_encoding(input: TokenStream) -> TokenStream {
let mut hash_map = parse_map(input.into_iter());
let encoding = get_encoding(&mut hash_map);
check_empty(hash_map);
format!("{:?}", encoding.internal_implementation()).parse().unwrap()
}

#[proc_macro]
pub fn internal_decode(input: TokenStream) -> TokenStream {
let mut hash_map = parse_map(input.into_iter());
let encoding = get_encoding(&mut hash_map);
check_present(&mut hash_map, "name");
let name = get_string(&mut hash_map, "name");
check_present(&mut hash_map, "input");
let input = get_string(&mut hash_map, "input");
check_empty(hash_map);
let output = encoding.decode(input.as_bytes()).unwrap();
format!("{}: [u8; {}] = {:?};", name, output.len(), output).parse().unwrap()
}
Loading

0 comments on commit 9e56afe

Please sign in to comment.