diff --git a/Cargo.lock b/Cargo.lock index a0ada6e..a82b5ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,9 +191,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "arrow" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755b6da235ac356a869393c23668c663720b8749dd6f15e52b6c214b4b964cc7" +checksum = "dc208515aa0151028e464cc94a692156e945ce5126abd3537bb7fd6ba2143ed1" dependencies = [ "arrow-arith", "arrow-array", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "arrow-arith" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64656a1e0b13ca766f8440752e9a93e11014eec7b67909986f83ed0ab1fe37b8" +checksum = "e07e726e2b3f7816a85c6a45b6ec118eeeabf0b2a8c208122ad949437181f49a" dependencies = [ "arrow-array", "arrow-buffer", @@ -226,9 +226,9 @@ dependencies = [ [[package]] name = "arrow-array" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a4a6d2896083cfbdf84a71a863b22460d0708f8206a8373c52e326cc72ea1a" +checksum = "a2262eba4f16c78496adfd559a29fe4b24df6088efc9985a873d58e92be022d5" dependencies = [ "ahash", "arrow-buffer", @@ -243,9 +243,9 @@ dependencies = [ [[package]] name = "arrow-buffer" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef870583ce5e4f3b123c181706f2002fb134960f9a911900f64ba4830c7a43a" +checksum = "4e899dade2c3b7f5642eb8366cfd898958bcca099cde6dfea543c7e8d3ad88d4" dependencies = [ "bytes", "half", @@ -254,9 +254,9 @@ dependencies = [ [[package]] name = "arrow-cast" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac7eba5a987f8b4a7d9629206ba48e19a1991762795bbe5d08497b7736017ee" +checksum = "4103d88c5b441525ed4ac23153be7458494c2b0c9a11115848fdb9b81f6f886a" dependencies = [ "arrow-array", "arrow-buffer", @@ -275,9 +275,9 @@ dependencies = [ [[package]] name = "arrow-csv" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f12542b8164398fc9ec595ff783c4cf6044daa89622c5a7201be920e4c0d4c" +checksum = "43d3cb0914486a3cae19a5cad2598e44e225d53157926d0ada03c20521191a65" dependencies = [ "arrow-array", "arrow-cast", @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "arrow-data" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b095e8a4f3c309544935d53e04c3bfe4eea4e71c3de6fe0416d1f08bb4441a83" +checksum = "0a329fb064477c9ec5f0870d2f5130966f91055c7c5bce2b3a084f116bc28c3b" dependencies = [ "arrow-buffer", "arrow-schema", @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "arrow-ipc" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c63da4afedde2b25ef69825cd4663ca76f78f79ffe2d057695742099130ff6" +checksum = "ddecdeab02491b1ce88885986e25002a3da34dd349f682c7cfe67bab7cc17b86" dependencies = [ "arrow-array", "arrow-buffer", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "arrow-json" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9551d9400532f23a370cabbea1dc5a53c49230397d41f96c4c8eedf306199305" +checksum = "d03b9340013413eb84868682ace00a1098c81a5ebc96d279f7ebf9a4cac3c0fd" dependencies = [ "arrow-array", "arrow-buffer", @@ -337,9 +337,9 @@ dependencies = [ [[package]] name = "arrow-ord" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c07223476f8219d1ace8cd8d85fa18c4ebd8d945013f25ef5c72e85085ca4ee" +checksum = "f841bfcc1997ef6ac48ee0305c4dfceb1f7c786fe31e67c1186edf775e1f1160" dependencies = [ "arrow-array", "arrow-buffer", @@ -350,9 +350,9 @@ dependencies = [ [[package]] name = "arrow-row" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b194b38bfd89feabc23e798238989c6648b2506ad639be42ec8eb1658d82c4" +checksum = "1eeb55b0a0a83851aa01f2ca5ee5648f607e8506ba6802577afdda9d75cdedcd" dependencies = [ "arrow-array", "arrow-buffer", @@ -363,15 +363,15 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f40f6be8f78af1ab610db7d9b236e21d587b7168e368a36275d2e5670096735" +checksum = "85934a9d0261e0fa5d4e2a5295107d743b543a6e0484a835d4b8db2da15306f9" [[package]] name = "arrow-select" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac265273864a820c4a179fc67182ccc41ea9151b97024e1be956f0f2369c2539" +checksum = "7e2932aece2d0c869dd2125feb9bd1709ef5c445daa3838ac4112dcfa0fda52c" dependencies = [ "ahash", "arrow-array", @@ -383,9 +383,9 @@ dependencies = [ [[package]] name = "arrow-string" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44c8eed43be4ead49128370f7131f054839d3d6003e52aebf64322470b8fbd0" +checksum = "912e38bd6a7a7714c1d9b61df80315685553b7455e8a6045c27531d8ecd5b458" dependencies = [ "arrow-array", "arrow-buffer", @@ -416,11 +416,11 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +checksum = "06575e6a9673580f52661c92107baabffbf41e2141373441cbcdc47cb733003c" dependencies = [ - "bzip2 0.4.4", + "bzip2 0.5.1", "flate2", "futures-core", "memchr", @@ -476,9 +476,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.5.16" +version = "1.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50236e4d60fe8458de90a71c0922c761e41755adf091b1b03de1cef537179915" +checksum = "490aa7465ee685b2ced076bb87ef654a47724a7844e2c7d3af4e749ce5b875dd" dependencies = [ "aws-credential-types", "aws-runtime", @@ -543,9 +543,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.59.0" +version = "1.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00a35fc7e74f5be45839eb753568535c074a592185dd0a2d406685018d581c43" +checksum = "60186fab60b24376d3e33b9ff0a43485f99efd470e3b75a9160c849741d63d56" dependencies = [ "aws-credential-types", "aws-runtime", @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.60.0" +version = "1.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fa655b4f313124ce272cbc38c5fef13793c832279cec750103e5e6b71a54b8" +checksum = "7033130ce1ee13e6018905b7b976c915963755aef299c1521897679d6cd4f8ef" dependencies = [ "aws-credential-types", "aws-runtime", @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.60.0" +version = "1.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1cfe5e16b90421ea031f4c6348b534ef442e76f6bf4a1b2b592c12cc2c6af9" +checksum = "c5c1cac7677179d622b4448b0d31bcb359185295dc6fca891920cfb17e2b5156" dependencies = [ "aws-credential-types", "aws-runtime", @@ -857,16 +857,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937" +checksum = "675f87afced0413c9bb02843499dbbd3882a237645883f71a2b59644a6d2f753" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "memmap2", ] [[package]] @@ -970,9 +969,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.14" +version = "1.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" dependencies = [ "jobserver", "libc", @@ -1037,9 +1036,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.30" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", "clap_derive", @@ -1047,9 +1046,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.30" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", @@ -1821,9 +1820,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" dependencies = [ "serde", ] @@ -1834,6 +1833,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -1936,9 +1947,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", @@ -2735,9 +2746,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libflate" @@ -2808,9 +2819,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" @@ -2824,9 +2835,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "lz4_flex" @@ -2864,15 +2875,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - [[package]] name = "mimalloc" version = "0.1.43" @@ -2890,9 +2892,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -3157,6 +3159,8 @@ dependencies = [ "ariadne", "async-recursion", "chumsky", + "clap", + "enum_dispatch", "futures", "optd-core", "ordered-float 5.0.0", @@ -3226,9 +3230,9 @@ dependencies = [ [[package]] name = "parquet" -version = "54.2.0" +version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761c44d824fe83106e0600d2510c07bf4159a4985bf0569b513ea4288dc1b4fb" +checksum = "f88838dca3b84d41444a0341b19f347e8098a3898b0f21536654b8b799e11abd" dependencies = [ "ahash", "arrow-array", @@ -3591,9 +3595,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags 2.8.0", ] @@ -3698,9 +3702,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.9" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" +checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" dependencies = [ "cc", "cfg-if", @@ -4416,9 +4420,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d08feb8f695b465baed819b03c128dc23f57a694510ab1f06c77f763975685e" +checksum = "d9156ebd5870ef293bfb43f91c7a74528d363ec0d424afe24160ed5a4343d08a" dependencies = [ "cc", "cfg-if", @@ -4931,9 +4935,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" dependencies = [ "getrandom 0.3.1", "serde", @@ -5414,18 +5418,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", @@ -5463,9 +5467,9 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] diff --git a/optd-dsl/Cargo.toml b/optd-dsl/Cargo.toml index 7563965..5b60608 100644 --- a/optd-dsl/Cargo.toml +++ b/optd-dsl/Cargo.toml @@ -11,3 +11,13 @@ ordered-float = "5.0.0" futures = "0.3.31" tokio.workspace = true async-recursion.workspace = true +enum_dispatch = "0.3.13" +clap = { version = "4.5.31", features = ["derive"] } + +[lib] +name = "optd_dsl" +path = "src/lib.rs" + +[[bin]] +name = "optd-cli" +path = "src/cli/main.rs" diff --git a/optd-dsl/src/analyzer/mod.rs b/optd-dsl/src/analyzer/mod.rs index 85fe522..1d963ed 100644 --- a/optd-dsl/src/analyzer/mod.rs +++ b/optd-dsl/src/analyzer/mod.rs @@ -1 +1,3 @@ pub mod hir; +pub mod semantic; +pub mod r#type; diff --git a/optd-dsl/src/analyzer/semantic/error.rs b/optd-dsl/src/analyzer/semantic/error.rs new file mode 100644 index 0000000..848e7c0 --- /dev/null +++ b/optd-dsl/src/analyzer/semantic/error.rs @@ -0,0 +1,16 @@ +use ariadne::{Report, Source}; + +use crate::utils::{error::Diagnose, span::Span}; + +#[derive(Debug)] +pub struct SemanticError {} + +impl Diagnose for SemanticError { + fn report(&self) -> Report { + todo!() + } + + fn source(&self) -> (String, Source) { + todo!() + } +} diff --git a/optd-dsl/src/analyzer/semantic/mod.rs b/optd-dsl/src/analyzer/semantic/mod.rs new file mode 100644 index 0000000..a91e735 --- /dev/null +++ b/optd-dsl/src/analyzer/semantic/mod.rs @@ -0,0 +1 @@ +pub mod error; diff --git a/optd-dsl/src/analyzer/type/error.rs b/optd-dsl/src/analyzer/type/error.rs new file mode 100644 index 0000000..5a9774f --- /dev/null +++ b/optd-dsl/src/analyzer/type/error.rs @@ -0,0 +1,16 @@ +use ariadne::{Report, Source}; + +use crate::utils::{error::Diagnose, span::Span}; + +#[derive(Debug)] +pub struct TypeError {} + +impl Diagnose for TypeError { + fn report(&self) -> Report { + todo!() + } + + fn source(&self) -> (String, Source) { + todo!() + } +} diff --git a/optd-dsl/src/analyzer/type/mod.rs b/optd-dsl/src/analyzer/type/mod.rs new file mode 100644 index 0000000..a91e735 --- /dev/null +++ b/optd-dsl/src/analyzer/type/mod.rs @@ -0,0 +1 @@ +pub mod error; diff --git a/optd-dsl/src/cli/basic.op b/optd-dsl/src/cli/basic.op new file mode 100644 index 0000000..829b922 --- /dev/null +++ b/optd-dsl/src/cli/basic.op @@ -0,0 +1,114 @@ +data LogicalProps(schema_len: I64) + +data Scalar with + | ColumnRef(idx: Int64) + | Literal with + | IntLiteral(value: Int64) + | StringLiteral(value: String) + | BoolLiteral(value: Bool) + \ NullLiteral + | Arithmetic with + | Mult(left: Scalar, right: Scalar) + | Add(left: Scalar, right: Scalar) + | Sub(left: Scalar, right: Scalar) + \ Div(left: Scalar, right: Scalar) + | Predicate with + | And(children: [Predicate]) + | Or(children: [Predicate]) + | Not(child: Predicate) + | Equals(left: Scalar, right: Scalar) + | NotEquals(left: Scalar, right: Scalar) + | LessThan(left: Scalar, right: Scalar) + | LessThanEqual(left: Scalar, right: Scalar) + | GreaterThan(left: Scalar, right: Scalar) + | GreaterThanEqual(left: Scalar, right: Scalar) + | IsNull(expr: Scalar) + \ IsNotNull(expr: Scalar) + | Function with + | Cast(expr: Scalar, target_type: String) + | Substring(str: Scalar, start: Scalar, length: Scalar) + \ Concat(args: [Scalar]) + \ AggregateExpr with + | Sum(expr: Scalar) + | Count(expr: Scalar) + | Min(expr: Scalar) + | Max(expr: Scalar) + \ Avg(expr: Scalar) + +data Logical with + | Scan(table_name: String) + | Filter(child: Logical, cond: Predicate) + | Project(child: Logical, exprs: [Scalar]) + | Join( + left: Logical, + right: Logical, + typ: JoinType, + cond: Predicate + ) + \ Aggregate( + child: Logical, + group_by: [Scalar], + aggregates: [AggregateExpr] + ) + +data Physical with + | Scan(table_name: String) + | Filter(child: Physical, cond: Predicate) + | Project(child: Physical, exprs: [Scalar]) + | Join with + | HashJoin( + build_side: Physical, + probe_side: Physical, + typ: String, + cond: Predicate + ) + | MergeJoin( + left: Physical, + right: Physical, + typ: String, + cond: Predicate + ) + \ NestedLoopJoin( + outer: Physical, + inner: Physical, + typ: String, + cond: Predicate + ) + | Aggregate( + child: Physical, + group_by: [Scalar], + aggregates: [AggregateExpr] + ) + \ Sort( + child: Physical, + order_by: [(Scalar, SortOrder)] + ) + +data JoinType with + | Inner + | Left + | Right + | Full + \ Semi + +[rust] +fn (expr: Scalar) apply_children(f: Scalar => Scalar) = () + +fn (pred: Predicate) remap(map: {I64 : I64)}) = + match predicate + | ColumnRef(idx) => ColumnRef(map(idx)) + \ _ => predicate -> apply_children(child => rewrite_column_refs(child, map)) + +[rule] +fn (expr: Logical) join_commute = match expr + \ Join(left, right, Inner, cond) -> + let + right_indices = 0.right.schema_len, + left_indices = 0..left.schema_len, + remapping = left_indices.map(i => (i, i + right_len)) ++ + right_indices.map(i => (left_len + i, i)).to_map, + in + Project( + Join(right, left, Inner, cond.remap(remapping)), + right_indices.map(i => ColumnRef(i)).to_array + ) \ No newline at end of file diff --git a/optd-dsl/src/cli/main.rs b/optd-dsl/src/cli/main.rs new file mode 100644 index 0000000..dbe77c5 --- /dev/null +++ b/optd-dsl/src/cli/main.rs @@ -0,0 +1,105 @@ +//! CLI tool for the Optimizer DSL +//! +//! This tool provides a command-line interface for the Optimizer DSL compiler. +//! +//! # Usage +//! +//! ``` +//! # Parse a DSL file (validate syntax): +//! optd parse path/to/file.op +//! +//! # Parse a file and print the AST: +//! optd parse path/to/file.op --print-ast +//! +//! # Get help: +//! optd --help +//! optd parse --help +//! ``` +//! +//! When developing, you can run through cargo: +//! +//! ``` +//! cargo run -- parse examples/example.dsl +//! cargo run -- parse examples/example.dsl --print-ast +//! ``` + +use clap::{Parser, Subcommand}; +use optd_dsl::compiler::compile::{parse, CompileOptions}; +use optd_dsl::utils::error::Diagnose; +use std::fs; +use std::path::PathBuf; + +#[derive(Parser)] +#[command( + name = "optd", + about = "Optimizer DSL compiler and toolchain", + version, + author +)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Parse a DSL file and validate its syntax + Parse { + /// Input file to parse + #[arg(value_name = "FILE")] + input: PathBuf, + + /// Print the AST in a readable format + #[arg(long)] + print_ast: bool, + }, +} + +fn main() -> Result<(), Box> { + let cli = Cli::parse(); + + match &cli.command { + Commands::Parse { input, print_ast } => { + println!("Parsing file: {}", input.display()); + + // Improve file reading error handling + let source = match fs::read_to_string(input) { + Ok(content) => content, + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + eprintln!("❌ Error: File not found: {}", input.display()); + eprintln!( + "Please check that the file exists and you have correct permissions." + ); + } else { + eprintln!("❌ Error reading file: {}", e); + } + std::process::exit(1); + } + }; + + let options = CompileOptions { + source_path: input.to_string_lossy().to_string(), + }; + + match parse(&source, &options) { + Ok(ast) => { + println!("✅ Parse successful!"); + if *print_ast { + println!("\nAST Structure:"); + println!("{:#?}", ast); + } + } + Err(errors) => { + eprintln!("❌ Parse failed with {} errors:", errors.len()); + for error in errors { + error.print(std::io::stderr())?; + } + std::process::exit(1); + } + } + } + } + + Ok(()) +} diff --git a/optd-dsl/src/compiler/compile.rs b/optd-dsl/src/compiler/compile.rs new file mode 100644 index 0000000..99e7132 --- /dev/null +++ b/optd-dsl/src/compiler/compile.rs @@ -0,0 +1,65 @@ +use crate::lexer::lex::lex; +use crate::parser::ast::Module; +use crate::parser::module::parse_module; +use crate::utils::error::CompileError; + +/// Compilation options for the DSL +pub struct CompileOptions { + /// Path to the main module source file + pub source_path: String, +} + +/// Parse DSL source code to AST +/// +/// This function performs lexing and parsing stages of compilation, +/// returning either the parsed AST Module or collected errors. +/// +/// # Arguments +/// * `source` - The source code to parse +/// * `options` - Compilation options including source path +/// +/// # Returns +/// * `Result>` - The parsed AST or errors +pub fn parse(source: &str, options: &CompileOptions) -> Result> { + let mut errors = Vec::new(); + + // Step 1: Lexing + let (tokens_opt, lex_errors) = lex(source, &options.source_path); + errors.extend(lex_errors); + + match tokens_opt { + Some(tokens) => { + // Step 2: Parsing + let (ast_opt, parse_errors) = parse_module(tokens, source, &options.source_path); + errors.extend(parse_errors); + + match ast_opt { + Some(ast) if errors.is_empty() => Ok(ast), + _ => Err(errors), + } + } + None => Err(errors), + } +} + +/// Compile DSL source code to HIR +/// +/// This function performs the full compilation pipeline including lexing, +/// parsing, and semantic analysis to produce HIR. +/// +/// # Arguments +/// * `source` - The source code to compile +/// * `options` - Compilation options including source path +/// +/// # Returns +/// * `Result>` - The compiled HIR or errors +pub fn compile( + source: &str, + options: &CompileOptions, +) -> Result> { + // Step 1 & 2: Parse to AST + let _ast = parse(source, options)?; + + // Step 3: Semantic analysis to HIR + todo!("Implement semantic analysis to convert AST to HIR") +} diff --git a/optd-dsl/src/compiler/mod.rs b/optd-dsl/src/compiler/mod.rs new file mode 100644 index 0000000..0ac8652 --- /dev/null +++ b/optd-dsl/src/compiler/mod.rs @@ -0,0 +1 @@ +pub mod compile; diff --git a/optd-dsl/src/engine/mod.rs b/optd-dsl/src/engine/mod.rs index 3f26595..e6ce8ef 100644 --- a/optd-dsl/src/engine/mod.rs +++ b/optd-dsl/src/engine/mod.rs @@ -12,7 +12,7 @@ use bridge::{from_optd::partial_logical_to_value, into_optd::value_to_partial_lo use futures::StreamExt; use optd_core::cascades::ir::PartialLogicalPlan; use std::sync::Arc; -use utils::{errors::Error, streams::PartialLogicalPlanStream}; +use utils::{error::Error, streams::PartialLogicalPlanStream}; use CoreData::*; use Expr::*; diff --git a/optd-dsl/src/engine/utils/errors.rs b/optd-dsl/src/engine/utils/error.rs similarity index 100% rename from optd-dsl/src/engine/utils/errors.rs rename to optd-dsl/src/engine/utils/error.rs diff --git a/optd-dsl/src/engine/utils/mod.rs b/optd-dsl/src/engine/utils/mod.rs index de9566c..b8c4cae 100644 --- a/optd-dsl/src/engine/utils/mod.rs +++ b/optd-dsl/src/engine/utils/mod.rs @@ -1,3 +1,3 @@ -pub(super) mod errors; +pub(super) mod error; pub(super) mod macros; pub(super) mod streams; diff --git a/optd-dsl/src/engine/utils/streams.rs b/optd-dsl/src/engine/utils/streams.rs index 237abbb..678eed0 100644 --- a/optd-dsl/src/engine/utils/streams.rs +++ b/optd-dsl/src/engine/utils/streams.rs @@ -5,7 +5,7 @@ use crate::analyzer::hir::{Expr, Value}; use crate::capture; -use crate::engine::utils::errors::Error; +use crate::engine::utils::error::Error; use crate::utils::context::Context; use futures::{stream, Stream, StreamExt}; use optd_core::cascades::ir::PartialLogicalPlan; diff --git a/optd-dsl/src/lexer/error.rs b/optd-dsl/src/lexer/error.rs new file mode 100644 index 0000000..215ab03 --- /dev/null +++ b/optd-dsl/src/lexer/error.rs @@ -0,0 +1,89 @@ +use crate::utils::{error::Diagnose, span::Span}; +use ariadne::{Color, Label, Report, ReportKind, Source}; +use chumsky::error::{Simple, SimpleReason}; + +/// Wrapper of a Chumsky Lexer error that adapts it to the Ariadne reporting system. +/// +/// Handles lexical errors such as invalid characters and arithmetic overflow +/// in numeric literals. +#[derive(Debug)] +pub struct LexerError { + /// The complete source code being lexed + src_code: String, + /// The underlying Chumsky error + error: Simple, +} + +impl LexerError { + /// Creates a new lexer error from source code and a Chumsky error. + pub fn new(src_code: String, error: Simple) -> Self { + Self { src_code, error } + } +} + +impl Diagnose for LexerError { + fn report(&self) -> Report { + match self.error.reason() { + SimpleReason::Custom(msg) => { + // Special handling for numeric overflow + if msg.contains("overflow") { + Report::build(ReportKind::Error, self.error.span()) + .with_message("Integer overflow") + .with_label( + Label::new(self.error.span()) + .with_message("This numeric literal is too large") + .with_color(Color::Magenta), + ) + .with_help("Use a smaller number or a different numeric type") + .finish() + } else { + // Other custom errors + Report::build(ReportKind::Error, self.error.span()) + .with_message("Lexical error") + .with_label( + Label::new(self.error.span()) + .with_message(msg) + .with_color(Color::Magenta), + ) + .finish() + } + } + SimpleReason::Unexpected => { + // For unexpected character errors + let mut report = Report::build(ReportKind::Error, self.error.span()) + .with_message("Invalid token"); + + // What was found + if let Some(c) = self.error.found() { + report = report.with_label( + Label::new(self.error.span()) + .with_message(format!("Unexpected character: '{}'", c)) + .with_color(Color::Magenta), + ); + } else { + report = report.with_label( + Label::new(self.error.span()) + .with_message("Unexpected end of input") + .with_color(Color::Magenta), + ); + } + + report.finish() + } + // Unclosed should never happen in lexer, so panic + SimpleReason::Unclosed { span, delimiter } => { + panic!( + "Unexpected unclosed delimiter error in lexer: '{}' at {:?}", + delimiter, span + ); + } + } + } + + fn source(&self) -> (String, Source) { + ( + self.error.span().src_file.clone(), + Source::from(self.src_code.clone()), + ) + } +} diff --git a/optd-dsl/src/lexer/errors.rs b/optd-dsl/src/lexer/errors.rs deleted file mode 100644 index 552a1a9..0000000 --- a/optd-dsl/src/lexer/errors.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::fmt::{self, Display, Formatter}; - -use ariadne::{Color, Label, Report, ReportKind, Source}; -use chumsky::error::{Simple, SimpleReason}; - -use crate::utils::{errors::Reporter, span::Span}; - -#[derive(Debug)] -pub struct LexerError { - src_code: String, - error: Simple, -} - -impl LexerError { - pub fn new(src_code: String, error: Simple) -> Self { - Self { src_code, error } - } -} - -impl Reporter for LexerError { - fn report(&self) -> Report { - let reason = match &self.error.reason() { - SimpleReason::Custom(msg) => msg.clone(), - _ => self.error.to_string(), - }; - - Report::build(ReportKind::Error, self.error.span()) - .with_message("Lexer error") - .with_label( - Label::new(self.error.span()) - .with_message(&reason) - .with_color(Color::Red), - ) - .finish() - } - - fn source(&self) -> (String, Source) { - ( - self.error.span().src_file, - Source::from(self.src_code.clone()), - ) - } -} - -impl Display for LexerError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let reason = match &self.error.reason() { - SimpleReason::Custom(msg) => msg.clone(), - _ => self.error.to_string(), - }; - write!(f, "Lexer error at {:?}: {}", self.error.span(), reason) - } -} diff --git a/optd-dsl/src/lexer/lex.rs b/optd-dsl/src/lexer/lex.rs index 6d90184..13ee023 100644 --- a/optd-dsl/src/lexer/lex.rs +++ b/optd-dsl/src/lexer/lex.rs @@ -8,9 +8,9 @@ use chumsky::{ }; use ordered_float::OrderedFloat; -use crate::utils::{errors::Error, span::Span}; +use crate::utils::{error::CompileError, span::Span}; -use super::{errors::LexerError, tokens::Token}; +use super::{error::LexerError, tokens::Token}; /// Lexes a source string into a sequence of tokens with their positions. /// Uses Chumsky for lexing and Ariadne for error reporting. @@ -21,7 +21,7 @@ use super::{errors::LexerError, tokens::Token}; /// /// # Returns /// * `(Option>, Vec)` - Any successfully lexed tokens and errors -pub fn lex(source: &str, file_name: &str) -> (Option>, Vec) { +pub fn lex(source: &str, file_name: &str) -> (Option>, Vec) { let len = source.chars().count(); let eoi = Span::new(file_name.into(), len..len); diff --git a/optd-dsl/src/lexer/mod.rs b/optd-dsl/src/lexer/mod.rs index 99e4b29..ceea2b2 100644 --- a/optd-dsl/src/lexer/mod.rs +++ b/optd-dsl/src/lexer/mod.rs @@ -1,3 +1,3 @@ -pub mod errors; +pub mod error; pub mod lex; pub mod tokens; diff --git a/optd-dsl/src/lib.rs b/optd-dsl/src/lib.rs index 6262c0d..6234dce 100644 --- a/optd-dsl/src/lib.rs +++ b/optd-dsl/src/lib.rs @@ -1,4 +1,5 @@ pub mod analyzer; +pub mod compiler; pub mod engine; pub mod lexer; pub mod parser; diff --git a/optd-dsl/src/parser/error.rs b/optd-dsl/src/parser/error.rs new file mode 100644 index 0000000..0c70859 --- /dev/null +++ b/optd-dsl/src/parser/error.rs @@ -0,0 +1,115 @@ +use crate::{ + lexer::tokens::Token, + utils::{error::Diagnose, span::Span}, +}; +use ariadne::{Color, Label, Report, ReportKind, Source}; +use chumsky::error::{Simple, SimpleReason}; + +/// Wrapper of a Chumsky Parser error that adapts it to the Ariadne reporting system. +/// +/// Takes the errors produced by Chumsky during parsing and converts them to +/// rich error diagnostics using Ariadne. +#[derive(Debug)] +pub struct ParserError { + /// The complete source code being parsed + src_code: String, + /// The underlying Chumsky error + error: Simple, +} + +impl ParserError { + /// Creates a new parser error from source code and a Chumsky error. + pub fn new(src_code: String, error: Simple) -> Self { + Self { src_code, error } + } +} + +impl Diagnose for ParserError { + fn report(&self) -> Report { + match self.error.reason() { + SimpleReason::Unclosed { span, delimiter } => { + // Special handling for unclosed delimiters + Report::build(ReportKind::Error, self.error.span()) + .with_message("Unclosed delimiter") + .with_label( + Label::new(self.error.span()) + .with_message(format!("Expected closing delimiter for '{}'", delimiter)) + .with_color(Color::Red), + ) + .with_label( + Label::new(span.clone()) + .with_message("Delimiter opened here") + .with_color(Color::Cyan), + ) + .with_help(format!( + "Add a matching closing delimiter for '{}'", + delimiter + )) + .finish() + } + SimpleReason::Unexpected => { + // For unexpected token errors + let mut report = Report::build(ReportKind::Error, self.error.span()) + .with_message("Syntax error"); + + // What was found + if let Some(token) = self.error.found() { + report = report.with_label( + Label::new(self.error.span()) + .with_message(format!("Unexpected token: '{:?}'", token)) + .with_color(Color::Red), + ); + } else { + report = report.with_label( + Label::new(self.error.span()) + .with_message("Unexpected end of input") + .with_color(Color::Red), + ); + } + + // What was expected + let expected: Vec<_> = self + .error + .expected() + .filter_map(|token_opt| token_opt.as_ref().map(|token| format!("{:?}", token))) + .collect(); + + if !expected.is_empty() { + let expected_msg = if expected.len() == 1 { + format!("Expected '{}'", expected[0]) + } else { + format!( + "Expected one of: {}", + expected + .iter() + .map(|t| format!("'{}'", t)) + .collect::>() + .join(", ") + ) + }; + report = report.with_note(expected_msg); + } + + report.finish() + } + SimpleReason::Custom(msg) => { + // For custom error messages + Report::build(ReportKind::Error, self.error.span()) + .with_message("Parser error") + .with_label( + Label::new(self.error.span()) + .with_message(msg) + .with_color(Color::Red), + ) + .finish() + } + } + } + + fn source(&self) -> (String, Source) { + ( + self.error.span().src_file.clone(), + Source::from(self.src_code.clone()), + ) + } +} diff --git a/optd-dsl/src/parser/mod.rs b/optd-dsl/src/parser/mod.rs index f85d375..13573e6 100644 --- a/optd-dsl/src/parser/mod.rs +++ b/optd-dsl/src/parser/mod.rs @@ -1,8 +1,9 @@ -pub mod adt; +mod adt; pub mod ast; -pub mod expr; -pub mod function; +pub mod error; +mod expr; +mod function; pub mod module; -pub mod pattern; -pub mod r#type; -pub mod utils; +mod pattern; +mod r#type; +mod utils; diff --git a/optd-dsl/src/parser/module.rs b/optd-dsl/src/parser/module.rs index 842e6d8..e97a926 100644 --- a/optd-dsl/src/parser/module.rs +++ b/optd-dsl/src/parser/module.rs @@ -1,11 +1,46 @@ use crate::lexer::tokens::Token; +use crate::utils::error::CompileError; use crate::utils::span::Span; -use chumsky::prelude::*; +use chumsky::{prelude::*, Stream}; use super::adt::adt_parser; use super::ast::{Item, Module}; +use super::error::ParserError; use super::function::function_parser; +/// Parses a vector of tokens into a module AST. +/// Uses Chumsky for parsing and Ariadne for error reporting. +/// +/// # Arguments +/// * `tokens` - The tokens to parse +/// * `source` - The original source code (needed for error reporting) +/// * `file_name` - Name of the source file, used for error reporting +/// +/// # Returns +/// * `(Option, Vec)` - The parsed module (if successful) and any errors +pub fn parse_module( + tokens: Vec<(Token, Span)>, + source: &str, + file_name: &str, +) -> (Option, Vec) { + let len = source.chars().count(); + let eoi = Span::new(file_name.into(), len..len); + + let (module, errors) = module_parser() + .then_ignore(end()) + .parse_recovery(Stream::from_iter(eoi, tokens.into_iter())); + + let errors = errors + .into_iter() + .map(|e| ParserError::new(source.into(), e).into()) + .collect(); + + (module, errors) +} + +/// Creates a parser for modules. +/// +/// A module consists of a sequence of items, which can be either ADTs or functions. pub fn module_parser() -> impl Parser> + Clone { let adt = adt_parser().map(Item::Adt); let func = function_parser().map(Item::Function); @@ -14,19 +49,8 @@ pub fn module_parser() -> impl Parser #[cfg(test)] mod tests { - use chumsky::Stream; - use super::*; - use crate::{lexer::lex::lex, utils::span::Span}; - - fn parse_module(input: &str) -> (Option, Vec>) { - let (tokens, _) = lex(input, "test.txt"); - let len = input.chars().count(); - let eoi = Span::new("test.txt".into(), len..len); - module_parser() - .then_ignore(end()) - .parse_recovery(Stream::from_iter(eoi, tokens.unwrap().into_iter())) - } + use crate::lexer::lex::lex; #[test] fn test_module_parser() { @@ -146,12 +170,18 @@ mod tests { right_indices.map(i => ColumnRef(i)).to_array ) "#; - let (module, errors) = parse_module(source); - assert_eq!(errors.len(), 0); - assert!(module.is_some()); + // First lex the input to get tokens + let (tokens, lex_errors) = lex(source, "test.txt"); + assert_eq!(lex_errors.len(), 0, "Expected no lexer errors"); + assert!(tokens.is_some(), "Expected tokens from lexing"); + + // Then parse the tokens into a module + let (module, parse_errors) = parse_module(tokens.unwrap(), source, "test.txt"); + assert_eq!(parse_errors.len(), 0, "Expected no parser errors"); + assert!(module.is_some(), "Expected a module to be parsed"); let module = module.unwrap(); - assert_eq!(module.items.len(), 8); + assert_eq!(module.items.len(), 8, "Expected 8 items in the module"); } } diff --git a/optd-dsl/src/utils/error.rs b/optd-dsl/src/utils/error.rs new file mode 100644 index 0000000..c71f5a8 --- /dev/null +++ b/optd-dsl/src/utils/error.rs @@ -0,0 +1,57 @@ +use super::span::Span; +use crate::{ + analyzer::{r#type::error::TypeError, semantic::error::SemanticError}, + lexer::error::LexerError, + parser::error::ParserError, +}; +use ariadne::{Report, Source}; +use enum_dispatch::enum_dispatch; +use std::io::Write; + +/// Reporter of all compilation errors +/// +/// This trait provides a unified interface for error reporting across all +/// compilation phases of the DSL. It leverages the Ariadne crate for rich, +/// user-friendly error diagnostics with source code context. +#[enum_dispatch] +pub trait Diagnose { + /// Creates an Ariadne diagnostic report for the error + fn report(&self) -> Report; + + /// Returns the source code and filename where the error occurred + fn source(&self) -> (String, Source); + + /// Writes a formatted error report to the provided output + /// + /// This is a convenience method that combines `report()` and `source()` + /// to produce a complete error diagnostic. + fn print(&self, w: W) -> std::io::Result<()> { + self.report().write(self.source(), w) + } +} + +/// Unified error type for all compilation phases +/// +/// `CompileError` aggregates errors from all phases of the DSL compilation: +/// - Lexing (tokenization) +/// - Parsing (syntax analysis) +/// - Analysis (semantic analysis) +/// - Typing (type checking) +/// +/// Each error variant implements the `Diagnose` trait to provide consistent +/// error reporting using Ariadne's rich diagnostic format. +#[enum_dispatch(Diagnose)] +#[derive(Debug)] +pub enum CompileError { + /// Errors occurring during the lexing/tokenization phase + LexerError(LexerError), + + /// Errors occurring during the parsing/syntax analysis phase + ParserError(ParserError), + + /// Errors occurring during the semantic analysis phase + SemanticError(SemanticError), + + /// Errors occurring during the type analysis phase + TypeError(TypeError), +} diff --git a/optd-dsl/src/utils/errors.rs b/optd-dsl/src/utils/errors.rs deleted file mode 100644 index b32643a..0000000 --- a/optd-dsl/src/utils/errors.rs +++ /dev/null @@ -1,50 +0,0 @@ -use core::fmt; -use std::fmt::{Display, Formatter}; - -use ariadne::{Report, Source}; - -use crate::lexer::errors::LexerError; - -use super::span::Span; - -pub trait Reporter { - fn report(&self) -> Report; - fn source(&self) -> (String, Source); - fn eprint(&self) -> std::io::Result<()> { - let (file, source) = self.source(); - self.report().eprint((file, source)) - } -} - -#[derive(Debug)] -pub enum Error { - LexerError(LexerError), -} - -impl From for Error { - fn from(e: LexerError) -> Self { - Error::LexerError(e) - } -} - -impl Reporter for Error { - fn report(&self) -> Report { - match self { - Error::LexerError(e) => e.report(), - } - } - - fn source(&self) -> (String, Source) { - match self { - Error::LexerError(e) => e.source(), - } - } -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Error::LexerError(e) => e.fmt(f), - } - } -} diff --git a/optd-dsl/src/utils/mod.rs b/optd-dsl/src/utils/mod.rs index ca3b940..7c05f3d 100644 --- a/optd-dsl/src/utils/mod.rs +++ b/optd-dsl/src/utils/mod.rs @@ -1,3 +1,3 @@ pub mod context; -pub mod errors; +pub mod error; pub mod span; diff --git a/optd-dsl/src/utils/span.rs b/optd-dsl/src/utils/span.rs index 80c3586..da7a2ce 100644 --- a/optd-dsl/src/utils/span.rs +++ b/optd-dsl/src/utils/span.rs @@ -1,13 +1,30 @@ +/// Span module for source code location tracking +/// +/// This module provides span functionality for tracking the location of +/// elements in source code. It integrates with: +/// - Chumsky: For parser error reporting during syntax analysis +/// - Ariadne: For rich, user-friendly error diagnostics with code context +/// +/// The span system connects AST nodes with their original source locations, +/// enabling precise error messages during compilation. use core::fmt; use std::ops::Range; +/// A location span in source code that tracks the file and character range. +/// +/// Used to connect AST nodes with their original location in the source code, +/// enabling accurate error reporting. #[derive(Clone, PartialEq, Eq)] pub struct Span { + /// The source file path or identifier pub src_file: String, + + /// The range of character positions (start, end) in the source file pub range: (usize, usize), } impl Span { + /// Creates a new span from a source file and character range. pub fn new(src_file: String, range: Range) -> Self { Self { src_file, @@ -16,13 +33,21 @@ impl Span { } } +/// A value annotated with its source code location. +/// +/// This type enriches AST nodes with location information, which is essential +/// for providing meaningful error messages during compilation. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Spanned { + /// The wrapped value pub value: Box, + + /// The source location of the value pub span: Span, } impl Spanned { + /// Creates a new spanned value. pub fn new(value: T, span: Span) -> Self { Self { value: Box::new(value), @@ -40,22 +65,18 @@ impl fmt::Debug for Span { impl chumsky::Span for Span { type Context = String; type Offset = usize; - fn new(src_file: String, range: Range) -> Self { Self { src_file, range: (range.start, range.end), } } - fn context(&self) -> String { self.src_file.clone() } - fn start(&self) -> Self::Offset { self.range.0 } - fn end(&self) -> Self::Offset { self.range.1 } @@ -63,15 +84,12 @@ impl chumsky::Span for Span { impl ariadne::Span for Span { type SourceId = String; - fn source(&self) -> &String { &self.src_file } - fn start(&self) -> usize { self.range.0 } - fn end(&self) -> usize { self.range.1 }