diff --git a/Cargo.lock b/Cargo.lock index 362f97f..708ba6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,82 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + [[package]] name = "makeutil" version = "0.1.0" dependencies = [ - "nom", + "pest", + "pest_derive", + "regex", ] [[package]] @@ -16,17 +87,165 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "nom" -version = "7.1.3" +name = "pest" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", - "minimal-lexical", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +dependencies = [ + "pest", + "pest_generator", ] + +[[package]] +name = "pest_generator" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/Cargo.toml b/Cargo.toml index 7a8ead8..669ac40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nom = "7.1.3" +pest = "2.7.10" +pest_derive = "2.7.10" +regex = "1.10.5" diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..1d3d165 --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,9 @@ +#[derive(Debug)] +pub enum Term<'a> { + Var(&'a str, &'a str, &'a str), + Task { + name: &'a str, + deps: Vec<&'a str>, + body: Vec<&'a str>, + }, +} diff --git a/src/main.rs b/src/main.rs index c5c7ec2..53645b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,209 +1,201 @@ -use post::split_kind; - -fn main() { - let sample = include_str!("../Makefile"); - let (_, terms) = grammar::makefile(&sample).unwrap(); - let (tasks, vars) = split_kind(terms); - for (k, v) in tasks.iter() { - println!("{}: {}\n{}", k, v.deps.join(", "), v.body) - } - // println!("{:#?}", tasks); -} - -mod post { - use std::collections::HashMap; +use std::{collections::HashMap, env, fs, io}; + +use ast::Term; +use pest::{iterators::Pair, Parser}; +use pest_derive::Parser; +use regex::{Captures, Regex}; + +pub mod ast; + +#[derive(Parser)] +#[grammar = "makefile.pest"] +pub struct MakefileParser; + +fn parse(data: &str) -> Vec { + let file = MakefileParser::parse(Rule::makefile, data) + .unwrap() + .next() + .unwrap(); + + let mut out = vec![]; + for term in file.into_inner() { + match term.as_rule() { + Rule::task => { + let mut inner_rules = term.into_inner(); + let name = inner_rules.next().unwrap().as_str(); + + let mut deps = vec![]; + let mut body = vec![]; + + for t in inner_rules { + match t.as_rule() { + Rule::body => body = t.into_inner().map(|v| v.as_str()).collect(), + Rule::deps => deps = t.into_inner().map(|v| v.as_str()).collect(), + _ => (), + } + } - use crate::grammar::{self, Term}; + out.push(Term::Task { name, deps, body }) + } + Rule::var => { + let inner_rules = term.into_inner(); + let [name, eq, val]: [Pair<'_, Rule>; 3] = + inner_rules.collect::>().try_into().unwrap(); - #[derive(Debug, PartialEq, Eq)] - pub struct Variable<'a> { - pub name: &'a str, - pub value: &'a str, - pub eq: &'a str, + out.push(Term::Var(name.as_str(), eq.as_str(), val.as_str())) + } + Rule::EOI => (), + _ => unreachable!(), + } } - #[derive(Debug, PartialEq, Eq)] - pub struct Task<'a> { - pub phony: bool, - pub name: &'a str, - pub deps: Vec<&'a str>, - pub body: &'a str, - } + return out; +} - fn convert_task<'a>(phonies: &Vec<&str>, task: grammar::Task<'a>) -> Task<'a> { - assert!(task.name != ".PHONY"); - Task { - phony: phonies.contains(&task.name), - name: task.name, - deps: task.deps, - body: task.body, - } - } +#[derive(Debug)] +struct Var<'a>(&'a str, &'a str, &'a str); +#[derive(Debug)] +struct Task<'a> { + phony: bool, + name: &'a str, + deps: Vec<&'a str>, + body: Vec<&'a str>, +} - fn convert_variable(var: grammar::Variable) -> Variable { - Variable { - name: var.name, - value: var.value, - eq: var.eq, - } +fn task_from_ast<'a>( + phonies: &Vec<&str>, + name: &'a str, + deps: Vec<&'a str>, + body: Vec<&'a str>, +) -> Task<'a> { + Task { + phony: phonies.contains(&name), + name, + deps, + body, } +} - pub fn split_kind<'a>( - terms: Vec>, - ) -> (HashMap<&'a str, Task>, HashMap<&'a str, Variable>) { - let mut tasks = HashMap::new(); - let mut vars = HashMap::new(); - let phonies = terms +fn from_ast(terms: Vec) -> (HashMap<&str, Task>, HashMap<&str, Var>) { + let empty = vec![]; + let phonies: Vec<&str> = { + terms .iter() - .filter_map(|t| match t { - Term::Task(task) if task.name == ".PHONY" => Some(task.deps.clone()), - _ => None, + .flat_map(|t| match t { + Term::Task { + name: ".PHONY", + deps, + .. + } => deps, + _ => &empty, }) - .flatten() - .collect(); + .map(|v| *v) + .collect() + }; - for term in terms { - match term { - Term::Variable(v) => { - vars.insert(v.name, convert_variable(v)); - } - Term::Task(t) if t.name == ".PHONY" => {} - Term::Task(t) if tasks.contains_key(t.name) => { - let out: &mut Task = tasks.get_mut(t.name).unwrap(); - out.deps.append(&mut t.deps.clone()); - if !t.body.is_empty() { - out.body = t.body; + let mut tasks = HashMap::new(); + let mut vars = HashMap::new(); + + for t in terms { + match t { + Term::Var(name, eq, val) => { + vars.insert(name, Var(name, eq, val)); + } + Term::Task { + name, + mut deps, + body, + } if name != ".PHONY" => { + match tasks.get_mut(name) { + Some(t) => { + let t: &mut Task = t; + t.deps.append(&mut deps); } - } - // tasks[t.name] = convert_task(phonies, t) - Term::Task(t) => { - tasks.insert(t.name, convert_task(&phonies, t)); - } + None => { + tasks.insert(name, task_from_ast(&phonies, name, deps, body)); + } + }; } + _ => (), } - - (tasks, vars) } -} -mod grammar { - use nom::{ - branch::alt, - bytes::complete::{is_a, is_not, tag, take_until}, - character::complete::{alpha1, alphanumeric1, multispace0, newline, one_of}, - combinator::{recognize, value}, - error::ParseError, - multi::{many0, many0_count, many1, separated_list0}, - sequence::{delimited, pair, preceded, terminated, tuple}, - IResult, Parser, - }; - - #[derive(Debug, PartialEq, Eq)] - pub struct Variable<'a> { - pub name: &'a str, - pub value: &'a str, - pub eq: &'a str, - } - - #[derive(Debug, PartialEq, Eq)] - pub struct Task<'a> { - pub name: &'a str, - pub deps: Vec<&'a str>, - pub body: &'a str, - } + (tasks, vars) +} - #[derive(Debug, PartialEq, Eq)] - pub enum Term<'a> { - Variable(Variable<'a>), - Task(Task<'a>), - } +struct IDGen { + uuid: i64, +} - fn identifier(input: &str) -> IResult<&str, &str> { - let allowed_symbols = b"asd"; - recognize(pair( - alt((alpha1, recognize(one_of("_.-")))), - many0_count(alt((alphanumeric1, recognize(one_of("_.-"))))), - )) - .parse(input) +impl IDGen { + fn new() -> Self { + IDGen { uuid: 0 } } + fn next(&mut self) -> String { + let id = self.uuid; + self.uuid += 1; - fn comment<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, &str, E> { - recognize(pair(tag("#"), is_not("\n\r"))).parse(input) + format!("id{}", self.uuid) } +} - fn ws<'a, F, O, E>(inner: F) -> impl Parser<&'a str, O, E> - where - F: Parser<&'a str, O, E>, - E: ParseError<&'a str>, - { - delimited(multispace0, inner, multispace0) - } +struct SubGraph { + name: String, + nodes: Vec, +} - fn line<'a, F, O, E>(inner: F) -> impl Parser<&'a str, O, E> - where - F: Parser<&'a str, O, E>, - E: ParseError<&'a str>, - { - terminated(inner, tuple((take_until("\n"), tag("\n")))) - } +struct Node { + id: String, + label: String, + children: Vec, +} - fn wsenl<'a, F, O, E>(inner: F) -> impl Parser<&'a str, O, E> - where - F: Parser<&'a str, O, E>, - E: ParseError<&'a str>, - { - let wsn = value((), many0(alt((tag(" "), tag("\t"), tag("\\\n"))))); - preceded(wsn, inner) +impl Node { + fn new(id_provider: &mut IDGen, label: String) -> Self { + Self { + id: id_provider.next(), + label, + children: vec![], + } } +} - fn ignore<'a, F, O, E>(inner: F) -> impl Parser<&'a str, O, E> - where - F: Parser<&'a str, O, E>, - E: ParseError<&'a str>, - { - delimited(multispace0, inner, ws(comment)) - } +fn main() -> io::Result<()> { + let re_var = Regex::new(r"\$\{\s*(\w+)\s*\}").unwrap(); - fn variable(input: &str) -> IResult<&str, Term> { - let eq = alt((tag("="), tag("?="))); - tuple((identifier, ws(eq), is_not("#\n\r"))) - .map(|(name, eq, value)| Term::Variable(Variable { name, value, eq })) - .parse(input) - } + let args: Vec = env::args().collect(); + assert!(args.len() == 2); + let path: &str = &args[1]; - fn task(input: &str) -> IResult<&str, Term> { - let deps = line(separated_list0(wsenl(tag(",")), identifier)); - let indent = many1(alt((tag("\t"), tag(" ")))); - let body = recognize(many0(line(preceded(indent, is_not("\n"))))); - tuple((identifier, ws(tag(":")), deps, body)) - .map(|(name, _, deps, body)| Term::Task(Task { name, deps, body })) - .parse(input) - } + let file = fs::read_to_string(path)?; + let terms = parse(&file); + let (tasks, vars) = from_ast(terms); - fn term(input: &str) -> IResult<&str, Term> { - let (mut rest, out) = ws(alt((variable, task))).parse(input)?; - if let Ok((r, _)) = comment::>(rest) { - rest = r; - }; - Ok((rest, out)) + println!("strict graph {{"); + for (_, task) in tasks { + for dep in task.deps { + println!("\t\"{}\" -- \"{}\"", task.name, dep); + } + let external_deps = task.body.iter().filter(|l| l.contains("@make")); + for dep in external_deps { + let out = re_var.replace(dep, |caps: &Captures| match vars.get(&caps[1]) { + Some(v) => v.2.to_string(), + None => caps[0].to_string(), + }); + println!("\t# {}", out); + } } + println!("}}"); - pub fn makefile(input: &str) -> IResult<&str, Vec> { - many0(term).parse(input) - } + Ok(()) +} - #[cfg(test)] - mod test { - use super::*; - - #[test] - fn test_identifier() { - assert_eq!(identifier("foo"), Ok(("", "foo"))); - assert_eq!(identifier("foo_bar"), Ok(("", "foo_bar"))); - assert_eq!(identifier("foo123"), Ok(("", "foo123"))); - assert_eq!(identifier("_foo"), Ok(("", "_foo"))); - assert_eq!(identifier("_foo_bar"), Ok(("", "_foo_bar"))); - assert_eq!(identifier("_foo123"), Ok(("", "_foo123"))); - } +#[test] +fn calculator1() { + let parser = |v| MakefileParser::parse(Rule::var, v); + let inputs = ["CLUSTER_NAME ?= asd", "REGISTRY_NAME = 123"]; + for input in inputs { + let out = parser(input); + println!("Here: {:#?}", out.unwrap()) } } diff --git a/src/makefile.pest b/src/makefile.pest new file mode 100644 index 0000000..c485d72 --- /dev/null +++ b/src/makefile.pest @@ -0,0 +1,19 @@ +makefile = { SOI ~ (task | var | empty)* ~ EOI } + +task = { ident ~ ":" ~ deps ~ comment? ~ eol ~ body } +deps = {(ident ~ ws*)*} +var = { ident ~ eq ~ anyRest ~ comment? ~ eol } + +empty = _{ comment? ~ NEWLINE } + +body = { ("\t" ~ line ~ eol)* } +line = { (!NEWLINE ~ ANY)+ } +anyRest = { (!NEWLINE ~ !"#" ~ ANY)+ } +comment = _{ "#" ~ (!NEWLINE ~ ANY)+ } + +ident = @{ ('a'..'z' | 'A'..'Z' | '0'..'9' | "_" | "-" | ".")+ } +eq = { "=" | "?=" } + +WHITESPACE = _{ " " | "\\\n" } +ws = _{ " " | "\t" | "\\\n" } +eol = _{ NEWLINE | EOI }