diff options
| author | Federico Igne <git@federicoigne.com> | 2022-04-27 21:56:42 +0100 |
|---|---|---|
| committer | Federico Igne <git@federicoigne.com> | 2022-05-27 00:46:07 +0100 |
| commit | 394621b3a2290e363a60b372e07ad19c47105ff1 (patch) | |
| tree | c9171402f3cf9cf521b10071f821916c6630c541 | |
| parent | cd1dbb23feb4115267c5bc80fb3aa206daf866ea (diff) | |
| download | pangler-394621b3a2290e363a60b372e07ad19c47105ff1.tar.gz pangler-394621b3a2290e363a60b372e07ad19c47105ff1.zip | |
docs: add some notes on recent lifetime learnings
| -rw-r--r-- | src/main.rs | 81 |
1 files changed, 58 insertions, 23 deletions
diff --git a/src/main.rs b/src/main.rs index d03d5f8..a8b8198 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -9,34 +9,66 @@ use pandoc_ast::Block; | |||
| 9 | 9 | ||
| 10 | type Blocks<'a> = HashMap<String,Cow<'a,str>>; | 10 | type Blocks<'a> = HashMap<String,Cow<'a,str>>; |
| 11 | 11 | ||
| 12 | fn build_block(blocks: &Blocks, code: &Cow<str>) -> String { | 12 | /* |
| 13 | * Here are some notes on the following function | ||
| 14 | * | ||
| 15 | * lazy_static! { | ||
| 16 | * static ref MACRO: Regex = Regex::new(r"regex").unwrap(); | ||
| 17 | * } | ||
| 18 | * | ||
| 19 | * let mut text = Cow::from("This is some text..."); | ||
| 20 | * | ||
| 21 | * The problem with this version is that due to how `Cow` works, the value returned by | ||
| 22 | * `replace_all` cannot live more than the borrowed `text` passed as a parameter. This is | ||
| 23 | * because the function returns a reference to `text` (Cow::Borrowed) if no replacement takes | ||
| 24 | * place, so for the returned value to be valid, `text` still needs to be available. | ||
| 25 | * But text gets overridden right away, so, in principle, if no replacement takes place `text` | ||
| 26 | * gets overridden by a reference to it (losing data). | ||
| 27 | * | ||
| 28 | * Note that this doesn't happen in practice (but the compiler doesn't know about this) because | ||
| 29 | * the `replace_all` function is applied as long as some replacement is possible (`while` | ||
| 30 | * condition). In other words, all calls to `replace_all` always return an `Cow::Owned` value. | ||
| 31 | * | ||
| 32 | * while MACRO.is_match(&text) { | ||
| 33 | * text = MACRO.replace_all(&text, _closure); | ||
| 34 | * } | ||
| 35 | * | ||
| 36 | * This is how you would solve the problem instead: | ||
| 37 | * | ||
| 38 | * while let Cow::Owned(new_text) = MACRO.replace_all(&text, _closure) { | ||
| 39 | * text = Cow::from(new_text); | ||
| 40 | * } | ||
| 41 | * | ||
| 42 | * In this case, the matched `Cow::Owned` is not concerned by any lifetime (type is `Cow<'_,str>`) | ||
| 43 | * of the borrowed value `text`. Moreover `text` takes ownership of `new_text: String` using | ||
| 44 | * the `Cow::from()` function. No heap allocation is performed, and the string is not copied. | ||
| 45 | * | ||
| 46 | * println!("{}", text) | ||
| 47 | * | ||
| 48 | */ | ||
| 49 | fn build(blocks: &Blocks) { | ||
| 13 | lazy_static! { | 50 | lazy_static! { |
| 51 | static ref PATH: Regex = Regex::new(r"^(?:[[:word:]\.-]+/)*[[:word:]\.-]+\.[[:alpha:]]+$").unwrap(); | ||
| 14 | static ref MACRO: Regex = Regex::new(r"(?m)^([[:blank:]]*)<<([^>\s]+)>>").unwrap(); | 52 | static ref MACRO: Regex = Regex::new(r"(?m)^([[:blank:]]*)<<([^>\s]+)>>").unwrap(); |
| 15 | } | 53 | } |
| 16 | if MACRO.is_match(&code) { | 54 | blocks.iter().for_each(|(k,v)| if PATH.is_match(k) { |
| 17 | let code = MACRO.replace_all(&code, |caps: &Captures| { | 55 | let mut code = v.clone(); // No clone is happening because the value is a `Borrowed` |
| 56 | // Here `replace_all` returns a `Owned` value only when a replacement takes place. | ||
| 57 | // We can use it to recursively build blocks of code until no more substitutions are | ||
| 58 | // necessary (i.e., `replace_all` returns a `Borrowed`). | ||
| 59 | while let Cow::Owned(step) = MACRO.replace_all(&code, |caps: &Captures| { | ||
| 18 | let indent = caps[1].len(); | 60 | let indent = caps[1].len(); |
| 19 | blocks.get(&caps[2]) | 61 | blocks.get(&caps[2]) |
| 20 | .expect("Block not present") | 62 | .expect("Block not present") |
| 21 | .lines() | 63 | .lines() |
| 22 | .map(|l| format!("{:indent$}{}", "", l) ) | 64 | .map(|l| format!("{:indent$}{}", "", l) ) |
| 23 | .collect::<Vec<_>>() | 65 | .collect::<Vec<_>>() |
| 24 | .join("\n") | 66 | .join("\n") |
| 25 | } | 67 | } |
| 26 | ); | 68 | ) { |
| 27 | build_block(blocks, &code) | 69 | code = Cow::from(step); |
| 28 | } else { | 70 | } |
| 29 | code.to_string() | 71 | println!("[[{}]]\n{}", k, code); |
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | fn build(blocks: &Blocks) { | ||
| 34 | lazy_static! { | ||
| 35 | static ref PATH: Regex = Regex::new(r"^(?:[[:word:]\.-]+/)+[[:word:]\.-]+\.[[:alpha:]]+$").unwrap(); | ||
| 36 | } | ||
| 37 | blocks.iter().for_each(|(k,v)| if PATH.is_match(k) { | ||
| 38 | let string = build_block(blocks, v); | ||
| 39 | println!("[[{}]]\n{}", k, string); | ||
| 40 | }) | 72 | }) |
| 41 | } | 73 | } |
| 42 | 74 | ||
| @@ -60,7 +92,10 @@ fn main() -> Result<()> { | |||
| 60 | .find_map(|(k,v)| if k == "path" { Some(v.clone()) } else { None }) | 92 | .find_map(|(k,v)| if k == "path" { Some(v.clone()) } else { None }) |
| 61 | .unwrap_or(String::from("")); | 93 | .unwrap_or(String::from("")); |
| 62 | key.push_str(&attr.0); | 94 | key.push_str(&attr.0); |
| 63 | /* Insert (or replace) block of code */ | 95 | /* Insert (or replace) block of code. |
| 96 | * NOTE: we are always "borrowing" the snippets of code with `Cow`, i.e., it | ||
| 97 | * will always be a `Cow::Borrowed` value, until we process the string | ||
| 98 | * substituting macros. */ | ||
| 64 | blocks.insert(key, Cow::from(code)); | 99 | blocks.insert(key, Cow::from(code)); |
| 65 | } else { | 100 | } else { |
| 66 | // println!("The following code has no ID:"); | 101 | // println!("The following code has no ID:"); |
