diff --git a/.gitignore b/.gitignore index 1407055..fcaa00f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ .direnv/ /target .pre-commit-config.yaml -*.pdf diff --git a/.helix/languages.toml b/.helix/languages.toml deleted file mode 100644 index ca9fd35..0000000 --- a/.helix/languages.toml +++ /dev/null @@ -1,3 +0,0 @@ -[[language]] -name = "ron" -file-types = [ "rpl" ] diff --git a/Cargo.lock b/Cargo.lock index b0b723c..ee349ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,11 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -30,88 +24,43 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "beef" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" - -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" -dependencies = [ - "serde", -] - -[[package]] -name = "bytemuck" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] name = "clap" -version = "4.4.12" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", @@ -119,9 +68,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.12" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", @@ -148,10 +97,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] -name = "color_quant" -version = "1.1.0" +name = "codespan-reporting" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] [[package]] name = "colorchoice" @@ -160,128 +113,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] -name = "crc32fast" -version = "1.3.2" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "executor" -version = "0.1.0" -dependencies = [ - "clap", - "image", - "rpl", -] - -[[package]] -name = "exr" -version = "1.71.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fdeflate" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "flate2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "spin", -] - -[[package]] -name = "gif" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "half" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" -dependencies = [ - "crunchy", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "heck" @@ -290,129 +125,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "image" -version = "0.24.7" +name = "logos" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "exr", - "gif", - "jpeg-decoder", - "num-rational", - "num-traits", - "png", - "qoi", - "tiff", + "logos-derive", ] [[package]] -name = "jpeg-decoder" -version = "0.3.0" +name = "logos-codegen" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" dependencies = [ - "rayon", + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax", + "syn", ] [[package]] -name = "lebe" -version = "0.5.2" +name = "logos-derive" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" dependencies = [ - "autocfg", - "scopeguard", + "logos-codegen", ] [[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", - "simd-adler32", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "pl-cli" +name = "pipeline-lang" version = "0.1.0" dependencies = [ "clap", - "executor", - "rpl", -] - -[[package]] -name = "png" -version = "0.17.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "codespan-reporting", + "logos", ] [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - [[package]] name = "quote" version = "1.0.33" @@ -423,91 +184,10 @@ dependencies = [ ] [[package]] -name = "rayon" -version = "1.8.0" +name = "regex-syntax" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "ron" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" -dependencies = [ - "base64", - "bitflags 2.4.1", - "serde", - "serde_derive", -] - -[[package]] -name = "rpl" -version = "0.1.0" -dependencies = [ - "ron", - "serde", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.193" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.193" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "smallvec" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "strsim" @@ -517,9 +197,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.41" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -527,14 +207,12 @@ dependencies = [ ] [[package]] -name = "tiff" -version = "0.9.0" +name = "termcolor" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", + "winapi-util", ] [[package]] @@ -543,6 +221,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "utf8parse" version = "0.2.1" @@ -550,25 +234,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] -name = "weezl" -version = "0.1.7" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -581,51 +290,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml index 6aa4943..2291d53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,14 @@ -[workspace] -members = [ - "crates/executor", - "crates/pl-cli", - "crates/rpl" -] -resolver = "2" +[package] +name = "pipeline-lang" +version = "0.1.0" +edition = "2021" -[workspace.dependencies] -clap = { version = "4", features = [ "derive" ] } +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +logos = "0.13" +codespan-reporting = "0.11" +clap = { version = "4.4.8", features = [ "derive" ] } [lints.rust] unsafe_code = "deny" diff --git a/crates/executor/Cargo.toml b/crates/executor/Cargo.toml deleted file mode 100644 index fc01c27..0000000 --- a/crates/executor/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "executor" -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" ] } -image = "0.24" -rpl = { path = "../rpl" } diff --git a/crates/executor/src/cpu/mod.rs b/crates/executor/src/cpu/mod.rs deleted file mode 100644 index e82d20a..0000000 --- a/crates/executor/src/cpu/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) struct CpuExecutor; diff --git a/crates/executor/src/debug/instructions/mod.rs b/crates/executor/src/debug/instructions/mod.rs deleted file mode 100644 index 0c4b611..0000000 --- a/crates/executor/src/debug/instructions/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -pub mod read { - use image::{io::Reader as ImageReader, DynamicImage}; - use rpl::instructions::read::{Read, SourceType}; - - pub fn read(Read { source, format }: Read) -> DynamicImage { - let mut img = ImageReader::open(match source { - SourceType::File(path) => path, - }) - .expect("something went wrong :((("); - - img.decode().expect("couldn't decode image") - } -} - -pub mod write { - use image::{io::Reader as ImageReader, DynamicImage, ImageFormat}; - use rpl::instructions::write::{TargetFormat, TargetType, Write}; - - pub fn write(Write { target, format }: Write, input_data: DynamicImage) { - input_data.save_with_format( - match target { - TargetType::File(path) => path, - }, - match format { - TargetFormat::Jpeg => ImageFormat::Jpeg, - TargetFormat::Png => ImageFormat::Png, - }, - ); - } -} - -pub mod filters { - pub mod invert { - use image::DynamicImage; - - pub fn invert(mut input_data: DynamicImage) -> DynamicImage { - input_data.invert(); - input_data - } - } -} diff --git a/crates/executor/src/debug/mod.rs b/crates/executor/src/debug/mod.rs deleted file mode 100644 index d56f546..0000000 --- a/crates/executor/src/debug/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -use rpl::instructions::{FilterInstruction, Instruction}; - -use crate::{value::DynamicValue, Executor}; -mod instructions; - -pub struct DebugExecutor; - -impl Executor for DebugExecutor { - fn execute(instruction: Instruction, input: Option) -> Option { - match instruction { - Instruction::Read(read_instruction) => Some(DynamicValue::Image( - instructions::read::read(read_instruction), - )), - Instruction::Write(write_instruction) => { - instructions::write::write( - write_instruction, - match input { - Some(DynamicValue::Image(img)) => img, - _ => panic!("awawwawwa"), - }, - ); - None - } - Instruction::Math(_) => todo!(), - Instruction::Blend(_) => todo!(), - Instruction::Noise(_) => todo!(), - Instruction::Filter(filter_instruction) => match filter_instruction { - FilterInstruction::Invert => Some(DynamicValue::Image( - instructions::filters::invert::invert(match input { - Some(DynamicValue::Image(img)) => img, - _ => panic!("invalid value type for invert"), - }), - )), - }, - } - } -} diff --git a/crates/executor/src/lib.rs b/crates/executor/src/lib.rs deleted file mode 100644 index fb55029..0000000 --- a/crates/executor/src/lib.rs +++ /dev/null @@ -1,38 +0,0 @@ -use rpl::instructions::Instruction; -use value::DynamicValue; - -mod debug; -mod value; - -/// The available executors -/// unused in early dev. -#[derive(Debug, Clone, Copy, clap::ValueEnum)] -pub enum Executors { - /// the debug executor is single threaded and really, *really* slow. And unstable. Don't use. Unless you're a dev working on this. - Debug, - /// the CPU executor primarily uses the CPU. Most likely most feature complete, and the fallback. - Cpu, - /// the Vulkan executor (obviously) uses vulkan. there's a good chance this isn't implemented yet as you're reading this. - Vulkan, -} - -trait Executor { - fn execute(instruction: Instruction, input: Option) -> Option; -} - -pub fn execute_all(instructions: Vec) { - let mut tmp = None; - - for instruction in instructions { - tmp = debug::DebugExecutor::execute(instruction, tmp); - } -} - -// scratchpad lol: -// execution structure: -// 1. take in rpl -// 2. analyse/validate structure against allowed executors -// 3. assign executors to instructions -// 4. optimize -// 5. prepare memory management patterns -// 6. run diff --git a/crates/executor/src/value/mod.rs b/crates/executor/src/value/mod.rs deleted file mode 100644 index daf0dd2..0000000 --- a/crates/executor/src/value/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -use image::DynamicImage; - -pub enum DynamicValue { - Image(DynamicImage), -} diff --git a/crates/pl-cli/Cargo.toml b/crates/pl-cli/Cargo.toml deleted file mode 100644 index e6f8cc8..0000000 --- a/crates/pl-cli/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "pl-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" } -executor = { path = "../executor" } diff --git a/crates/pl-cli/src/main.rs b/crates/pl-cli/src/main.rs deleted file mode 100644 index 9e84d33..0000000 --- a/crates/pl-cli/src/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::{fs, path::PathBuf}; - -use clap::Parser; -use executor::{execute_all, Executors}; - -#[derive(Parser, Debug)] -pub struct Args { - file: PathBuf, - primary_executor: Executors, -} - -fn main() { - let args = dbg!(Args::parse()); - - let f = fs::read_to_string(args.file).unwrap(); - let pl = rpl::from_ron(&f); - - execute_all(pl.0); -} diff --git a/crates/rpl/Cargo.toml b/crates/rpl/Cargo.toml deleted file mode 100644 index e406f11..0000000 --- a/crates/rpl/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "rpl" -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" ] } -ron = "0.8" diff --git a/crates/rpl/src/instructions/mod.rs b/crates/rpl/src/instructions/mod.rs deleted file mode 100644 index 9fb8a56..0000000 --- a/crates/rpl/src/instructions/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub mod read; -pub mod write; - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum Instruction { - Read(read::Read), - Write(write::Write), - Math(MathInstruction), - Blend(BlendInstruction), - Noise(NoiseInstruction), - Filter(FilterInstruction), -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum MathInstruction { - Add, - Subtract, - Multiply, - Divide, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum BlendInstruction { - Normal, - Multiply, - Additive, - Overlay, - Screen, - Subtractive, - Difference, - Darken, - Lighten, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum NoiseInstruction { - Perlin, - Simplex, - Voronoi, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum FilterInstruction { - Invert, -} diff --git a/crates/rpl/src/instructions/read.rs b/crates/rpl/src/instructions/read.rs deleted file mode 100644 index 6ccd4d3..0000000 --- a/crates/rpl/src/instructions/read.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub struct Read { - pub source: SourceType, - pub format: SourceFormat, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum SourceType { - File(PathBuf), -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum SourceFormat { - Jpeg, - Png, -} diff --git a/crates/rpl/src/instructions/write.rs b/crates/rpl/src/instructions/write.rs deleted file mode 100644 index d5ea207..0000000 --- a/crates/rpl/src/instructions/write.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub struct Write { - pub target: TargetType, - pub format: TargetFormat, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum TargetType { - File(PathBuf), -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum TargetFormat { - Jpeg, - Png, -} diff --git a/crates/rpl/src/lib.rs b/crates/rpl/src/lib.rs deleted file mode 100644 index dbecd0a..0000000 --- a/crates/rpl/src/lib.rs +++ /dev/null @@ -1,38 +0,0 @@ -use instructions::Instruction; -use serde::{Deserialize, Serialize}; - -use crate::instructions::{ - read::{SourceFormat, SourceType}, - write::{TargetFormat, TargetType}, - MathInstruction, -}; - -pub mod instructions; - -pub fn from_ron(raw: &str) -> Rpl { - ron::from_str(raw).unwrap() -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub struct Rpl(pub Vec); - -#[test] -fn test_simple_deserialize() { - const TEST_DATA: &str = - "([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(MathInstruction::Add), - Instruction::Write(instructions::write::Write { - target: TargetType::File("~/example/out.jpg".into()), - format: TargetFormat::Jpeg - }) - ]) - ); -} diff --git a/crates/rpl/src/value/mod.rs b/crates/rpl/src/value/mod.rs deleted file mode 100644 index 238b9c2..0000000 --- a/crates/rpl/src/value/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub enum DynamicValue { - Image(DynamicImage), -} diff --git a/docs/design/types.typ b/docs/design/types.typ deleted file mode 100644 index 2e4af6e..0000000 --- a/docs/design/types.typ +++ /dev/null @@ -1,10 +0,0 @@ -#import "../template.typ": conf -#show: doc => conf( - doc -) - -= meow nya - -nyanyanya - -#lorem(50) diff --git a/docs/template.typ b/docs/template.typ deleted file mode 100644 index 6022b41..0000000 --- a/docs/template.typ +++ /dev/null @@ -1,12 +0,0 @@ -#let conf( - doc -) = { - set text(font: "Atkinson Hyperlegible"); - show heading: it => [ - #set text(font: "Montserrat", weight: "regular") - - #it - ] - - doc -} diff --git a/justfile b/justfile deleted file mode 100644 index 9228b4c..0000000 --- a/justfile +++ /dev/null @@ -1,2 +0,0 @@ -doc-design: - typst compile docs/design/*.typ --root=docs diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..6a0e72b --- /dev/null +++ b/src/args.rs @@ -0,0 +1,11 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + pub text: String, + #[arg(long)] + pub debug_tokens: bool, + #[arg(long)] + pub debug_typed_repr: bool, +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs new file mode 100644 index 0000000..a6e6e2f --- /dev/null +++ b/src/builtins/mod.rs @@ -0,0 +1,39 @@ +use crate::namespace::{typedef::TypeDef, GlobalNamespace}; + +pub const TYPE_INTEGER: &str = "int"; +pub const TYPE_FLOAT: &str = "float"; +pub const TYPE_STRING: &str = "string"; + +pub const TRAIT_NUMERIC: &str = "Num"; + +pub const CMD_ADD: &str = "add"; + +#[allow( + clippy::unwrap_used, + reason = "Errs can only be returned in case of duplicate names in the same namespace, which will not happen here" +)] +#[allow(clippy::missing_panics_doc, reason = "will not panic")] +pub fn initialise_globals() -> GlobalNamespace { + let ns = GlobalNamespace::init(); + + let numeric = ns.register_trait(TRAIT_NUMERIC).unwrap(); + + ns.register_type(TYPE_INTEGER).unwrap().add_trait(&numeric); + ns.register_type(TYPE_FLOAT).unwrap().add_trait(&numeric); + + ns.register_type(TYPE_STRING).unwrap(); + + // def math add [ Numeric Numeric ] -> Numeric + ns.register_command( + CMD_ADD, + vec![("T", vec![&numeric])], + Some(&TypeDef::List(vec![ + TypeDef::Generic("T".to_owned()), + TypeDef::Generic("T".to_owned()), + ])), + Some(&TypeDef::Generic("T".to_owned())), + ) + .unwrap(); + + ns +} diff --git a/src/builtins/namespace.rs b/src/builtins/namespace.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/builtins/namespace.rs @@ -0,0 +1 @@ + diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000..4f15d36 --- /dev/null +++ b/src/error/mod.rs @@ -0,0 +1,90 @@ +use codespan_reporting::{ + diagnostic::{Diagnostic, Label}, + files::SimpleFiles, +}; + +use crate::Span; + +#[derive(Debug)] +pub struct Errors { + pub kind: ErrorKind, + pub locs: Vec, +} + +#[derive(Debug)] +pub enum ErrorKind { + InvalidToken, + SyntaxError(SyntaxErrorKind), + CommandNotFound, +} + +#[derive(Debug)] +pub enum SyntaxErrorKind { + /// `MissingStreamer` means, that the pipeline starts with a Pipe (`|`), so it has no streamer as input in front of it. + MissingStreamer, + /// `MissingSink` means, that the pipeline ends with a Pipe (`|`), meaning that the output can't go anywhere + MissingSink, + /// This indicates a missing filter somewhere in the pipeline, meaning that there's 2 pipes after one another + MissingFilter, + /// A literal cannot be a sink + LiteralAsSink, + /// A literal can't be a filter either + LiteralAsFilter, + /// A literal acting as streamer cannot take arguments + LiteralWithArgs, +} + +impl Errors { + pub fn new(kind: ErrorKind, locs: Vec) -> Self { + Self { kind, locs } + } + pub fn new_single(kind: ErrorKind, loc: Span) -> Self { + Self { + kind, + locs: vec![loc], + } + } + pub fn into_diag( + &self, + file_id: usize, + _file_db: &SimpleFiles<&str, String>, + ) -> Diagnostic { + let Errors { kind, locs } = self; + + match kind { + ErrorKind::InvalidToken => simple_diag(locs.clone(), file_id, "invalid tokens"), + ErrorKind::SyntaxError(syntax_error) => match syntax_error { + SyntaxErrorKind::MissingStreamer => simple_diag( + locs.clone(), + file_id, + "pipeline is missing an input provider", + ), + SyntaxErrorKind::MissingSink => { + simple_diag(locs.clone(), file_id, "pipeline is missing a sink") + } + SyntaxErrorKind::MissingFilter => { + simple_diag(locs.clone(), file_id, "missing filters in pipeline") + } + SyntaxErrorKind::LiteralAsSink => { + simple_diag(locs.clone(), file_id, "pipelines can't end in a literal") + } + SyntaxErrorKind::LiteralAsFilter => { + simple_diag(locs.clone(), file_id, "literals can't filter data") + } + SyntaxErrorKind::LiteralWithArgs => { + simple_diag(locs.clone(), file_id, "literals can't take arguments") + } + }, + ErrorKind::CommandNotFound => simple_diag(locs.clone(), file_id, "command not found"), + } + } +} + +fn simple_diag(spans: Vec, file_id: usize, msg: &str) -> Diagnostic { + Diagnostic::error().with_message(msg).with_labels( + spans + .into_iter() + .map(|span| Label::primary(file_id, span)) + .collect(), + ) +} diff --git a/src/evaluator.rs b/src/evaluator.rs new file mode 100644 index 0000000..9b11f3e --- /dev/null +++ b/src/evaluator.rs @@ -0,0 +1,137 @@ +use std::{collections::HashMap, process::exit}; + +use clap::error::Result; +use codespan_reporting::{ + files::SimpleFiles, + term::{ + self, + termcolor::{ColorChoice, StandardStream}, + }, +}; + +use crate::{ + builtins::initialise_globals, + error::{ErrorKind, Errors}, + syntax::{check::check, parse_syntax, PipelineElement}, + typed::into_typed_repr, +}; + +pub struct EvalConfig { + debug_raw_toks: bool, + debug_print_typed_repr: bool, +} + +impl EvalConfig { + pub fn new(debug_raw_toks: bool, debug_print_typed_repr: bool) -> Self { + Self { + debug_raw_toks, + debug_print_typed_repr, + } + } +} + +pub type FileId = usize; + +// this is also bad +// need a better architecture for this +pub struct Evaluator<'a> { + curr_phase: EvalPhase, + files: SimpleFiles<&'a str, String>, + errors: HashMap>, + cfg: EvalConfig, +} + +impl<'a> Evaluator<'a> { + pub fn init(cfg: EvalConfig) -> Self { + Self { + curr_phase: EvalPhase::Lex, + files: SimpleFiles::new(), + errors: HashMap::new(), + cfg, + } + } + + pub fn run(&mut self, input: &str, name: Option<&'a str>) { + let fid = self.files.add(name.unwrap_or("input"), input.to_owned()); + + let syntax = parse_syntax(input); + if self.cfg.debug_raw_toks { + println!("Raw tokens: {syntax:#?}"); + } + + match syntax { + Ok(syntax) => self.curr_phase = EvalPhase::Check(fid, syntax), + Err(errs) => { + self.errors.insert( + fid, + vec![Errors { + kind: ErrorKind::InvalidToken, + locs: errs, + }], + ); + self.curr_phase = EvalPhase::Failed; + } + }; + } + + pub fn next(mut self) { + match self.curr_phase { + EvalPhase::Lex => { + unreachable!() + } + EvalPhase::Check(file_id, syntax) => { + let r = check(&syntax); + + if let Err(errs) = r { + self.errors.insert(file_id, errs); + self.curr_phase = EvalPhase::Failed; + } else { + self.curr_phase = EvalPhase::BareTyped(file_id, syntax.clone()); + } + } + EvalPhase::BareTyped(file_id, syntax) => { + let ns = initialise_globals(); + let r = into_typed_repr(&ns, syntax); + + match r { + Ok(typed) => { + if self.cfg.debug_print_typed_repr { + println!("Typed repr: {typed:#?}"); + } + + todo!() + } + Err(errs) => { + self.errors.insert(file_id, vec![errs]); + self.curr_phase = EvalPhase::Failed; + } + } + } + EvalPhase::Failed => self.error_out().expect("unable to print errors"), + } + self.next(); + } + + pub fn error_out(self) -> Result { + let Evaluator { files, errors, .. } = self; + + let writer = StandardStream::stderr(ColorChoice::Always); + let config = term::Config::default(); + + for (file_id, errors) in &errors { + let writer = &mut writer.lock(); + for error in errors { + term::emit(writer, &config, &files, &error.into_diag(*file_id, &files))?; + } + } + + exit(1) + } +} + +enum EvalPhase { + Lex, + Check(FileId, Vec), + BareTyped(FileId, Vec), + Failed, +} diff --git a/src/globals.rs b/src/globals.rs new file mode 100644 index 0000000..3de4b77 --- /dev/null +++ b/src/globals.rs @@ -0,0 +1,26 @@ +// concepts +// root type namespace +// -> builtin types/traits/functions +// -> builtin constants +// +// generic item namespaces +// -> in generic functions ( `add [ T T ] -> T` ) +// -> in generic types ( vec ) +// -> generic traits ( T: From ) +// +// TODO: how builtins? just compile everything to builtin types/defs? + +/// This is the root type namespace. +struct RootTypeNamespace { + +} + +mod r#type { + +} +struct Type<'a> { + pub(super) id: usize, + pub(super) namespace: &'a GlobalNamespace, +} + +struct InternalType diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..917a4e9 --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,58 @@ +use logos::Logos; + +#[derive(Logos, Debug, PartialEq)] +#[logos(skip r"[\s]+")] +pub enum Token<'a> { + #[regex("[\\w]+", |lex| lex.slice())] + Word(&'a str), + #[regex("[\\d]+", priority = 2, callback = |lex| lex.slice().parse::().expect("regex should only match valid integers. This is a bug."))] + IntLiteral(i64), + #[regex("[\\d]+\\.[\\d]+", |lex| lex.slice().parse::().expect("regex should only match valid floats. This is a bug."))] + FloatLiteral(f64), + #[regex(r#""([^"\\]|\\["\\bnfrt]|u[a-fA-F0-9]{4})*""#, |lex| lex.slice().to_owned())] + StringLiteral(String), + #[token("|")] + Pipe, + // #[token("def")] + // Define, + // #[token("type")] + // Type, + // #[token("->")] + // RightArrow, + // #[token("[")] + // BracketOpening, + // #[token("]")] + // BracketClosing, + // #[token("(")] + // ParensOpening, + // #[token(")")] + // ParensClosing, + // #[token("{")] + // BraceOpening, + // #[token("}")] + // BraceClosing, + // #[token("+")] + // Plus, + // #[token("-")] + // Minus, + // #[token("*")] + // Multiply, + // #[token("/")] + // Divide, + // #[token("%")] + // Percent, + // #[token("&")] + // Ampersand, + // #[token(":")] + // Colon, + // #[token(";")] + // Semicolon, + // #[token(".")] + // Dot, + // #[token(",")] + // Comma, + // #[token("!")] + // ExclaimationMark, + // #[token("?")] + // QuestionMark, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d8a67e7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,20 @@ +#![feature(never_type, lint_reasons)] + +use std::ops::Range; + +pub mod args; +pub mod builtins; +pub mod error; +pub mod evaluator; + +#[allow(clippy::indexing_slicing, reason = "in logos, outside our control")] +pub mod lexer; + +pub mod namespace; +pub mod syntax; + +#[allow(dead_code, reason = "the future!!!")] +pub mod typed; + +// basically logos::Span but in this repo +pub type Span = Range; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2727830 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,12 @@ +use clap::Parser; +use pipeline_lang::args::Args; +use pipeline_lang::evaluator::{EvalConfig, Evaluator}; + +fn main() { + let args = Args::parse(); + + let mut evaluator = Evaluator::init(EvalConfig::new(args.debug_tokens, args.debug_typed_repr)); + + evaluator.run(&args.text, None); + evaluator.next(); +} diff --git a/src/namespace/command.rs b/src/namespace/command.rs new file mode 100644 index 0000000..9513ca2 --- /dev/null +++ b/src/namespace/command.rs @@ -0,0 +1,70 @@ +use std::fmt::Debug; +use std::fmt::Display; + +use super::typedef::{InternalTypeDef, TypeDef}; +use super::CommandId; + +use super::GlobalNamespace; + +#[derive(Clone, Copy)] +pub struct Command<'a> { + pub(super) id: CommandId, + pub(super) namespace: &'a GlobalNamespace, +} + +impl<'a> Command<'a> { + pub fn get_input_types(&self) -> Option> { + self.namespace.commands.borrow()[self.id] + .input + .as_ref() + .map(|def| TypeDef::from_internal(self.namespace, def)) + } + // get a count of inputs required + pub fn input_count(&self) -> usize { + self.namespace.commands.borrow()[self.id] + .input + .as_ref() + .map_or(0, |inputs| match inputs { + InternalTypeDef::Single(_) | InternalTypeDef::Generic(_) => 1, + InternalTypeDef::List(list) => list.len(), + InternalTypeDef::Record(rec) => rec.len(), + }) + } + pub fn get_output_types(&self) -> Option { + self.namespace.commands.borrow()[self.id] + .output + .as_ref() + .map(|def| TypeDef::from_internal(self.namespace, def)) + } +} + +impl Display for Command<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = &self.namespace.commands.borrow()[self.id].name; + f.write_fmt(format_args!( + "{name} {} -> {}", + self.get_input_types() + .map_or("!".to_owned(), |v| v.to_string()), + self.get_output_types() + .map_or("!".to_owned(), |v| v.to_string()) + )) + } +} + +impl Debug for Command<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = &self.namespace.commands.borrow()[self.id].name; + f.write_fmt(format_args!( + "{name:?} {:?} -> {:?}", + self.get_input_types(), + self.get_output_types() + )) + } +} + +pub(super) struct InternalCommand { + pub(super) name: String, + // gosh this is hacky + pub(super) input: Option, + pub(super) output: Option, +} diff --git a/src/namespace/mod.rs b/src/namespace/mod.rs new file mode 100644 index 0000000..5931ae6 --- /dev/null +++ b/src/namespace/mod.rs @@ -0,0 +1,213 @@ +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, +}; + +use self::{ + command::{Command, InternalCommand}, + r#trait::{InternalTrait, Trait}, + r#type::{InternalType, Type}, + typedef::TypeDef, +}; + +pub mod command; +pub mod r#trait; +pub mod r#type; +pub mod typedef; + +pub struct GlobalNamespace { + types: RefCell>, + traits: RefCell>, + type_namespace: RefCell>, + commands: RefCell>, + data_namespace: RefCell>, +} + +pub type TypeId = usize; +pub type TraitId = usize; +pub type CommandId = usize; + +enum TypeNamespaceId { + Types(TypeId), + Traits(TraitId), +} + +enum DataNamespaceId { + Commands(CommandId), +} + +impl GlobalNamespace { + pub fn init() -> Self { + Self { + types: RefCell::new(Vec::new()), + traits: RefCell::new(Vec::new()), + type_namespace: RefCell::new(HashMap::new()), + commands: RefCell::new(Vec::new()), + data_namespace: RefCell::new(HashMap::new()), + } + } + + /// register a type to the namespace + /// + /// # Errors + /// + /// Will return `NsRegistrationError::NameAlreadyExists` if the desired name is already in use + pub fn register_type(&self, name: &str) -> Result { + if self.type_namespace.borrow().contains_key(name) { + Err(NsRegistrationError::NameAlreadyExists) + } else { + self.types.borrow_mut().push(InternalType { + traits: RefCell::new(HashSet::new()), + name: name.to_owned(), + }); + let id = self.types.borrow().len() - 1; + let _ = self + .type_namespace + .borrow_mut() + .insert(name.to_owned(), TypeNamespaceId::Types(id)); + Ok(Type { + id, + namespace: self, + }) + } + } + + /// register a trait to the namespace + /// + /// # Errors + /// + /// Will return `NsRegistrationError::NameAlreadyExists` if the desired name is already in use + pub fn register_trait(&self, name: &str) -> Result { + if self.type_namespace.borrow().contains_key(name) { + Err(NsRegistrationError::NameAlreadyExists) + } else { + self.traits.borrow_mut().push(InternalTrait { + types: RefCell::new(HashSet::new()), + name: name.to_owned(), + }); + let id = self.traits.borrow().len() - 1; + let _ = self + .type_namespace + .borrow_mut() + .insert(name.to_owned(), TypeNamespaceId::Traits(id)); + Ok(Trait { + id, + namespace: self, + }) + } + } + + /// register a command to the namespace + /// + /// # Errors + /// + /// Will return `NsRegistrationError::NameAlreadyExists` if the desired name is already in use + pub fn register_command<'a>( + &self, + name: &'a str, + // TODO: refacto with builder pattern maybe? + generics: Vec<(&'a str, Vec<&Trait>)>, + input: Option<&'a TypeDef>, + output: Option<&'a TypeDef>, + ) -> Result> { + if self.data_namespace.borrow().contains_key(name) { + Err(NsRegistrationError::NameAlreadyExists) + } else { + let mut internal_generics_namespace: HashMap> = HashMap::new(); + + for (name, conditions) in generics { + if internal_generics_namespace.contains_key(name) { + return Err(NsRegistrationError::GenericNameAlreadyExists); + } + internal_generics_namespace + .insert(name.to_owned(), conditions.iter().map(|t| t.id).collect()); + } + + if let Some(def) = input { + def.check_generics_exist(&internal_generics_namespace) + .map_err(NsRegistrationError::UnregisteredGenericsUsed)?; + } + + if let Some(def) = output { + def.check_generics_exist(&internal_generics_namespace) + .map_err(NsRegistrationError::UnregisteredGenericsUsed)?; + } + + self.commands.borrow_mut().push(InternalCommand { + name: name.to_owned(), + input: input.map(std::convert::Into::into), + output: output.map(std::convert::Into::into), + }); + let id = self.traits.borrow().len() - 1; + let _ = self + .data_namespace + .borrow_mut() + .insert(name.to_owned(), DataNamespaceId::Commands(id)); + Ok(Command { + id, + namespace: self, + }) + } + } + + pub fn get_type(&self, id: TypeId) -> Option { + (self.types.borrow().len() > id).then_some(Type { + id, + namespace: self, + }) + } + + pub fn get_trait(&self, id: TraitId) -> Option { + (self.traits.borrow().len() > id).then_some(Trait { + id, + namespace: self, + }) + } + + pub fn get_command(&self, id: CommandId) -> Option { + (self.commands.borrow().len() > id).then_some(Command { + id, + namespace: self, + }) + } + + pub fn get_type_by_name(&self, name: &str) -> Option { + if let Some(TypeNamespaceId::Types(id)) = self.type_namespace.borrow().get(name) { + Some(Type { + id: *id, + namespace: self, + }) + } else { + None + } + } + + pub fn get_trait_by_name(&self, name: &str) -> Option { + if let Some(TypeNamespaceId::Traits(id)) = self.type_namespace.borrow().get(name) { + Some(Trait { + id: *id, + namespace: self, + }) + } else { + None + } + } + + pub fn get_command_by_name(&self, name: &str) -> Option { + if let Some(DataNamespaceId::Commands(id)) = self.data_namespace.borrow().get(name) { + Some(Command { + id: *id, + namespace: self, + }) + } else { + None + } + } +} + +#[derive(Debug)] +pub enum NsRegistrationError<'a> { + NameAlreadyExists, + GenericNameAlreadyExists, + UnregisteredGenericsUsed(Vec<&'a str>), +} diff --git a/src/namespace/trait.rs b/src/namespace/trait.rs new file mode 100644 index 0000000..9cb3f74 --- /dev/null +++ b/src/namespace/trait.rs @@ -0,0 +1,35 @@ +use std::collections::HashSet; + +use std::cell::RefCell; +use std::fmt::Debug; +use std::fmt::Display; + +use super::GlobalNamespace; +use super::TraitId; +use super::TypeId; + +#[derive(Clone, Copy)] +pub struct Trait<'a> { + pub(super) id: TraitId, + pub(super) namespace: &'a GlobalNamespace, +} + +impl Display for Trait<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = &self.namespace.traits.borrow()[self.id].name; + + f.write_fmt(format_args!("{name}")) + } +} + +impl Debug for Trait<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Trait({self})") + } +} + +pub(super) struct InternalTrait { + // make resolution easier + pub(super) types: RefCell>, + pub(super) name: String, +} diff --git a/src/namespace/type.rs b/src/namespace/type.rs new file mode 100644 index 0000000..18a1bad --- /dev/null +++ b/src/namespace/type.rs @@ -0,0 +1,55 @@ +use std::collections::HashSet; + +use std::cell::RefCell; +use std::fmt::Debug; +use std::fmt::Display; + +use super::Trait; + +use super::GlobalNamespace; +use super::TraitId; +use super::TypeId; + +#[derive(Clone, Copy)] +pub struct Type<'a> { + pub(super) id: TypeId, + pub(super) namespace: &'a GlobalNamespace, +} + +impl<'a> Type<'a> { + pub fn add_trait(&self, to_add: &'a Trait) { + let internal_self = &self.namespace.types.borrow()[self.id]; + + internal_self.traits.borrow_mut().insert(to_add.id); + self.namespace.traits.borrow_mut()[to_add.id] + .types + .borrow_mut() + .insert(self.id); + } + + pub fn has_trait(&self, to_check: &'a Trait) -> bool { + self.namespace.types.borrow()[self.id] + .traits + .borrow() + .contains(&to_check.id) + } +} + +impl Display for Type<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = &self.namespace.types.borrow()[self.id].name; + + f.write_fmt(format_args!("{name}")) + } +} + +impl Debug for Type<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Type({self})") + } +} + +pub(super) struct InternalType { + pub(super) traits: RefCell>, + pub(super) name: String, +} diff --git a/src/namespace/typedef.rs b/src/namespace/typedef.rs new file mode 100644 index 0000000..8afb772 --- /dev/null +++ b/src/namespace/typedef.rs @@ -0,0 +1,184 @@ +use std::collections::HashMap; +use std::fmt::Debug; +use std::fmt::Display; + +use super::TraitId; +use super::TypeId; + +use super::GlobalNamespace; + +use super::r#type::Type; + +pub enum TypeDef<'a> { + Type(Type<'a>), + Generic(String), + List(Vec>), + Record(Vec<(String, TypeDef<'a>)>), +} + +impl<'a> TypeDef<'a> { + pub(super) fn check_generics_exist( + &'a self, + map: &HashMap>, + ) -> Result<(), Vec<&'a str>> { + match self { + TypeDef::Type(_) => Ok(()), + TypeDef::Generic(name) => { + if map.contains_key(name) { + Ok(()) + } else { + Err(vec![name]) + } + } + TypeDef::List(defs) => { + let r = defs + .iter() + .map(|def| def.check_generics_exist(map)) + .filter_map(|check_res| { + if let Err(invalid_names) = check_res { + Some(invalid_names) + } else { + None + } + }) + .reduce(|mut acc, mut errs| { + acc.append(&mut errs); + acc + }); + + match r { + Some(e) => Err(e), + None => Ok(()), + } + } + TypeDef::Record(rec) => { + let r = rec + .iter() + .map(|(_n, def)| def.check_generics_exist(map)) + .filter_map(|check_res| { + if let Err(invalid_names) = check_res { + Some(invalid_names) + } else { + None + } + }) + .reduce(|mut acc, mut errs| { + acc.append(&mut errs); + acc + }); + + match r { + Some(e) => Err(e), + None => Ok(()), + } + } + } + } + pub(super) fn from_internal(ns: &'a GlobalNamespace, def: &InternalTypeDef) -> TypeDef<'a> { + match def { + InternalTypeDef::Single(id) => TypeDef::Type( + ns.get_type(*id) + .expect("Unregistered internal type ID. This is a bug."), + ), + InternalTypeDef::Generic(name) => TypeDef::Generic(name.clone()), + InternalTypeDef::List(list) => TypeDef::List( + list.iter() + .map(|def| Self::from_internal(ns, def)) + .collect(), + ), + InternalTypeDef::Record(rec) => TypeDef::Record( + rec.iter() + .map(|(name, def)| (name.clone(), Self::from_internal(ns, def))) + .collect(), + ), + } + } +} + +impl Display for TypeDef<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TypeDef::Type(t) => Display::fmt(&t, f), + TypeDef::Generic(name) => Display::fmt(name, f), + TypeDef::List(l) => { + f.write_str("[ ")?; + if let Some(first) = l.first() { + Display::fmt(&first, f)?; + } + for item in l.iter().skip(1) { + f.write_str(", ")?; + Display::fmt(&item, f)?; + } + f.write_str(" ]") + } + TypeDef::Record(rec) => { + f.write_str("{ ")?; + for (i, item) in rec.iter().enumerate() { + if i != 0 { + f.write_str(", ")?; + } + f.write_fmt(format_args!("{}: {}", item.0, item.1))?; + } + f.write_str(" }") + } + } + } +} + +impl Debug for TypeDef<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TypeDef::Type(t) => Debug::fmt(&t, f), + TypeDef::Generic(name) => Debug::fmt(name, f), + TypeDef::List(l) => { + f.write_str("[ ")?; + for (i, item) in l.iter().enumerate() { + if i != 0 { + f.write_str(", ")?; + } + Debug::fmt(&item, f)?; + } + f.write_str(" ]") + } + TypeDef::Record(rec) => { + f.write_str("{ ")?; + for (i, item) in rec.iter().enumerate() { + if i != 0 { + f.write_str(", ")?; + } + f.write_fmt(format_args!("{:?}: {:?}", item.0, item.1))?; + } + f.write_str(" }") + } + } + } +} + +impl<'a> From> for TypeDef<'a> { + fn from(value: Type<'a>) -> Self { + TypeDef::Type(value) + } +} + +pub(super) enum InternalTypeDef { + Single(TypeId), + Generic(String), + List(Vec), + Record(Vec<(String, InternalTypeDef)>), +} + +impl From<&TypeDef<'_>> for InternalTypeDef { + fn from(value: &TypeDef) -> Self { + match value { + TypeDef::Type(val) => Self::Single(val.id), + // TODO: rewrite this to be better + TypeDef::Generic(name) => Self::Generic(name.to_owned()), + TypeDef::List(list) => Self::List(list.iter().map(std::convert::Into::into).collect()), + TypeDef::Record(rec) => Self::Record( + rec.iter() + .map(|(name, typ)| (name.to_owned(), typ.into())) + .collect(), + ), + } + } +} diff --git a/src/syntax/check.rs b/src/syntax/check.rs new file mode 100644 index 0000000..ff5b76e --- /dev/null +++ b/src/syntax/check.rs @@ -0,0 +1,214 @@ +#[cfg(test)] +#[allow(clippy::unwrap_used, reason = "these are tests. they may unwrap.")] +mod test; + +use crate::{ + error::{ErrorKind, Errors, SyntaxErrorKind}, + syntax::CommandPart, +}; + +use super::{CommandPartKind, PipelineElement, PipelineElementKind}; + +pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec> { + let mut errs = Vec::new(); + + if let Err(e_span) = check_missing_streamer(syntax) { + errs.push(Errors::new_single( + ErrorKind::SyntaxError(SyntaxErrorKind::MissingStreamer), + e_span, + )); + } + + if let Err(err_locs) = check_missing_filters(syntax) { + errs.push(Errors::new( + ErrorKind::SyntaxError(SyntaxErrorKind::MissingFilter), + err_locs, + )); + } + + if let Err(e_span) = check_missing_sink(syntax) { + errs.push(Errors::new_single( + ErrorKind::SyntaxError(SyntaxErrorKind::MissingSink), + e_span, + )); + } + + if let Err(e_span) = check_literal_as_sink(syntax) { + errs.push(Errors::new_single( + ErrorKind::SyntaxError(SyntaxErrorKind::LiteralAsSink), + e_span, + )); + } + + if let Err(err_locs) = check_literal_as_filter(syntax) { + errs.push(Errors::new( + ErrorKind::SyntaxError(SyntaxErrorKind::LiteralAsFilter), + err_locs, + )); + } + + if let Err(e_span) = check_literal_with_args(syntax) { + errs.push(Errors::new_single( + ErrorKind::SyntaxError(SyntaxErrorKind::LiteralWithArgs), + e_span, + )); + } + + if errs.is_empty() { + Ok(()) + } else { + Err(errs) + } +} + +fn check_missing_streamer(syntax: &[PipelineElement]) -> Result<(), logos::Span> { + if let Some(&PipelineElement { + kind: PipelineElementKind::Pipe, + ref span, + }) = syntax.first() + { + Err(span.clone()) + } else { + Ok(()) + } +} + +fn check_missing_filters(syntax: &[PipelineElement]) -> Result<(), Vec> { + let mut missing_filter_locs = Vec::new(); + + for i in 0..syntax.len() { + if let ( + Some(&PipelineElement { + kind: PipelineElementKind::Pipe, + ref span, + }), + Some(&PipelineElement { + kind: PipelineElementKind::Pipe, + span: ref span1, + }), + ) = (syntax.get(i), syntax.get(i + 1)) + { + missing_filter_locs.push(span.start..span1.end); + } + } + + if missing_filter_locs.is_empty() { + Ok(()) + } else { + Err(missing_filter_locs) + } +} + +fn check_missing_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> { + if let Some(&PipelineElement { + kind: PipelineElementKind::Pipe, + ref span, + }) = syntax.last() + { + Err(span.clone()) + } else { + Ok(()) + } +} + +fn check_literal_as_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> { + let last_block: Option<&[PipelineElement]> = syntax + .split(|PipelineElement { kind, .. }| kind == &PipelineElementKind::Pipe) + .last(); + + // there HAS to be a better way to do this... this is HORRIBLE + if let Some(last_block) = last_block { + if let Some(PipelineElement { + kind: PipelineElementKind::Command(parts), + span, + }) = last_block.first() + { + if let Some(first_part) = parts.first() { + if matches!( + first_part, + CommandPart { + kind: CommandPartKind::Word(_), + .. + } + ) { + Ok(()) + } else { + Err(span.clone()) + } + } else { + Ok(()) + } + } else { + Ok(()) + } + } else { + Ok(()) + } +} + +fn check_literal_as_filter(syntax: &[PipelineElement]) -> Result<(), Vec> { + let errs = syntax + .iter() + .take(syntax.len() - 1) + .skip(1) + .filter(|element| { + !matches!( + element, + PipelineElement { + kind: PipelineElementKind::Pipe, + .. + } + ) + }) + .map(|item| { + let PipelineElement { + kind: PipelineElementKind::Command(c), + span, + } = item + else { + return None; + }; + + let Some(CommandPart { kind, .. }) = c.first() else { + return None; + }; + + if matches!(kind, CommandPartKind::Word(_)) { + None + } else { + Some(span) + } + }) + .filter_map(|err| err.map(Clone::clone)) + .collect::>(); + + if errs.is_empty() { + Ok(()) + } else { + Err(errs) + } +} + +fn check_literal_with_args(syntax: &[PipelineElement]) -> Result<(), logos::Span> { + if let Some(PipelineElement { + kind: PipelineElementKind::Command(c), + span, + }) = syntax.first() + { + if c.len() > 1 + && !matches!( + c.first(), + Some(CommandPart { + kind: CommandPartKind::Word(_), + .. + }) + ) + { + Err(span.clone()) + } else { + Ok(()) + } + } else { + Ok(()) + } +} diff --git a/src/syntax/check/test.rs b/src/syntax/check/test.rs new file mode 100644 index 0000000..9166ea3 --- /dev/null +++ b/src/syntax/check/test.rs @@ -0,0 +1,66 @@ +use crate::syntax::{ + check::{ + check_literal_as_filter, check_literal_as_sink, check_literal_with_args, + check_missing_filters, check_missing_sink, check_missing_streamer, + }, + parse_syntax, +}; + +#[test] +fn test_check_missing_streamer() { + let test_data = "| invert | save \"./image_processed.jpg\""; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!(check_missing_streamer(&syntax), Err(0..1)); +} + +#[test] +fn test_check_missing_filters() { + let test_data = "meow | | test | awa | | nya"; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!(check_missing_filters(&syntax), Err(vec![5..8, 20..25])); +} + +#[test] +fn test_check_missing_sink() { + let test_data = "meow | invert | "; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!(check_missing_sink(&syntax), Err(14..15)); +} + +#[test] +fn test_check_literal_as_sink() { + let test_data = "meow | test | 3"; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!(check_literal_as_sink(&syntax), Err(14..15)); +} + +#[test] +fn test_check_literal_as_filter() { + let test_data = "meow | \"gay\" | 42 | 3.14 | uwu"; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!( + check_literal_as_filter(&syntax), + Err(vec![7..12, 15..17, 20..24]) + ); +} + +#[test] +fn test_check_literal_as_filter_positive_on_sink() { + let test_data = "meow | 42"; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!(check_literal_as_filter(&syntax), Ok(())); +} + +#[test] +fn test_check_literal_with_args() { + let test_data = "14 12 | sink"; + let syntax = parse_syntax(test_data).unwrap(); + + assert_eq!(check_literal_with_args(&syntax), Err(0..5)); +} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs new file mode 100644 index 0000000..cb4bbfd --- /dev/null +++ b/src/syntax/mod.rs @@ -0,0 +1,110 @@ +use logos::Logos; +use logos::Span; + +use crate::lexer::Token; + +pub mod check; + +#[derive(Debug, Clone, PartialEq)] +pub struct PipelineElement { + pub kind: PipelineElementKind, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PipelineElementKind { + Pipe, + Command(Vec), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CommandPart { + pub kind: CommandPartKind, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum CommandPartKind { + Word(String), + Integer(i64), + Float(f64), + String(String), +} + +pub fn parse_syntax(input: &str) -> Result, Vec> { + let lexer = Token::lexer(input); + let mut errs = Vec::new(); + + let mut r = Vec::new(); + + let mut partial_command: Vec = Vec::new(); + for (tok, span) in lexer.spanned() { + if let Ok(tok) = tok { + match tok { + Token::Pipe => { + if !partial_command.is_empty() { + #[allow( + clippy::unwrap_used, + reason = "this branch can only run if partial_command isn't empty" + )] + let span = partial_command.first().unwrap().span.start + ..partial_command.last().unwrap().span.end; + r.push(PipelineElement { + kind: PipelineElementKind::Command(std::mem::take( + &mut partial_command, + )), + span, + }); + } + r.push(PipelineElement { + kind: PipelineElementKind::Pipe, + span, + }); + } + Token::Word(word) => partial_command.push(CommandPart { + kind: CommandPartKind::Word(word.to_owned()), + span, + }), + Token::IntLiteral(int) => partial_command.push(CommandPart { + kind: CommandPartKind::Integer(int), + span, + }), + Token::FloatLiteral(float) => partial_command.push(CommandPart { + kind: CommandPartKind::Float(float), + span, + }), + Token::StringLiteral(string) => partial_command.push(CommandPart { + kind: CommandPartKind::String(string), + span, + }), + } + } else { + errs.push(span); + } + } + + if !partial_command.is_empty() { + #[allow( + clippy::unwrap_used, + reason = "this branch can only run if partial_command isn't empty" + )] + let span = + partial_command.first().unwrap().span.start..partial_command.last().unwrap().span.end; + r.push(PipelineElement { + kind: PipelineElementKind::Command(std::mem::take(&mut partial_command)), + span, + }); + } + + if errs.is_empty() { + Ok(r) + } else { + Err(errs) + } +} + +#[test] +fn test_invalid_toks() { + let test_data = "meow | gay $ error!"; + assert_eq!(parse_syntax(test_data), Err(vec![11..12, 18..19])); +} diff --git a/src/typed.rs b/src/typed.rs new file mode 100644 index 0000000..fbe86ab --- /dev/null +++ b/src/typed.rs @@ -0,0 +1,234 @@ +use core::panic; + +use crate::{ + builtins::{TYPE_FLOAT, TYPE_INTEGER, TYPE_STRING}, + error::Errors, + namespace::{command::Command, r#type::Type, typedef::TypeDef, GlobalNamespace}, + syntax::{CommandPart, CommandPartKind, PipelineElement, PipelineElementKind}, + Span, +}; + +#[allow(dead_code, reason = "will be used later")] +#[derive(Debug)] +pub struct Expr<'a> { + kind: ExprKind<'a>, + span: Span, +} + +impl<'a> Expr<'a> { + fn try_find_concrete(&'a self, ns: &'a GlobalNamespace) -> IoTypes<'a> { + let Self { ref kind, .. } = self; + kind.try_find_concrete(ns) + } + + fn get_input_typedef(&'a self) -> Option> { + match &self.kind { + ExprKind::Command(c) => c.command.get_input_types(), + ExprKind::Literal(_) => None, + } + } +} + +#[derive(Debug)] +pub enum ExprKind<'a> { + Command(CommandExpr<'a>), + Literal(LiteralKind), +} + +impl<'a> ExprKind<'a> { + fn try_find_concrete(&'a self, ns: &'a GlobalNamespace) -> IoTypes<'a> { + match self { + ExprKind::Literal(lit) => IoTypes::Concrete(lit.get_type(ns)), + ExprKind::Command(c) => c.try_find_concrete(ns), + } + } +} + +#[derive(Debug)] +pub struct CommandExpr<'a> { + command: Command<'a>, + args: Vec>, +} + +impl<'a> CommandExpr<'a> { + fn try_find_concrete(&'a self, _ns: &'a GlobalNamespace) -> IoTypes<'a> { + let Self { command, .. } = self; + + match command.get_output_types() { + None => IoTypes::Empty, + Some(def) => { + if let Some(concrete) = ConcreteTypeDef::try_from_typedef(&def) { + IoTypes::Concrete(concrete) + } else { + // TODO: make it possible to get clear output type + IoTypes::Unclear + } + } + } + } +} + +#[derive(Debug)] +pub enum LiteralKind { + Int(i64), + Float(f64), + String(String), +} + +enum IoTypes<'a> { + /// expression does not expect/return anything + Empty, + /// expression does expect/return something, but some types are unclear + Unclear, + /// we know for sure what types the expression expects/returns + Concrete(ConcreteTypeDef<'a>), +} + +impl LiteralKind { + #[allow( + clippy::unwrap_used, + reason = "these are fetched by type name constants used for keeping names consistent in codebase, which cannot be None" + )] + fn get_type<'a>(&self, ns: &'a GlobalNamespace) -> ConcreteTypeDef<'a> { + ConcreteTypeDef::Single(match self { + LiteralKind::Int(_) => ns.get_type_by_name(TYPE_INTEGER).unwrap(), + LiteralKind::Float(_) => ns.get_type_by_name(TYPE_FLOAT).unwrap(), + LiteralKind::String(_) => ns.get_type_by_name(TYPE_STRING).unwrap(), + }) + } +} + +pub fn into_typed_repr( + ns: &GlobalNamespace, + syntax: Vec, +) -> Result, Errors> { + let mut res = Vec::new(); + let mut errs = Vec::new(); + + for item in syntax { + let PipelineElement { kind, span } = item; + match kind { + PipelineElementKind::Command(c) => { + if c.len() == 1 { + let CommandPart { kind, .. } = &c[0]; + + res.push(Expr { + kind: match kind { + CommandPartKind::Word(val) => ExprKind::Command(CommandExpr { + command: { + let Some(c) = ns.get_command_by_name(val) else { + errs.push(span); + continue; + }; + c + }, + args: Vec::new(), + }), + CommandPartKind::Integer(val) => { + ExprKind::Literal(LiteralKind::Int(*val)) + } + CommandPartKind::Float(val) => { + ExprKind::Literal(LiteralKind::Float(*val)) + } + CommandPartKind::String(val) => { + ExprKind::Literal(LiteralKind::String(val.clone())) + } + }, + span, + }); + } else { + let Some(CommandPart { + kind: CommandPartKind::Word(name), + span, + }) = c.first() + else { + panic!("tried conversion to typed representation with invalid syntax") + }; + + res.push(Expr { + kind: ExprKind::Command(CommandExpr { + command: { + let Some(c) = ns.get_command_by_name(name) else { + errs.push(span.clone()); + continue; + }; + c + }, + args: c + .iter() + .skip(1) + .map(|CommandPart { kind, span }| Expr { + kind: ExprKind::Literal(match kind { + CommandPartKind::String(val) + | CommandPartKind::Word(val) => { + LiteralKind::String(val.to_string()) + } + CommandPartKind::Integer(val) => LiteralKind::Int(*val), + CommandPartKind::Float(val) => LiteralKind::Float(*val), + }), + span: span.clone(), + }) + .collect(), + }), + span: span.clone(), + }); + } + } + PipelineElementKind::Pipe => {} + } + } + + if errs.is_empty() { + Ok(res) + } else { + Err(Errors::new(crate::error::ErrorKind::CommandNotFound, errs)) + } +} + +enum ConcreteTypeDef<'a> { + Single(Type<'a>), + List(Vec>), + Record(Vec<(String, ConcreteTypeDef<'a>)>), +} + +impl<'a> ConcreteTypeDef<'a> { + fn try_from_typedef(val: &TypeDef<'a>) -> Option { + match val { + TypeDef::Type(typ) => Some(Self::Single(*typ)), + TypeDef::Generic(_) => todo!(), + TypeDef::List(list) => { + let out: Vec> = + list.iter().filter_map(Self::try_from_typedef).collect(); + + (out.len() == list.len()).then_some(ConcreteTypeDef::List(out)) + } + TypeDef::Record(rec) => { + let out: Vec<(String, ConcreteTypeDef<'a>)> = rec + .iter() + .filter_map(|(name, def)| { + Self::try_from_typedef(def).map(|def| (name.clone(), def)) + }) + .collect(); + + (out.len() == rec.len()).then_some(ConcreteTypeDef::Record(out)) + } + } + } +} + +struct TypeChecker<'a> { + global_ns: &'a GlobalNamespace, + typed_syntax: Vec>, +} + +impl<'a> TypeChecker<'a> { + fn new(ns: &'a GlobalNamespace, syntax: Vec>) -> TypeChecker<'a> { + Self { + global_ns: ns, + typed_syntax: syntax, + } + } + + // not sure if this is the optimal alg, or even a working one, but lets see + fn check_forward() {} +} diff --git a/testfiles/bare.rpl b/testfiles/bare.rpl deleted file mode 100644 index 1770e37..0000000 --- a/testfiles/bare.rpl +++ /dev/null @@ -1,12 +0,0 @@ -( - [ - Read(( - source: File("/home/jade/example/file.png"), - format: Png - )), - Write(( - target: File("/home/jade/example/out.jpg"), - format: Jpeg - )) - ] -) diff --git a/testfiles/invert.rpl b/testfiles/invert.rpl deleted file mode 100644 index d17fe1a..0000000 --- a/testfiles/invert.rpl +++ /dev/null @@ -1,13 +0,0 @@ -( - [ - Read(( - source: File("/home/jade/example/file.png"), - format: Png - )), - Filter(Invert), - Write(( - target: File("/home/jade/example/inverted.jpg"), - format: Jpeg - )) - ] -)