Skip to content

Commit 102b1d3

Browse files
committed
Support proc_macros
Refactoring code to support both declarative macros and procedure macros. Refer to a great blog post: https://blog.m-ou.se/writing-python-inside-rust-1/
1 parent f9a86d6 commit 102b1d3

File tree

16 files changed

+565
-375
lines changed

16 files changed

+565
-375
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ authors = ["rust-shell-script <[email protected]>"]
1313
edition = "2018"
1414

1515
[dependencies]
16+
cmd_lib_macros = { version = "0.1", path = "crates/macros" }
17+
# For nightly:
18+
# cmd_lib_macros = { version = "0.1", path = "crates/proc_macros" }
19+
cmd_lib_core = { version = "0.1", path = "crates/cmd_lib_core" }

crates/cmd_lib_core/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "cmd_lib_core"
3+
version = "0.1.0"
4+
authors = ["Tao Guo <[email protected]>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]

crates/cmd_lib_core/src/lib.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
mod parser;
2+
mod process;
3+
mod proc_env;
4+
mod proc_var;
5+
6+
pub type FunResult = std::io::Result<String>;
7+
pub type CmdResult = std::io::Result<()>;
8+
pub use proc_env::Env;
9+
pub use parser::Parser;
10+
11+
use std::collections::{HashMap, VecDeque};
12+
13+
pub fn run_cmd<S: Into<String>>(cmds: S) -> CmdResult {
14+
Parser::new(cmds.into()).parse().run_cmd()
15+
}
16+
17+
pub fn run_fun<S: Into<String>>(cmds: S) -> FunResult {
18+
Parser::new(cmds.into()).parse().run_fun()
19+
}
20+
21+
// APIs For proc_macros
22+
pub fn run_cmd_with_ctx(
23+
code: &str,
24+
fn_sym_table: impl FnOnce(&mut HashMap<&str, String>),
25+
fn_str_lits: impl FnOnce(&mut VecDeque<String>),
26+
) -> CmdResult {
27+
parse_cmds_with_ctx(code, fn_sym_table, fn_str_lits).run_cmd()
28+
}
29+
30+
pub fn run_fun_with_ctx(
31+
code: &str,
32+
fn_sym_table: impl FnOnce(&mut HashMap<&str, String>),
33+
fn_str_lits: impl FnOnce(&mut VecDeque<String>),
34+
) -> FunResult {
35+
parse_cmds_with_ctx(code, fn_sym_table, fn_str_lits).run_fun()
36+
}
37+
38+
fn parse_cmds_with_ctx(
39+
code: &str,
40+
fn_sym_table: impl FnOnce(&mut HashMap<&str, String>),
41+
fn_str_lits: impl FnOnce(&mut VecDeque<String>),
42+
) -> crate::process::GroupCmds {
43+
let mut sym_table = HashMap::new();
44+
fn_sym_table(&mut sym_table);
45+
46+
let mut str_lits = VecDeque::new();
47+
fn_str_lits(&mut str_lits);
48+
49+
Parser::new(code)
50+
.with_sym_table(sym_table)
51+
.with_lits(str_lits)
52+
.parse()
53+
}
54+
55+
#[cfg(test)]
56+
mod tests {
57+
use super::*;
58+
59+
#[test]
60+
fn test_optional_args() {
61+
let show_all = true;
62+
let show_details = true;
63+
64+
let mut ls_opts = String::new();
65+
if show_all {
66+
ls_opts += " -a";
67+
}
68+
if show_details {
69+
ls_opts += " -l";
70+
}
71+
let ls_cmd = format!(r#"ls {} | grep "\.\.$" | awk "{{print $9}}""#, ls_opts);
72+
assert_eq!(run_fun(ls_cmd).unwrap(), "..");
73+
}
74+
}

src/parser.rs renamed to crates/cmd_lib_core/src/parser.rs

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,10 @@
11
use std::collections::{VecDeque, HashMap};
22
use crate::process::{GroupCmds, Cmds, Cmd, FdOrFile};
33

4-
#[doc(hidden)]
5-
#[macro_export]
6-
macro_rules! parse_string_literal {
7-
(&$sl:expr;) => {
8-
$sl
9-
};
10-
(&$sl:expr; - $($other:tt)*) => {
11-
$crate::parse_string_literal!{&$sl; $($other)*}
12-
};
13-
(&$sl:expr; $cur:literal $($other:tt)*) => {
14-
let s = stringify!($cur);
15-
// only save string literals
16-
if s.starts_with("\"") || s.starts_with("r") {
17-
$sl.push_back($cur.to_string());
18-
}
19-
$crate::parse_string_literal!{&$sl; $($other)*}
20-
};
21-
(&$sl:expr; $cur:tt $($other:tt)*) => {
22-
$crate::parse_string_literal!{&$sl; $($other)*}
23-
};
24-
($cur:tt $($other:tt)*) => {{
25-
let mut __str_lits = std::collections::VecDeque::<String>::new();
26-
$crate::parse_string_literal!{&__str_lits; $cur $($other)*}
27-
}};
28-
}
29-
304
#[doc(hidden)]
315
pub struct Parser {
326
str_lits: Option<VecDeque<String>>,
33-
sym_table: Option<HashMap<String, String>>,
7+
sym_table: Option<HashMap<&'static str, String>>,
348

359
file: &'static str,
3610
line: u32,
@@ -54,7 +28,7 @@ impl Parser {
5428
self
5529
}
5630

57-
pub fn with_sym_table(&mut self, sym_table: HashMap<String, String>) -> &mut Self {
31+
pub fn with_sym_table(&mut self, sym_table: HashMap<&'static str, String>) -> &mut Self {
5832
self.sym_table = Some(sym_table);
5933
self
6034
}
@@ -65,6 +39,47 @@ impl Parser {
6539
self
6640
}
6741

42+
fn resolve_name(src: &str, sym_table: &HashMap<&'static str, String>, file: &str, line: u32) -> String {
43+
let mut output = String::new();
44+
let input: Vec<char> = src.chars().collect();
45+
let len = input.len();
46+
47+
let mut i = 0;
48+
while i < len {
49+
if input[i] == '$' && (i == 0 || input[i - 1] != '\\') {
50+
i += 1;
51+
let with_bracket = i < len && input[i] == '{';
52+
let mut var = String::new();
53+
if with_bracket { i += 1; }
54+
while i < len
55+
&& ((input[i] >= 'a' && input[i] <= 'z')
56+
|| (input[i] >= 'A' && input[i] <= 'Z')
57+
|| (input[i] >= '0' && input[i] <= '9')
58+
|| (input[i] == '_'))
59+
{
60+
var.push(input[i]);
61+
i += 1;
62+
}
63+
if with_bracket {
64+
if input[i] != '}' {
65+
panic!("invalid name {}, {}:{}\n{}", var, file, line, src);
66+
}
67+
} else {
68+
i -= 1; // back off 1 char
69+
}
70+
match sym_table.get(var.as_str()) {
71+
None => panic!("resolve {} failed, {}:{}\n{}", var, file, line, src),
72+
Some(v) => output += v,
73+
};
74+
} else {
75+
output.push(input[i]);
76+
}
77+
i += 1;
78+
}
79+
80+
output
81+
}
82+
6883
pub fn parse(&mut self) -> GroupCmds {
6984
let mut ret = GroupCmds::new();
7085
let s: Vec<char> = self.src.chars().collect();
@@ -173,10 +188,7 @@ impl Parser {
173188

174189
let mut arg1 = self.parse_normal_arg(s, i);
175190
if let Some(sym_table) = self.sym_table.as_ref() {
176-
arg1 = crate::sym_table::resolve_name(&arg1,
177-
sym_table,
178-
&self.file,
179-
self.line);
191+
arg1 = Parser::resolve_name(&arg1, sym_table, &self.file, self.line);
180192
}
181193
arg += &arg1;
182194
}
@@ -260,10 +272,7 @@ impl Parser {
260272

261273
let mut file = self.parse_normal_arg(s, i);
262274
if let Some(sym_table) = self.sym_table.as_ref() {
263-
file = crate::sym_table::resolve_name(&file,
264-
sym_table,
265-
&self.file,
266-
self.line);
275+
file = Parser::resolve_name(&file, sym_table, &self.file, self.line);
267276
}
268277
FdOrFile::File(file, append)
269278
}
@@ -318,14 +327,11 @@ impl Parser {
318327
return str_lit;
319328
}
320329

321-
str_lit = self.str_lits.as_mut().unwrap().pop_front().unwrap();
330+
str_lit = self.str_lits.as_mut().unwrap().pop_front().unwrap().to_string();
322331
if is_raw {
323332
return str_lit; // don't resolve names for raw string literals
324333
} else {
325-
return crate::sym_table::resolve_name(&str_lit,
326-
self.sym_table.as_ref().unwrap(),
327-
&self.file,
328-
self.line);
334+
return Parser::resolve_name(&str_lit, self.sym_table.as_ref().unwrap(), &self.file, self.line);
329335
}
330336
}
331337
}
@@ -334,18 +340,6 @@ impl Parser {
334340
mod tests {
335341
use super::*;
336342

337-
#[test]
338-
fn test_parse_string_literal() {
339-
let str_lits1 = parse_string_literal!(ls "/tmp" "/");
340-
assert_eq!(str_lits1, ["/tmp", "/"]);
341-
342-
let str_lits2 = parse_string_literal!(ping -c 3 r"127.0.0.1");
343-
assert_eq!(str_lits2, ["127.0.0.1"]);
344-
345-
let str_lits3 = parse_string_literal!(echo r#"rust"cmd_lib"#);
346-
assert_eq!(str_lits3, ["rust\"cmd_lib"]);
347-
}
348-
349343
#[test]
350344
fn test_parser_or_cmd() {
351345
assert!(Parser::new("ls /nofile || true; echo continue")
File renamed without changes.
File renamed without changes.

src/process.rs renamed to crates/cmd_lib_core/src/process.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use std::io::{Error, ErrorKind};
33
use std::fs::{File, OpenOptions};
44
use std::os::unix::io::{FromRawFd, AsRawFd};
55
use std::collections::HashSet;
6-
use crate::{CmdResult, FunResult, Env};
6+
use crate::proc_env::Env;
77
use crate::proc_env::ENV_VARS;
8+
use crate::{CmdResult, FunResult};
89

910
pub struct GroupCmds {
1011
cmds: Vec<(Cmds, Option<Cmds>)>, // (cmd, orCmd) pairs

crates/macros/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "cmd_lib_macros"
3+
version = "0.1.0"
4+
authors = ["Tao Guo <[email protected]>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
cmd_lib_core = { version = "0.1", path = "../cmd_lib_core" }

0 commit comments

Comments
 (0)