Skip to content
Snippets Groups Projects
Commit 0f379d2e authored by Per Lindgren's avatar Per Lindgren
Browse files

lit, lite

parent 785c229f
No related branches found
No related tags found
No related merge requests found
...@@ -5,8 +5,20 @@ ...@@ -5,8 +5,20 @@
"tasks": [ "tasks": [
{ {
"type": "shell", "type": "shell",
"label": "cargo run --example ex1", "label": "cargo run --example ex_lit",
"command": "cargo run --example ex1", "command": "cargo run --example ex_lit",
"problemMatcher": [
"$rustc"
],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "shell",
"label": "cargo run --example ex_lite",
"command": "cargo run --example ex_lite",
"problemMatcher": [ "problemMatcher": [
"$rustc" "$rustc"
], ],
......
...@@ -4,67 +4,71 @@ Disclaimer, I'm NOT the author of the `syn` and `nom` parsing frameworks, so no ...@@ -4,67 +4,71 @@ Disclaimer, I'm NOT the author of the `syn` and `nom` parsing frameworks, so no
The same goes for all references to supporting crates and references to other Rust libraries, crates and the language itself, so use material at own risk :) The same goes for all references to supporting crates and references to other Rust libraries, crates and the language itself, so use material at own risk :)
## Parsing a `LitInt` and rejecting invalid range ## Built in parsers
As a simple example let us start out with `mylit!(LitInt)`, where
The `syn::parse` function allows to parse any struct that implements the `syn::synom::Synom` trait. The `syn` crate provides a large set of structs which satisfies this requirement, which we can use directly or for building parser combinators.
We can invoke custom parsers through procedural macros in Rust.
As a simple example let us start out with simple macro `lit!` parsing a `LitInt`, where
`LitInt` should be an intereger literal `x` in the range `10 <= x < 100`. `LitInt` should be an intereger literal `x` in the range `10 <= x < 100`.
Consider the following program (`ex1.rs`): Consider the following program (`ex_lit.rs`):
``` rust ``` rust
#![feature(proc_macro)] #![feature(proc_macro)]
extern crate parsetest; extern crate parsetest;
use parsetest::mylit; use parsetest::lit;
fn main() { fn main() {
// should pass // should pass
let _v = mylit!(99); let _v = lit!(99);
// should be rejected // should be rejected
let _v = mylit!(102); let _v = lit!(102);
// should be rejected // should be rejected
let _v = mylit!(9o9); let _v = lit!(9o9);
// should be rejected // should be rejected
let _v = mylit!((99)); let _v = lit!((99));
} }
``` ```
The expected outcome for `mylit!` is: The expected outcome for `lit!` is:
``` shell ``` shell
> Executing task: cargo run --example ex1 < > Executing task: cargo run --example ex_lit <
Compiling parsetest v0.1.0 (file:///home/pln/course/parsetest) Compiling parsetest v0.1.0 (file:///home/pln/rtfm/parsetest)
error: expected literal 10 <= x < 100, got 102 error: expected literal 10 <= x < 100, got 102
--> examples/ex1.rs:11:21 --> examples/ex_lit.rs:11:19
| |
11 | let _v = mylit!(102); 11 | let _v = lit!(102);
| ^^^ | ^^^
error: expected literal 10 <= x < 100, got 9 error: expected literal 10 <= x < 100, got 9
--> examples/ex1.rs:14:21 --> examples/ex_lit.rs:14:19
| |
14 | let _v = mylit!(9o9); 14 | let _v = lit!(9o9);
| ^^^ | ^^^
error: invalid suffix `o9` for numeric literal error: invalid suffix `o9` for numeric literal
--> examples/ex1.rs:14:21 --> examples/ex_lit.rs:14:19
| |
14 | let _v = mylit!(9o9); 14 | let _v = lit!(9o9);
| ^^^ | ^^^
| |
= help: the suffix must be one of the integral types (`u32`, `isize`, etc) = help: the suffix must be one of the integral types (`u32`, `isize`, etc)
error: proc macro panicked error: proc macro panicked
--> examples/ex1.rs:17:14 --> examples/ex_lit.rs:17:14
| |
17 | let _v = mylit!((99)); 17 | let _v = lit!((99));
| ^^^^^^^^^^^^ | ^^^^^^^^^^
| |
= help: message: called `Result::unwrap()` on an `Err` value: ParseError(None) = help: message: called `Result::unwrap()` on an `Err` value: ParseError(Some("failed to parse integer literal: failed to parse"))
error: Could not compile `parsetest`. error: Could not compile `parsetest`.
``` ```
...@@ -87,6 +91,108 @@ use quote::ToTokens; ...@@ -87,6 +91,108 @@ use quote::ToTokens;
use std::convert::From; use std::convert::From;
/// Procedural macro `lit`
/// Parsing a LinInt with compile time range checking
#[proc_macro]
pub fn lit(input: TokenStream) -> TokenStream {
let v: LitInt = syn::parse(input).unwrap();
let value = v.value();
if !(10 <= value && value < 100) {
v.span()
.unstable()
.error(format!("expected literal 10 <= x < 100, got {}", value,))
.emit();
}
From::from(v.into_tokens())
}
```
The `syn::LitInt` type implement the `syn::synom::Synom` trait (required by the `syn::parse` function). The unwrapped result is of type `LitInt`, for which we can get the inner value representation (a `u64`).
In this example we use that to (at compile time) ensure that the value is within a given range, (between 10 and 100), and reject faulty assignment with a sensible error message.
The error message is presented as a `TokenStream` throught the `.emit()` function. This does NOT imply an immediate abort (unlike a `panic!`), thus rustc will continue compilation and catch all errors.
For the first case (`lit!(99)`) the parsing succeeded and the returned token stream would amount to the literate integer 99. As our `v` is a `LitInt` we need to convert it into a `TokenStream`, a two stage process by first turning `v` into tokens, from wich we get a token stream. (You may comment out the failing parts and print the value of `_v` to convince yourself.)
For the second and third cases, we see that appropriate errors and spans are reported by the built in parser. However the last case is somewhat unsatisfying, due to the `syn::parse` failing (unwrap on an `Err`). We can handle that as well as follows, for the example `ex_lite.rs`.
``` rust
#![feature(proc_macro)]
extern crate parsetest;
use parsetest::lite;
fn main() {
// should be rejected
let _v = lite!((99));
}
```
With the improved error reporting, we can get.
``` shell
> Executing task: cargo run --example ex_lite <
Compiling parsetest v0.1.0 (file:///home/pln/rtfm/parsetest)
error: failed to parse integer literal: failed to parse
--> examples/ex_lite.rs:8:14
|
8 | let _v = lite!((99));
| ^^^^^^^^^^^
error: aborting due to previous error
error: Could not compile `parsetest`.
```
The backing implementation now matches the parsed result and inserts a `compiler_error!`, instead of the previous unwrap.
``` rust
#![feature(proc_macro)]
extern crate proc_macro;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
use proc_macro::TokenStream;
use syn::spanned::Spanned;
use syn::synom::Synom;
use syn::LitInt;
use quote::ToTokens;
use std::convert::From;
use std::error::Error;
/// Procedural macro `lite`
/// Parsing a LinInt with compile time range checking
#[proc_macro]
pub fn lite(input: TokenStream) -> TokenStream {
match syn::parse::<LitInt>(input) {
Ok(v) => {
let value = v.value();
if !(10 <= value && value < 100) {
v.span()
.unstable()
.error(format!("expected literal 10 <= x < 100, got {}", value,))
.emit();
}
From::from(v.into_tokens())
}
Err(err) => {
let desc = err.description();
let tokens = quote! {
compile_error!(#desc)
};
return tokens.into();
}
```
The description here is the default error, provided by the built in `LitInt` parser.
### Direct approach
/// MyLit /// MyLit
struct MyLit { struct MyLit {
val: LitInt, val: LitInt,
...@@ -155,6 +261,3 @@ pub fn mylit(input: TokenStream) -> TokenStream { ...@@ -155,6 +261,3 @@ pub fn mylit(input: TokenStream) -> TokenStream {
} }
``` ```
Besides just parsing an `IntLit` (that is a `u64`), we want at compile time to ensure that it is in a given range, (between 10 and 100 in this example), and report a sensible error message for illegal usage.
The error message is presented as a `TokenStream` throught the `.emit()` function.
\ No newline at end of file
#![feature(proc_macro)]
extern crate parsetest;
use parsetest::lit;
fn main() {
// should pass
let _v = lit!(99);
// should be rejected
let _v = lit!(102);
// should be rejected
let _v = lit!(9o9);
// should be rejected
let _v = lit!((99));
}
#![feature(proc_macro)]
extern crate parsetest;
use parsetest::lite;
fn main() {
// should be rejected
let _v = lite!((99));
}
#![feature(proc_macro)] #![feature(proc_macro)]
extern crate proc_macro; extern crate proc_macro;
// #[macro_use] #[macro_use]
extern crate quote; extern crate quote;
#[macro_use] #[macro_use]
extern crate syn; extern crate syn;
...@@ -13,7 +13,47 @@ use syn::LitInt; ...@@ -13,7 +13,47 @@ use syn::LitInt;
use quote::ToTokens; use quote::ToTokens;
use std::convert::From; use std::convert::From;
// use std::error::Error; use std::error::Error;
/// Procedural macro `lit`
/// Parsing a LinInt with compile time range checking
#[proc_macro]
pub fn lit(input: TokenStream) -> TokenStream {
let v: LitInt = syn::parse(input).unwrap();
let value = v.value();
if !(10 <= value && value < 100) {
v.span()
.unstable()
.error(format!("expected literal 10 <= x < 100, got {}", value,))
.emit();
}
From::from(v.into_tokens())
}
/// Procedural macro `lite`
/// Parsing a LinInt with compile time range checking
#[proc_macro]
pub fn lite(input: TokenStream) -> TokenStream {
match syn::parse::<LitInt>(input) {
Ok(v) => {
let value = v.value();
if !(10 <= value && value < 100) {
v.span()
.unstable()
.error(format!("expected literal 10 <= x < 100, got {}", value,))
.emit();
}
From::from(v.into_tokens())
}
Err(err) => {
let desc = err.description();
let tokens = quote! {
compile_error!(#desc)
};
return tokens.into();
}
}
}
/// MyLit /// MyLit
struct MyLit { struct MyLit {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment