From bcd96a68fe1d23a7ba27f7b98a2f0de02b2ba13e Mon Sep 17 00:00:00 2001 From: MultisampledNight Date: Fri, 12 Jan 2024 17:23:17 +0100 Subject: [PATCH] feat(ir): replace Rpl with GraphIr Semi-broken as atm the CLI just does nothing except printing the parsed IR, instead of actually executing it. --- Cargo.lock | 102 ++++++++++++++--- Cargo.toml | 6 +- crates/{pl-cli => cli}/Cargo.toml | 6 +- crates/{pl-cli => cli}/src/main.rs | 6 +- crates/executor/Cargo.toml | 2 +- crates/executor/src/debug/instructions/mod.rs | 4 +- crates/executor/src/debug/mod.rs | 16 +-- crates/executor/src/lib.rs | 6 +- crates/{rpl => ir}/Cargo.toml | 5 +- .../src/instruction}/mod.rs | 12 +- .../src/instruction}/read.rs | 6 +- .../src/instruction}/write.rs | 6 +- crates/ir/src/lib.rs | 108 ++++++++++++++++++ crates/{rpl => ir}/src/value/mod.rs | 0 crates/rpl/src/lib.rs | 51 --------- flake.nix | 2 +- 16 files changed, 233 insertions(+), 105 deletions(-) rename crates/{pl-cli => cli}/Cargo.toml (69%) rename crates/{pl-cli => cli}/src/main.rs (76%) rename crates/{rpl => ir}/Cargo.toml (62%) rename crates/{rpl/src/instructions => ir/src/instruction}/mod.rs (59%) rename crates/{rpl/src/instructions => ir/src/instruction}/read.rs (54%) rename crates/{rpl/src/instructions => ir/src/instruction}/write.rs (55%) create mode 100644 crates/ir/src/lib.rs rename crates/{rpl => ir}/src/value/mod.rs (100%) delete mode 100644 crates/rpl/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index b0b723c..a7163cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,20 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + [[package]] name = "anstream" version = "0.6.5" @@ -147,6 +161,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "cli" +version = "0.1.0" +dependencies = [ + "clap", + "executor", + "ir", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -217,7 +240,7 @@ version = "0.1.0" dependencies = [ "clap", "image", - "rpl", + "ir", ] [[package]] @@ -264,6 +287,17 @@ dependencies = [ "spin", ] +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gif" version = "0.12.0" @@ -308,6 +342,15 @@ dependencies = [ "tiff", ] +[[package]] +name = "ir" +version = "0.1.0" +dependencies = [ + "ahash", + "ron", + "serde", +] + [[package]] name = "jpeg-decoder" version = "0.3.0" @@ -323,6 +366,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + [[package]] name = "lock_api" version = "0.4.11" @@ -374,13 +423,10 @@ dependencies = [ ] [[package]] -name = "pl-cli" -version = "0.1.0" -dependencies = [ - "clap", - "executor", - "rpl", -] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "png" @@ -454,14 +500,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "rpl" -version = "0.1.0" -dependencies = [ - "ron", - "serde", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -549,6 +587,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "weezl" version = "0.1.7" @@ -621,6 +671,26 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zune-inflate" version = "0.2.54" diff --git a/Cargo.toml b/Cargo.toml index e248c8a..d14a2e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [workspace] members = [ "crates/executor", - "crates/pl-cli", - "crates/rpl" + "crates/ir", + "crates/cli", ] resolver = "2" [workspace.dependencies] -clap = { version = "4", features = [ "derive" ] } +clap = { version = "4", features = ["derive"] } # to enable all the lints below, this must be present in a workspace member's Cargo.toml: # [lints] diff --git a/crates/pl-cli/Cargo.toml b/crates/cli/Cargo.toml similarity index 69% rename from crates/pl-cli/Cargo.toml rename to crates/cli/Cargo.toml index 4dc900e..de446e8 100644 --- a/crates/pl-cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "pl-cli" +name = "cli" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { workspace = true, features = [ "derive" ] } -rpl = { path = "../rpl" } +clap = { workspace = true } executor = { path = "../executor" } +ir = { path = "../ir" } [lints] workspace = true diff --git a/crates/pl-cli/src/main.rs b/crates/cli/src/main.rs similarity index 76% rename from crates/pl-cli/src/main.rs rename to crates/cli/src/main.rs index 4c82336..a8c3dfd 100644 --- a/crates/pl-cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,7 +1,7 @@ use std::{fs, path::PathBuf}; use clap::Parser; -use executor::{execute_all, Executors}; +use executor::Executors; #[derive(Parser, Debug)] pub struct Args { @@ -14,7 +14,7 @@ fn main() { let f = fs::read_to_string(args.file) .expect("reading IR failed — come back to this later handle errors properly"); - let pl = rpl::from_ron(&f); + let pl = ir::from_ron(&f).expect("handle me properly"); - execute_all(pl.0); + dbg!(pl); } diff --git a/crates/executor/Cargo.toml b/crates/executor/Cargo.toml index 49d9685..711b9b9 100644 --- a/crates/executor/Cargo.toml +++ b/crates/executor/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] clap = { workspace = true, features = [ "derive" ] } image = "0.24" -rpl = { path = "../rpl" } +ir = { path = "../ir" } [lints] workspace = true diff --git a/crates/executor/src/debug/instructions/mod.rs b/crates/executor/src/debug/instructions/mod.rs index 7fa5531..b78c7b5 100644 --- a/crates/executor/src/debug/instructions/mod.rs +++ b/crates/executor/src/debug/instructions/mod.rs @@ -1,6 +1,6 @@ pub mod read { use image::{io::Reader as ImageReader, DynamicImage}; - use rpl::instructions::read::{Read, SourceType}; + use ir::instruction::read::{Read, SourceType}; pub fn read(Read { source, .. }: Read) -> DynamicImage { // TODO: actual error handling @@ -15,7 +15,7 @@ pub mod read { pub mod write { use image::{DynamicImage, ImageFormat}; - use rpl::instructions::write::{TargetFormat, TargetType, Write}; + use ir::instruction::write::{TargetFormat, TargetType, Write}; pub fn write(Write { target, format }: Write, input_data: &DynamicImage) { input_data diff --git a/crates/executor/src/debug/mod.rs b/crates/executor/src/debug/mod.rs index b7524d3..b9cd743 100644 --- a/crates/executor/src/debug/mod.rs +++ b/crates/executor/src/debug/mod.rs @@ -1,4 +1,4 @@ -use rpl::instructions::{Filter, Instruction}; +use ir::instruction::{Filter, Kind}; use crate::value::Dynamic; mod instructions; @@ -6,12 +6,12 @@ mod instructions; pub struct Executor; impl crate::Executor for Executor { - fn execute(instruction: Instruction, input: Option) -> Option { + fn execute(instruction: Kind, input: Option) -> Option { match instruction { - Instruction::Read(read_instruction) => { + Kind::Read(read_instruction) => { Some(Dynamic::Image(instructions::read::read(read_instruction))) } - Instruction::Write(write_instruction) => { + Kind::Write(write_instruction) => { instructions::write::write( write_instruction, match input { @@ -21,10 +21,10 @@ impl crate::Executor for Executor { ); None } - Instruction::Math(_) => todo!(), - Instruction::Blend(_) => todo!(), - Instruction::Noise(_) => todo!(), - Instruction::Filter(filter_instruction) => match filter_instruction { + Kind::Math(_) => todo!(), + Kind::Blend(_) => todo!(), + Kind::Noise(_) => todo!(), + Kind::Filter(filter_instruction) => match filter_instruction { Filter::Invert => Some(Dynamic::Image(instructions::filters::invert::invert( match input { Some(Dynamic::Image(img)) => img, diff --git a/crates/executor/src/lib.rs b/crates/executor/src/lib.rs index 9e4da44..639947e 100644 --- a/crates/executor/src/lib.rs +++ b/crates/executor/src/lib.rs @@ -1,4 +1,4 @@ -use rpl::instructions::Instruction; +use ir::instruction::Kind; use value::Dynamic; mod debug; @@ -17,10 +17,10 @@ pub enum Executors { } trait Executor { - fn execute(instruction: Instruction, input: Option) -> Option; + fn execute(instruction: Kind, input: Option) -> Option; } -pub fn execute_all(instructions: Vec) { +pub fn execute_all(instructions: Vec) { let mut tmp = None; for instruction in instructions { diff --git a/crates/rpl/Cargo.toml b/crates/ir/Cargo.toml similarity index 62% rename from crates/rpl/Cargo.toml rename to crates/ir/Cargo.toml index 9fb8505..1bb10f9 100644 --- a/crates/rpl/Cargo.toml +++ b/crates/ir/Cargo.toml @@ -1,13 +1,14 @@ [package] -name = "rpl" +name = "ir" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = { version = "1.0.193", features = [ "derive" ] } +ahash = { version = "0.8.7", features = ["serde"] } ron = "0.8" +serde = { version = "1.0.193", features = ["derive"] } [lints] workspace = true diff --git a/crates/rpl/src/instructions/mod.rs b/crates/ir/src/instruction/mod.rs similarity index 59% rename from crates/rpl/src/instructions/mod.rs rename to crates/ir/src/instruction/mod.rs index 76ecc22..adb46e6 100644 --- a/crates/rpl/src/instructions/mod.rs +++ b/crates/ir/src/instruction/mod.rs @@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize}; pub mod read; pub mod write; -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum Instruction { +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Kind { Read(read::Read), Write(write::Write), Math(Math), @@ -13,7 +13,7 @@ pub enum Instruction { Filter(Filter), } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Math { Add, Subtract, @@ -21,7 +21,7 @@ pub enum Math { Divide, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Blend { Normal, Multiply, @@ -34,14 +34,14 @@ pub enum Blend { Lighten, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Noise { Perlin, Simplex, Voronoi, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Filter { Invert, } diff --git a/crates/rpl/src/instructions/read.rs b/crates/ir/src/instruction/read.rs similarity index 54% rename from crates/rpl/src/instructions/read.rs rename to crates/ir/src/instruction/read.rs index 6ccd4d3..7f68524 100644 --- a/crates/rpl/src/instructions/read.rs +++ b/crates/ir/src/instruction/read.rs @@ -1,18 +1,18 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Read { pub source: SourceType, pub format: SourceFormat, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SourceType { File(PathBuf), } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SourceFormat { Jpeg, Png, diff --git a/crates/rpl/src/instructions/write.rs b/crates/ir/src/instruction/write.rs similarity index 55% rename from crates/rpl/src/instructions/write.rs rename to crates/ir/src/instruction/write.rs index d5ea207..973fab8 100644 --- a/crates/rpl/src/instructions/write.rs +++ b/crates/ir/src/instruction/write.rs @@ -1,18 +1,18 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Write { pub target: TargetType, pub format: TargetFormat, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum TargetType { File(PathBuf), } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum TargetFormat { Jpeg, Png, diff --git a/crates/ir/src/lib.rs b/crates/ir/src/lib.rs new file mode 100644 index 0000000..790221d --- /dev/null +++ b/crates/ir/src/lib.rs @@ -0,0 +1,108 @@ +use std::ops::RangeInclusive; + +use serde::{Deserialize, Serialize}; + +pub mod instruction; + +pub type Map = ahash::AHashMap; +pub type Set = ahash::AHashSet; + +/// # Errors +/// +/// Returns an error if the parsed source is not a valid graph IR. +pub fn from_ron(source: &str) -> ron::error::SpannedResult { + ron::from_str(source) +} + +/// The toplevel representation of a whole pipeline. +/// +/// Pipelines may not be fully linear. They may branch out and recombine later on. +/// As such, the representation for them which is currently used is a +/// [**D**irected **A**cyclic **G**raph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) +/// . +/// +/// For those who are already familiar with graphs, a DAG is one, except that: +/// +/// - It is **directed**: Edges have a direction they point to. +/// In this case, edges point from the outputs of streamers to inputs of consumers. +/// - It is **acyclic**: Those directed edges may not form loops. +/// In other words, if one follows edges only in their direction, it must be impossible +/// to come back to an already visited node. +/// +/// Here, if an edge points from _A_ to _B_ (`A --> B`), +/// then _A_ is called a **dependency** of _B_, +/// and _B_ is called a **dependent** of _A_. +/// +/// The DAG also enables another neat operation: +/// [Topological sorting](https://en.wikipedia.org/wiki/Topological_sorting). +/// This allows to put the entire graph into a linear list, +/// where it's guaranteed that once a vertex is visited, +/// all dependencies of it will have been visited already as well. +/// +/// The representation used here in specific is a bit more complicated, +/// since **instructions** directly aren't just connected to one another, +/// but their **sockets** are instead. +/// +/// So the vertices of the DAG are the **sockets** +/// (which are either [`id::Input`] or [`id::Output`] depending on the direction), +/// and each **socket** in turn belongs to an **instruction**. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct GraphIr { + /// "Backbone" storage of all **instruction** IDs to + /// what **kind of instruction** they are. + instructions: Map, + + /// How the data flows forward. **Dependencies** map to **dependents** here. + edges: Map>, + /// How the data flows backward. **Dependents** map to **dependencies** here. + rev_edges: Map>, +} + +pub mod id { + use serde::{Deserialize, Serialize}; + + use crate::Span; + + /// One specific instruction, and where it is found in code. + /// + /// It does **not** contain what kind of instruction this is. + /// Refer to [`crate::instruction::Kind`] for this instead. + #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] + pub struct Instruction(Span); + + impl Instruction { + /// Where this instruction is written down. + pub fn span(&self) -> &Span { + &self.0 + } + } + + /// On an **instruction**, accepts incoming data. + /// + /// An **instruction** cannot run if any of these are not connected. + #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] + pub struct Input(Socket); + + /// On an **instruction**, returns outgoing data to be fed to [`Input`]s. + /// + /// In contrast to [`Input`]s, [`Output`]s may be used or unused. + #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] + pub struct Output(Socket); + + /// An unspecified socket on a specific **instruction**, + /// and where it is on that **instruction**. + #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] + pub struct Socket { + pub belongs_to: Instruction, + pub index: SocketIdx, + } + + /// Where a [`Socket`] is on an **instruction**. + #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] + pub struct SocketIdx(pub u16); +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct Span { + range: RangeInclusive, +} diff --git a/crates/rpl/src/value/mod.rs b/crates/ir/src/value/mod.rs similarity index 100% rename from crates/rpl/src/value/mod.rs rename to crates/ir/src/value/mod.rs diff --git a/crates/rpl/src/lib.rs b/crates/rpl/src/lib.rs deleted file mode 100644 index 657b07d..0000000 --- a/crates/rpl/src/lib.rs +++ /dev/null @@ -1,51 +0,0 @@ -use instructions::Instruction; -use serde::{Deserialize, Serialize}; - -pub mod instructions; - -/// # Panics -/// -/// Panics if deserialization fails. lol. -#[must_use] -pub fn from_ron(raw: &str) -> Rpl { - ron::from_str(raw).expect("come back later and handle me correctly") -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub struct Rpl(pub Vec); - -#[cfg(test)] -mod tests { - use super::*; - use crate::instructions::{ - read::{SourceFormat, SourceType}, - write::{TargetFormat, TargetType}, - Math, - }; - - #[test] - fn test_simple_deserialize() { - const TEST_DATA: &str = concat!( - "([", - "Read( (source: File(\"~/example/file.png\"), format: Png) ),", - "Math(Add),", - "Write(( target: File(\"~/example/out.jpg\"), format: Jpeg)),", - "])", - ); - - assert_eq!( - from_ron(TEST_DATA), - Rpl(vec![ - Instruction::Read(instructions::read::Read { - source: SourceType::File("~/example/file.png".into()), - format: SourceFormat::Png - }), - Instruction::Math(Math::Add), - Instruction::Write(instructions::write::Write { - target: TargetType::File("~/example/out.jpg".into()), - format: TargetFormat::Jpeg - }) - ]) - ); - } -} diff --git a/flake.nix b/flake.nix index aa617a3..b0d5bde 100644 --- a/flake.nix +++ b/flake.nix @@ -40,7 +40,7 @@ just nushell typst typst-lsp mold - cargo-nextest + cargo-nextest cargo-watch ]; }) ];