commit c5de16c57b82f6abd2a6ebbe2cbed524347bac05 from: Benjamin Stürz date: Wed Jun 26 21:58:12 2024 UTC import commit - /dev/null commit + c5de16c57b82f6abd2a6ebbe2cbed524347bac05 blob - /dev/null blob + ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba (mode 644) --- /dev/null +++ .gitignore @@ -0,0 +1 @@ +/target blob - /dev/null blob + 1a84a0966df721c2c027591b4b9ac8d6913f5d73 (mode 644) --- /dev/null +++ Cargo.lock @@ -0,0 +1,212 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "dotvis" +version = "0.1.0" +dependencies = [ + "pest", + "pest_derive", +] + +[[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 = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "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 = "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" blob - /dev/null blob + 38731a70a889e12000e18791b8a66394d668c4c6 (mode 644) --- /dev/null +++ Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "dotvis" +version = "0.1.0" +edition = "2021" + +[dependencies] +pest = "2.7.10" +pest_derive = "2.7.10" blob - /dev/null blob + fe0326b6a5a875e13318d38ff247c1946ae7f93e (mode 644) --- /dev/null +++ src/ast.rs @@ -0,0 +1,77 @@ +use std::fmt::{self, Display, Formatter}; +use std::collections::BTreeMap; + + +#[derive(Default, Debug)] +pub struct Node { + pub end: bool, +} + +#[derive(Debug)] +pub struct Edge { + pub from: String, + pub to: String, + pub chars: Vec, +} + +#[derive(Debug)] +pub struct Machine { + pub nodes: BTreeMap, + pub edges: Vec, + pub begin: String, + pub current: String, + pub consumed: Vec, +} + +impl Machine { + pub fn feed(&mut self, ch: char) -> bool { + let next = self + .edges + .iter() + .find(|e| e.from == self.current && e.chars.contains(&ch)) + .map(|e| e.to.clone()); + + match next { + Some(next) => { + self.current = next; + self.consumed.push(ch); + true + }, + None => false, + } + } +} + +impl Display for Edge { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "\"{}\" -> \"{}\" [label=\"{:?}\"];", self.from, self.to, self.chars) + } +} + +impl Display for Machine { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "digraph {{")?; + + for e in &self.edges { + writeln!(f, "\t{e}")?; + } + + writeln!(f, "\t__begin -> {};", self.begin)?; + + writeln!(f)?; + writeln!(f, "\t__begin [style=\"invis\"];")?; + writeln!(f, "\t\"{}\" [style=\"filled\", fillcolor=\"green\"];", self.current)?; + + for (name, node) in &self.nodes { + let shape = match node { + Node { end: true } => "doublecircle", + Node { end: false } => "circle", + }; + writeln!(f, "\t\"{name}\" [shape=\"{shape}\"];")?; + } + + writeln!(f, "\t__consumed [shape=\"box\", label=\"consumed: {:?}\"];", self.consumed)?; + + writeln!(f, "}}") + } +} blob - /dev/null blob + c4beed8fb117a4dc6b30bcfcf83720a57f991308 (mode 644) --- /dev/null +++ src/grammar.pest @@ -0,0 +1,13 @@ +file = { SOI ~ nodes ~ edges ~ EOI } + +nodes = { "nodes" ~ "{" ~ node+ ~ "}" } +node = { IDENT ~ ":" ~ nodespecs ~ ";" } +nodespecs = { nodespec ~ ("," ~ nodespec)* } +nodespec = @{ "begin" | "end" } + +edges = { "edges" ~ "{" ~ edge+ ~ "}" } +edge = { IDENT ~ "->" ~ IDENT ~ STRING ~ ";" } + +IDENT = @{ ('a'..'z' | 'A'..'Z' | "_") ~ ('a'..'z' | 'A'..'Z' | '0'..'9' | "_")* } +STRING = @{ "\"" ~ (!"\"" ~ ANY)+ ~ "\"" } +WHITESPACE = _{ " " | "\t" | "\n" | "\r" } \ No newline at end of file blob - /dev/null blob + dc25e626edaf9acb9c66b01542810d85de4a6932 (mode 644) --- /dev/null +++ src/main.rs @@ -0,0 +1,75 @@ +use std::{path::Path, process::{Command, Stdio}, io::Write}; + +use ast::Machine; + +mod ast; +mod parser; + +fn write(mach: &Machine, tmpdir: &Path, id: usize) { + let path = tmpdir.join(format!("{id}.png")); + let mut cmd = Command::new("dot") + .args(&["-Tpng", "-o"]) + .arg(path) + .stdin(Stdio::piped()) + .spawn() + .unwrap(); + + let mut stdin = cmd.stdin.take().unwrap(); + + writeln!(&mut stdin, "{mach}").unwrap(); + drop(stdin); + + cmd.wait().unwrap(); +} + +fn compile(out: &Path, tmpdir: &Path, num: usize) { + let size = imgsize(&tmpdir.join(format!("{}.png", num - 1))); + let mut cmd = Command::new("convert"); + for i in 0..num { + let path = tmpdir.join(format!("{i}.png")); + cmd.args(&["-delay", "100"]).arg(path); + } + let st = cmd + .args(&["-extent", &size]) + .arg(out) + .status() + .unwrap() + .success(); + + if !st { + panic!("not ok"); + } +} + +fn imgsize(path: &Path) -> String { + let data = Command::new("identify") + .args(&["-format", "%wx%h"]) + .arg(path) + .output() + .unwrap() + .stdout; + String::from_utf8(data).unwrap() +} + +fn main() { + let tmpdir = std::env::temp_dir(); + let input = "011"; + let s = std::fs::read_to_string("test.dv").expect("failed to read"); + let mut mach = crate::parser::parse(&s).expect("failed to parse"); + + write(&mach, &tmpdir, 0); + let mut id = 1; + + for ch in input.chars() { + if !mach.feed(ch) { + break; + } + + write(&mach, &tmpdir, id); + id = id + 1; + } + + let out = Path::new("test.gif"); + compile(out, &tmpdir, id); + +} blob - /dev/null blob + 71fd85b7c74a163cd6f80dacc8695839566c9c77 (mode 644) --- /dev/null +++ src/parser.rs @@ -0,0 +1,82 @@ +use std::collections::BTreeMap; + +use pest::{error::Error, iterators::Pair, Parser}; +use pest_derive::Parser; + +use crate::ast::{Edge, Machine, Node}; + +#[derive(Parser)] +#[grammar = "grammar.pest"] +struct DotVisParser; + +fn parse_value(pair: Pair) -> Machine { + let mut iter = pair.into_inner(); + let nodes_iter = iter.next().unwrap().into_inner(); + let edges_iter = iter.next().unwrap().into_inner(); + + let mut nodes = BTreeMap::new(); + let mut edges = Vec::new(); + let mut begin = None; + + for node in nodes_iter { + let mut iter = node.into_inner(); + let name = iter.next().unwrap().as_str().to_string(); + let specs = iter.next().unwrap(); + let mut end = false; + for spec in specs.into_inner() { + match spec.as_str() { + "begin" if begin.is_some() => panic!("begin already specified"), + "begin" => begin = Some(name.clone()), + "end" => end = true, + spec => panic!("invalid spec: {spec}"), + } + } + nodes.entry(name) + .and_modify(|n: &mut Node| n.end = end) + .or_insert(Node { end }); + } + + for edge in edges_iter { + let mut iter = edge.into_inner(); + let from = iter.next().unwrap().as_str().to_string(); + let to = iter.next().unwrap().as_str().to_string(); + let chars = iter + .next() + .unwrap() + .as_str() + .strip_prefix('"') + .unwrap() + .strip_suffix('"') + .unwrap() + .chars() + .collect(); + nodes + .entry(from.clone()) + .or_insert(Node::default()); + nodes + .entry(to.clone()) + .or_insert(Node::default()); + + let e = Edge { + from, + to, + chars, + }; + edges.push(e); + } + + let begin = begin.unwrap_or_else(|| edges.first().unwrap().from.clone()); + Machine { + current: begin.clone(), + begin, + nodes, + edges, + consumed: Vec::new(), + } +} + +pub fn parse(s: &str) -> Result> { + let x = DotVisParser::parse(Rule::file, s)?.next().unwrap(); + + Ok(parse_value(x)) +}