diff --git a/.gitignore b/.gitignore index d6e3ba0..df77090 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ /db/ .vim .vscode +*.blz +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index d352282..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,609 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blaze" -version = "0.1.0" -dependencies = [ - "bson", - "colored", - "dotenvy", - "rand", - "regex", - "serde", - "strum", - "strum_macros", -] - -[[package]] -name = "bson" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d43b38e074cc0de2957f10947e376a1d88b9c4dbab340b590800cc1b2e066b2" -dependencies = [ - "ahash", - "base64", - "bitvec", - "hex", - "indexmap", - "js-sys", - "once_cell", - "rand", - "serde", - "serde_bytes", - "serde_json", - "time", - "uuid", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "colored" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" -dependencies = [ - "lazy_static", - "windows-sys", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "getrandom" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.154" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - -[[package]] -name = "rustversion" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" - -[[package]] -name = "ryu" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "serde" -version = "1.0.200" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_bytes" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.200" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" - -[[package]] -name = "strum_macros" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "uuid" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 2dafd0b..f6db798 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,16 @@ edition = "2021" [dependencies] regex = "1.10" bson = "2.10" +uuid = "1.8.0" +ctrlc = "3.4.4" rand = "0.8" strum = "0.26" +chrono = "0.4.38" colored = "2.1.0" strum_macros = "0.26" dotenvy = "0.15" -serde = { version = "1.0", features = ["derive"] } \ No newline at end of file +crossbeam = "0.8.4" +http-body-util = "0.1" +hyper = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["full"] } +serde = { version = "1", features = ["derive"] } diff --git a/README.md b/README.md index fc02ba4..20bc612 100644 --- a/README.md +++ b/README.md @@ -5,87 +5,103 @@ And of course it's blazingly fast ## ✨ Blaze Language Syntax (currently uncompleted) -1. Manager.blz (used to raise the database with packages, settings and ispects included) +1. Manage file (configuration and attaching to a datafile are only available in this type of files) ```ruby manage ( - packages = "./packages", - max_connections = 8, - port = "6980", - host = "127.0.0.1" + address = "127.0.0.1:3306", + sessions_limit = 250, + session_lifetime = 120 ); -import users:all, animals:species; - -inspect all; - -attach "./data"; +import scheme[target]; +attach()"./data.dblz"; ``` -2. Basic Scheme and some declarations +2. A package with functions, events, enums, plans, lanes, and tables ```ruby package scheme; -enum Gender: str { - Male, - Female, - Other, - Unspecified +// Enum Members have their own IDs +// so you can access them without any changes but here. +enum Gender { + Unspecified = [ 0, "unknown" ], + Male = [ 1, "boy" ], + Female = [ 2, "girl" ], + Other = [ 3, "other" ], }; -enum TargetAudience: str { - Kids, - Everyone, - Adults, - Elderly +// Snake_case is the standard +mut variable_showcase = Gender.Male; // "boy" +fin best_gender: str = Gender[0]; // "unknown" + +enum TargetAudience { + Kids = [ 0, lower(self [0]) ], + Everyone = [ 1, lower(self [1]) ], + Adults = [ 2, lower(self [2]) ], + Elderly = [ 3, lower(self [3]) ], }; +// Plan is a data structure that can be used for instances creation, +// and tables & lanes implementation +plan Geo { + x: float, y: float +} + +// Table ID's are created without explicit declaration, +// Exclamation mark means the field value must be unique table countries { - name: str <=50, - alpha2: str 2, - alpha3: str 3, - geolocation: geo; + name: !str[ <50 ], + alpha2: !str[ =2 ], + alpha3: !str[ =2 ], + geolocation: Geo; }; +// db.countries is some sort of a foreign key, +// Question Mark means the country field can be null table accounts: uuid { - name: str <=30 = format("User{}", self.id), - bio: str <=200, - password: str, - gender: Gender = Gender.Unspecified, - age: int >0 <100, - country: &countries?, - created_at: datetime = "now"; + name: str[ <=30 ] = format("User{}", id), + bio: str[ <=200 ], + password: str[ >8 ], + age: int[ >0 <100 ], + gender: Gender = Gender.Unspecified, + created_at: datetime = "now"; + country: db.countries?, }; - -table products: uuid { - title: str = "product", - seller: &accounts, - price: float >= 0, +plan Products { + title: str = "product", + created_at: datetime = "now", + seller: db.accounts, + price: float[>=0], description: str, - created_at: datetime = "now", - sales_count: int >=0 , - audience: TargetAudience = TargetAudience.Everyone; + sales_count: int[>-1], + audience: TargetAudience = TargetAudience.Everyone; } +table products(Products): uuid; table shopping_cart { - account: &account!, - added_products: &products[], - last_update: datetime = "now"; + account: !db.accounts, + added_products: !arr[db.products], + last_update: timestamp[local] = "now"; }; -event shopping_cart_update (&shopping_cart.added_products, "change") { - &shopping_cart.last_update = "now"; +// Should be considered +event shopping_cart_updated("update", db.shopping_cart): x { + x.last_update = "now"; }; function get_population_of(country_name: str): int { - fin country_id = &countries.{self.name=country_name}.id; - count(&accounts.{self.country=country_id}) + // db.contries.() is a call to a database, + // the condition inside parenthesis filters the "rows"; + // .id returns an array of all the IDs of the filtered rows + fin country_id = db.countries.(self.name==country_name).id; + count(db.accounts.(country=country_id)) }; -function get_total_cart_price(products: &products[]): float { +function get_total_cart_price(products: Products): float { mut total: float = 0; product of products { total += product.price; }; total -} \ No newline at end of file +} diff --git a/src/db/create_db.rs b/src/db/create_db.rs deleted file mode 100644 index 21931ad..0000000 --- a/src/db/create_db.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::fs::{self, File}; -use std::io::{Result, Write}; -use std::path::{Path, PathBuf}; - -pub const OFFICIAL_REPOSITORY: &str = "https://github.com/Reeliks/blaze"; - -pub fn create_db_structure(path_to_db: &str) -> Result<()> { - let mut db_workdir_path_buf = PathBuf::from(&path_to_db); - db_workdir_path_buf.push("datablaze"); - create_db_folders(&db_workdir_path_buf)?; - - let is_manage_file_created: bool = create_manage_file(&db_workdir_path_buf)?; - if is_manage_file_created { - println!("manage.blz has been created") - } else { - println!("manage.blz already exists; skipping..."); - } - println!("\nA new datablaze has been structured. Use 'blaze --help' to see the commands.\nTo contribute the development process, check out the official repository:\n{}", OFFICIAL_REPOSITORY); - - Ok(()) -} - -fn create_db_folders(db_path_buf: &Path) -> Result<()> { - for folder in ["data", "model"] { - let mut cloned_db_path_buf = db_path_buf.to_path_buf(); - cloned_db_path_buf.push(folder); - fs::create_dir_all(cloned_db_path_buf)?; - } - Ok(()) -} - -fn create_manage_file(path_to_db_buf: &Path) -> Result { - let mut managing_file_path_buf = path_to_db_buf.to_path_buf(); - let manage_file_content = br#"manage ( - tmax_connections = 3, - work_dir = "/", - backups_dir = "backups/" -); - -attach "/data/main;"#; - - managing_file_path_buf.push("manage.blz"); - if let Err(e) = fs::metadata(managing_file_path_buf.to_str().unwrap()) { - match e.kind() { - std::io::ErrorKind::NotFound => { - let mut managing_file = File::create(&mut managing_file_path_buf)?; - managing_file.write_all(manage_file_content)?; - return Ok(true); - } - _ => { - panic!("{}", e); - } - }; - }; - Ok(false) -} diff --git a/src/fs/filesystem.rs b/src/db/filesystem.rs similarity index 93% rename from src/fs/filesystem.rs rename to src/db/filesystem.rs index 97e9e44..2156997 100644 --- a/src/fs/filesystem.rs +++ b/src/db/filesystem.rs @@ -53,7 +53,10 @@ impl Fs { if *key == table_name { value.push(data.clone()); } else { - doc.insert(table_name.clone(), Bson::Array(vec![Bson::from(data.clone())])); + doc.insert( + table_name.clone(), + Bson::Array(vec![Bson::from(data.clone())]), + ); } } diff --git a/src/db/mod.rs b/src/db/mod.rs index 2db1a55..a286318 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1 +1 @@ -pub mod create_db; +pub mod filesystem; diff --git a/src/fs/mod.rs b/src/fs/mod.rs deleted file mode 100644 index a286318..0000000 --- a/src/fs/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod filesystem; diff --git a/src/lib.rs b/src/lib.rs index 05050dc..48ecf0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod db; -pub mod fs; +pub mod prelude; +pub mod routine; pub mod scripting; pub mod server; pub mod shell; diff --git a/src/main.rs b/src/main.rs index 5427807..17f997b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,6 @@ -use std::io::Result; +use blaze::shell::command_handler::ShellCommandHandler; -use blaze::shell; - -fn main() -> Result<()> { - shell::handling::handle_command_arguments()?; - Ok(()) +#[tokio::main] +async fn main() { + ShellCommandHandler::new().handle_command().await.unwrap(); } diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..0f1c9b2 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1 @@ +pub use std::io::ErrorKind; diff --git a/src/routine/formatting.rs b/src/routine/formatting.rs new file mode 100644 index 0000000..62e6e97 --- /dev/null +++ b/src/routine/formatting.rs @@ -0,0 +1,11 @@ +use colored::Colorize; + +pub trait MessagesFormatting { + fn into_hint(self) -> String; +} + +impl MessagesFormatting for String { + fn into_hint(self) -> String { + format!(" * {}", self.bright_blue()) + } +} diff --git a/src/routine/info_channel.rs b/src/routine/info_channel.rs new file mode 100644 index 0000000..18f6258 --- /dev/null +++ b/src/routine/info_channel.rs @@ -0,0 +1,39 @@ +use crossbeam::channel::{bounded, Sender}; +use std::thread; + +use std::io::Result; + +pub struct InfoChannel { + sender: Option>, +} + +impl InfoChannel { + pub fn new(sender: Option>) -> Self { + InfoChannel { sender } + } + + pub fn send(self, message: String) -> Result<()> { + if let Some(channel) = self.sender { + channel.send(message).unwrap(); + } + Ok(()) + } +} + +impl Clone for InfoChannel { + fn clone(&self) -> Self { + InfoChannel { + sender: self.sender.clone(), + } + } +} + +pub fn get_console_info_channel() -> InfoChannel { + let (tx, rx) = bounded(100); + thread::spawn(move || loop { + if let Ok(received) = rx.try_recv() { + println!("{}", received); + } + }); + InfoChannel::new(Some(tx)) +} diff --git a/src/routine/mod.rs b/src/routine/mod.rs new file mode 100644 index 0000000..cedbbb9 --- /dev/null +++ b/src/routine/mod.rs @@ -0,0 +1,2 @@ +pub mod formatting; +pub mod info_channel; diff --git a/src/scripting/ast/binary_operator.rs b/src/scripting/ast/binary_operator.rs index a65a2ab..dce655b 100644 --- a/src/scripting/ast/binary_operator.rs +++ b/src/scripting/ast/binary_operator.rs @@ -1,6 +1,4 @@ -use crate::scripting::tokens::TokenType; - -use super::expression::ExpressionNode; +use super::{expression::ExpressionNode, tokens::TokenType}; pub struct BinaryOperatorNode { _operator: TokenType, diff --git a/src/scripting/ast/boolean.rs b/src/scripting/ast/boolean.rs index 696a1d4..c9a2733 100644 --- a/src/scripting/ast/boolean.rs +++ b/src/scripting/ast/boolean.rs @@ -1,8 +1,6 @@ use std::io; -use crate::scripting::tokens::TokenType; - -use super::expression::ExpressionNode; +use super::{expression::ExpressionNode, tokens::TokenType}; pub struct BooleanNode { _state: bool, diff --git a/src/scripting/ast/functional_return.rs b/src/scripting/ast/functional_return.rs index 4d6ea13..91e9ec6 100644 --- a/src/scripting/ast/functional_return.rs +++ b/src/scripting/ast/functional_return.rs @@ -1,7 +1,7 @@ use super::expression::ExpressionNode; pub struct FunctionalReturnNode { - _value: Option> + _value: Option>, } impl ExpressionNode for FunctionalReturnNode { @@ -12,8 +12,6 @@ impl ExpressionNode for FunctionalReturnNode { impl FunctionalReturnNode { pub fn new(value: Option>) -> Self { - FunctionalReturnNode { - _value: value - } + FunctionalReturnNode { _value: value } } } diff --git a/src/scripting/ast/loop_control.rs b/src/scripting/ast/loop_control.rs index 08c9500..3882a22 100644 --- a/src/scripting/ast/loop_control.rs +++ b/src/scripting/ast/loop_control.rs @@ -2,11 +2,11 @@ use super::expression::ExpressionNode; pub enum LoopControlType { Continue, - Break + Break, } pub struct LoopControlNode { - _control_type: LoopControlType + _control_type: LoopControlType, } impl ExpressionNode for LoopControlNode { @@ -18,7 +18,7 @@ impl ExpressionNode for LoopControlNode { impl LoopControlNode { pub fn new(control_type: LoopControlType) -> Self { LoopControlNode { - _control_type: control_type + _control_type: control_type, } } } diff --git a/src/scripting/ast/mod.rs b/src/scripting/ast/mod.rs index 87478f5..d2edd18 100644 --- a/src/scripting/ast/mod.rs +++ b/src/scripting/ast/mod.rs @@ -1,3 +1,5 @@ +pub mod tokens; + pub mod binary_operator; pub mod body; pub mod boolean; @@ -5,14 +7,14 @@ pub mod call; pub mod conditional_tree; pub mod expression; pub mod function_declaration; -pub mod identifier; -pub mod member; pub mod functional_return; -pub mod while_loop; +pub mod identifier; pub mod loop_control; +pub mod member; pub mod null; pub mod number; pub mod parameter; pub mod string; pub mod unary_operator; pub mod variable_declaration; +pub mod while_loop; diff --git a/src/scripting/tokens.rs b/src/scripting/ast/tokens.rs similarity index 96% rename from src/scripting/tokens.rs rename to src/scripting/ast/tokens.rs index 8fa5486..d10d87d 100644 --- a/src/scripting/tokens.rs +++ b/src/scripting/ast/tokens.rs @@ -61,6 +61,9 @@ pub enum TokenType { Fin, Function, Enum, + Table, + Lane, + Plan, // Brackets LPar, RPar, @@ -95,6 +98,9 @@ impl TokenType { TokenType::Mut => r"mut\b", TokenType::Fin => r"fin\b", TokenType::Enum => r"enum\b", + TokenType::Lane => r"lane\b", + TokenType::Table => r"table\b", + TokenType::Plan => r"plan\b", TokenType::True => r"true\b", TokenType::False => r"false\b", TokenType::Null => r"null\b", diff --git a/src/scripting/ast/unary_operator.rs b/src/scripting/ast/unary_operator.rs index a67e343..8d99dcf 100644 --- a/src/scripting/ast/unary_operator.rs +++ b/src/scripting/ast/unary_operator.rs @@ -1,6 +1,7 @@ -use crate::scripting::tokens::{TokenSide, TokenType}; - -use super::expression::ExpressionNode; +use super::{ + expression::ExpressionNode, + tokens::{TokenSide, TokenType}, +}; pub struct UnaryOperatorNode { _operator: TokenType, diff --git a/src/scripting/ast/while_loop.rs b/src/scripting/ast/while_loop.rs index a1f63d8..0065675 100644 --- a/src/scripting/ast/while_loop.rs +++ b/src/scripting/ast/while_loop.rs @@ -2,7 +2,7 @@ use super::{body::BodyNode, expression::ExpressionNode}; pub struct WhileLoopNode { _condition: Box, - _body: BodyNode + _body: BodyNode, } impl ExpressionNode for WhileLoopNode { @@ -15,7 +15,7 @@ impl WhileLoopNode { pub fn new(condition: Box, body: BodyNode) -> Self { WhileLoopNode { _condition: condition, - _body: body + _body: body, } } } diff --git a/src/scripting/context.rs b/src/scripting/context.rs index 8360fc3..ee1b6ac 100644 --- a/src/scripting/context.rs +++ b/src/scripting/context.rs @@ -5,7 +5,7 @@ pub struct Context { } impl Context { - fn new(code_source: String) -> Self { + pub fn new(code_source: String) -> Self { Context { code_source, position: 0, diff --git a/src/scripting/executor.rs b/src/scripting/executor.rs index 06e6693..4f3fbef 100644 --- a/src/scripting/executor.rs +++ b/src/scripting/executor.rs @@ -1,13 +1,16 @@ -pub struct Executor {} +use super::{ast::body::BodyNode, context::Context}; +use std::io::Result; -impl Executor { - pub fn new() -> Self { - Executor {} - } +pub struct Executor<'a> { + _context: &'a mut Context, } -impl Default for Executor { - fn default() -> Self { - Self::new() +impl<'a> Executor<'a> { + pub fn new(context: &'a mut Context) -> Self { + Self { _context: context } + } + + pub fn execute(self, _nodes: BodyNode) -> Result<()> { + Ok(()) } } diff --git a/src/scripting/lexer.rs b/src/scripting/lexer.rs index 4c9673c..0e96f4b 100644 --- a/src/scripting/lexer.rs +++ b/src/scripting/lexer.rs @@ -3,21 +3,26 @@ use regex::Regex; use std::io; use strum::IntoEnumIterator; +use crate::routine::info_channel::InfoChannel; + +use super::ast::tokens::{Token, TokenType, WHITESPACE_TOKENS}; use super::context::Context; -use super::tokens::{Token, TokenType, WHITESPACE_TOKENS}; +use crate::prelude::*; pub struct Lexer { pub context: Context, code: String, tokens: Vec, + info_channel: InfoChannel, // Connect it only on debugging } impl Lexer { - pub fn new(code: String) -> Self { - Lexer { + pub fn new(code: String, info_channel: InfoChannel) -> Self { + Self { code, context: Context::default(), tokens: vec![], + info_channel, } } @@ -31,20 +36,23 @@ impl Lexer { Ok(proceed_parsing) => { if !proceed_parsing { self.tokens.retain(|token| !token.is_type(TokenType::Space)); - return Ok(self.tokens); + return Ok(self.tokens.clone()); } let last_token = self.tokens.last().unwrap(); if !WHITESPACE_TOKENS.contains(&last_token.token_type) { let start_position = 1 + self.context.position - last_token.value.len() as u64; - println!( - "{}:{} = {}", - start_position, last_token.value, last_token.token_type - ); + self.info_channel + .clone() + .send(format!( + "{}:{} = {}", + start_position, last_token.value, last_token.token_type + )) + .unwrap(); } } Err(err) => { - eprintln!("{}", err); + self.info_channel.send(err.to_string()).unwrap(); break; } }; @@ -61,7 +69,11 @@ impl Lexer { let token_regex_string = TokenType::regex_str(&token_type); let token_regex = Regex::new(&format!(r#"^{}"#, token_regex_string)).unwrap(); if let Some(matches) = token_regex.find(positioned_code) { - let matched_str = matches.as_str(); + let matched_str = if token_type == TokenType::Number { + matches.as_str().replace('_', "") + } else { + matches.as_str().to_string() + }; self.tokens.push(Token { token_type, start: self.context.position, @@ -78,7 +90,7 @@ impl Lexer { } } Err(io::Error::new( - io::ErrorKind::Other, + ErrorKind::Other, format!( "{}: '{}' {} <-= at {}:{}:{}", "Lexical Error".bright_red(), @@ -106,7 +118,7 @@ impl Lexer { let last_token_is_number = last_token.is_type(TokenType::Number); if last_token_is_number && current_token_is_alphanumeric { return Err(io::Error::new( - io::ErrorKind::Other, + ErrorKind::Other, format!( "{}: numbers cannot end with alphanumeric <-= at {}:{}:{}", "Lexical Error".bright_red(), @@ -142,9 +154,9 @@ impl Lexer { || left_side_unresolved_chars_regex.is_match(char_before) { return Err(io::Error::new( - io::ErrorKind::Other, + ErrorKind::Other, format!( - "{}: \"{}\" near a string with no space between <-= at {}:{}:{}", + "{}: \"{}\" before a string with no space between <-= at {}:{}:{}", "Lexical Error".bright_red(), char_before, self.context.code_source, @@ -163,7 +175,7 @@ impl Lexer { .to_string(); if both_sides_unresolved_chars_regex.is_match(char_after) { return Err(io::Error::new( - io::ErrorKind::Other, + ErrorKind::Other, format!( "{}: \"{}\" after a string with no space between <-= at {}:{}:{}", "Lexical Error".bright_red(), diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs index 604a387..c9fc292 100644 --- a/src/scripting/mod.rs +++ b/src/scripting/mod.rs @@ -3,4 +3,3 @@ pub mod context; pub mod executor; pub mod lexer; pub mod parser; -pub mod tokens; diff --git a/src/scripting/parser.rs b/src/scripting/parser.rs index 0de2db8..5334792 100644 --- a/src/scripting/parser.rs +++ b/src/scripting/parser.rs @@ -1,26 +1,30 @@ -use super::ast::binary_operator::BinaryOperatorNode; -use super::ast::body::BodyNode; -use super::ast::boolean::BooleanNode; -use super::ast::call::CallNode; -use super::ast::conditional_tree::{ConditionalTreeNode, Conditions}; -use super::ast::expression::ExpressionNode; -use super::ast::function_declaration::FunctionDeclarationNode; -use super::ast::functional_return::FunctionalReturnNode; -use super::ast::identifier::IdentifierNode; -use super::ast::loop_control::{LoopControlNode, LoopControlType}; -use super::ast::member::MemberNode; -use super::ast::null::NullNode; -use super::ast::number::NumberNode; -use super::ast::parameter::{Parameter, ParameterType, Parameters}; -use super::ast::string::StringNode; -use super::ast::unary_operator::UnaryOperatorNode; -use super::ast::variable_declaration::VariableDeclaration; -use super::ast::while_loop::WhileLoopNode; -use super::context::Context; -use super::tokens::{ - Token, TokenSide, TokenType, BINARY_OPERATOR_TOKENS, FORMULA_TOKENS, UNARY_OPERATOR_TOKENS, - VARIABLE_ASSIGNMENT_TOKENS, +use super::ast::{ + binary_operator::BinaryOperatorNode, + body::BodyNode, + boolean::BooleanNode, + call::CallNode, + conditional_tree::{ConditionalTreeNode, Conditions}, + expression::ExpressionNode, + function_declaration::FunctionDeclarationNode, + functional_return::FunctionalReturnNode, + identifier::IdentifierNode, + loop_control::{LoopControlNode, LoopControlType}, + member::MemberNode, + null::NullNode, + number::NumberNode, + parameter::{Parameter, ParameterType, Parameters}, + string::StringNode, + tokens::{ + Token, TokenSide, TokenType, BINARY_OPERATOR_TOKENS, FORMULA_TOKENS, UNARY_OPERATOR_TOKENS, + VARIABLE_ASSIGNMENT_TOKENS, + }, + unary_operator::UnaryOperatorNode, + variable_declaration::VariableDeclaration, + while_loop::WhileLoopNode, }; +use super::context::Context; +use crate::routine::info_channel::InfoChannel; + use colored::*; use rand::seq::SliceRandom; use std::io::{self, Result}; @@ -30,16 +34,21 @@ pub struct Parser { context: Context, parser_position: u64, + _info_channel: InfoChannel, syntax_error_marking: ColoredString, } impl Parser { - pub fn new(tokens: Vec) -> Self { + pub fn new( + tokens: Vec, + info_channel: InfoChannel, // Connect it only on debugging + ) -> Self { Parser { context: Context::default(), tokens, parser_position: 0, + _info_channel: info_channel, syntax_error_marking: "Syntax Error".bright_red(), } } @@ -274,11 +283,9 @@ impl Parser { ))) } TokenType::Return => { - let returned_formula_node - = if self.move_if_next_token_is(FORMULA_TOKENS.to_vec()) { + let returned_formula_node = if self.move_if_next_token_is(FORMULA_TOKENS.to_vec()) { Some(self.require_formula()?) - } - else { + } else { None }; Ok(Box::new(FunctionalReturnNode::new(returned_formula_node))) @@ -289,15 +296,11 @@ impl Parser { self.move_position(); Ok(Box::new(WhileLoopNode::new( condition_node, - self.require_body()? + self.require_body()?, ))) } - TokenType::Continue => { - Ok(Box::new(LoopControlNode::new(LoopControlType::Continue))) - } - TokenType::Break => { - Ok(Box::new(LoopControlNode::new(LoopControlType::Break))) - } + TokenType::Continue => Ok(Box::new(LoopControlNode::new(LoopControlType::Continue))), + TokenType::Break => Ok(Box::new(LoopControlNode::new(LoopControlType::Break))), _ => Err(io::Error::new( io::ErrorKind::Other, format!( @@ -428,12 +431,11 @@ impl Parser { )); } }; - self.move_position(); - if self.get_current_token().is_ok() - && self.get_current_token()?.is_type(TokenType::Comma) - { - self.move_position(); + if !self.move_if_next_token_is(vec![TokenType::Comma]) { + break; } + self.move_position(); + continue; } Ok(arguments) } diff --git a/src/server/client_session.rs b/src/server/client_session.rs new file mode 100644 index 0000000..196c612 --- /dev/null +++ b/src/server/client_session.rs @@ -0,0 +1,40 @@ +use std::time::Duration; + +use chrono::{DateTime, Local}; +use uuid::Uuid; + +// A client connection works like a session +pub struct ClientSession { + pub id: Uuid, + pub _creation_time: DateTime, + pub expiration_time: Option>, +} + +impl ClientSession { + pub fn new(creation_time: DateTime) -> Self { + Self { + id: Uuid::new_v4(), + _creation_time: creation_time, + expiration_time: None, + } + } + + pub fn is_active(&self) -> bool { + if self.expiration_time.is_none() { + return true; + } + Local::now() < self.expiration_time.unwrap() + } + + pub fn activate_indefinitely(&mut self) { + self.expiration_time = None + } + + pub fn desactivate(&mut self) { + self.expiration_time = Some(Local::now()) + } + + pub fn activate_temporairly(&mut self, lifetime: Duration) { + self.expiration_time = Some(Local::now() + lifetime) + } +} diff --git a/src/server/config.rs b/src/server/config.rs deleted file mode 100644 index 2517488..0000000 --- a/src/server/config.rs +++ /dev/null @@ -1,81 +0,0 @@ -use dotenvy::dotenv; -use regex::Regex; -use std::{env, ffi::OsStr, path::Path}; - -pub struct Config { - pub host: String, - pub port: String, - pub manager_file: String, - pub password: String, -} - -impl Config { - fn value(user_key: String) -> Option { - dotenv().expect(".env file not found"); - - let user_key = Regex::new(r"env\.(\w+)") - .unwrap() - .captures(&user_key)? - .get(1)? - .as_str(); - - env::vars() - .filter(|(key, _)| *key == user_key) - .map(|(_, value)| value) - .next() - } - - pub fn blz_exists(path: &String) -> bool { - let blz_file = Path::new(&path); - - if !blz_file.exists() || blz_file.extension().unwrap_or(OsStr::new("")) != "blz" { - return false; - } - true - } - - pub fn parse_arguments(args: Vec) -> Option { - let default = Self::default(); - let mut host = default.host; - let mut port = default.port; - let mut manager_file = default.manager_file; - let mut password = default.password; - - for arg in 0..args.len() { - let str = &args[arg]; - if str.chars().nth(0)? != '-' { - continue; - } - - let arg = args.get(arg + 1)?; - let value = if let Some(parse_value) = Config::value(arg.to_string()) { - parse_value - } else { - arg.to_string() - }; - - match str.as_str() { - "-host" => host.clone_from(&value), - "-port" => port.clone_from(&value), - "-blz_file" => manager_file.clone_from(&value), - "-password" => password.clone_from(&value), - _ => (), - } - } - Some(Config { - host, - port, - manager_file, - password, - }) - } - - fn default() -> Self { - Config { - host: "localhost".to_string(), - port: "3306".to_string(), - manager_file: "./db/datablaze/manage.blz".to_string(), - password: "password".to_string(), - } - } -} diff --git a/src/server/headers.rs b/src/server/header_parsing.rs similarity index 100% rename from src/server/headers.rs rename to src/server/header_parsing.rs diff --git a/src/server/instance.rs b/src/server/instance.rs new file mode 100644 index 0000000..f10fc15 --- /dev/null +++ b/src/server/instance.rs @@ -0,0 +1,170 @@ +use chrono::Local; +use colored::Colorize; +use ctrlc; + +use super::{client_session::ClientSession, header_parsing, runtime_config::RuntimeConfig}; +use crate::prelude::*; +use crate::routine::{formatting::MessagesFormatting, info_channel::InfoChannel}; +use std::{ + io::{self, Read, Result, Write}, + net::TcpListener, + path::Path, + process::exit, + thread, + time::Duration, +}; + +pub struct ServerInstance { + info_channel: InfoChannel, + sessions: Vec, + config: RuntimeConfig, +} + +impl ServerInstance { + pub fn new(info_channel: InfoChannel) -> Self { + Self { + info_channel, + sessions: vec![], + config: RuntimeConfig::default(), + } + } + + pub async fn launch(&mut self, args: &[String]) -> Result<()> { + self.config = RuntimeConfig::parse_arguments(args.to_vec()).unwrap(); + let error_marking = "Server Error".bright_red(); + if !RuntimeConfig::blz_exists(&self.config.manage_file) { + let error_message = format!( + "{}: {} not found; couldn't raise a server{}", + error_marking, + &self.config.manage_file, + if args.is_empty() && self.config.manage_file == "main.manage.blz" { + "\n".to_owned() + + &String::from("Try to specify a path to the management file").into_hint() + } else { + "".to_string() + }, + ); + return Err(io::Error::new(ErrorKind::NotFound, error_message)); + } + let listener = match TcpListener::bind(&self.config.address) { + Ok(success) => { + self.info_channel + .clone() + .send(format!( + "Gateway opened on {}", + self.config.address.yellow() + )) + .unwrap(); + success + } + Err(error) => { + return Err(io::Error::new( + error.kind(), + format!( + "{}: {}; could not start", + error_marking, + match error.kind() { + ErrorKind::AddrInUse => { + "The address is already taken".to_string() + } + ErrorKind::PermissionDenied => { + "An attempt to bind the port was refused".to_string() + } + ErrorKind::InvalidInput => { + format!("{}: Invalid socket address", error_marking) + } + _ => { + format!("{}: {}", error_marking, error) + } + } + ), + )); + } + }; + + let info_channel = self.info_channel.clone(); + + info_channel + .clone() + .send(format!( + "Executing {}", + Path::new(&self.config.manage_file) + .file_name() + .unwrap() + .to_string_lossy() + .yellow() + )) + .unwrap(); + + ctrlc::set_handler(move || { + info_channel + .clone() + .send("\nInterrupted with ^C, shutdown".to_string()) + .unwrap(); + exit(0); + }) + .expect("Error occured while setting ctrl+c handler"); + + self.start_closed_sessions_cleaner().await; + self.handle_connections(listener).await?; + Ok(()) + } + + async fn start_closed_sessions_cleaner(&mut self) -> tokio::task::JoinHandle<()> { + unsafe { + let this = &mut *(self as *mut Self); + tokio::spawn(async move { + loop { + thread::sleep(Duration::from_secs(10)); + this.sessions + .retain(|session: &ClientSession| session.is_active()); + } + }) + } + } + + async fn handle_connections(&mut self, listener: TcpListener) -> Result<()> { + for stream in listener.incoming() { + let mut stream = stream.unwrap(); + let correct_password = &self.config.password; + + let mut buffer = [0; 1024]; + let mut request = String::new(); + + let bytesize = stream.read(&mut buffer).unwrap(); + request.push_str(&String::from_utf8_lossy(&buffer[..bytesize])); + + let header = header_parsing::parse_header(request.clone()).unwrap(); + + let client_password = header.get("password"); + if client_password.is_none() || correct_password != client_password.unwrap() { + stream.write_all(b"HTTP/1.1 401 Unauthorized - Incorrect password\r\n\n")?; + continue; + } else if self.sessions.len() + 1 > self.config.sessions_limit as usize { + stream.write_all(b"HTTP/1.1 503 Sessions Limit\r\n\n")?; + } + let mut new_session = ClientSession::new(Local::now()); + new_session.activate_temporairly(self.config.session_lifetime); + let session_id = new_session.id; + self.sessions.push(new_session); + let response_content = format!("{}", session_id); + + self.info_channel + .clone() + .send("A new session been created".to_string()) + .unwrap(); + + stream + .write_all( + format!( + "HTTP/1.1 200 Ok\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\n{}", + response_content.len(), + response_content + ) + .as_bytes(), + ) + .unwrap(); + } + Ok(()) + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 910fab1..bd33a96 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,3 +1,4 @@ -pub mod config; -pub mod headers; -pub mod server_bz; +pub mod client_session; +pub mod header_parsing; +pub mod instance; +pub mod runtime_config; diff --git a/src/server/runtime_config.rs b/src/server/runtime_config.rs new file mode 100644 index 0000000..2565b4e --- /dev/null +++ b/src/server/runtime_config.rs @@ -0,0 +1,126 @@ +use colored::Colorize; +use dotenvy::dotenv; +use regex::Regex; +use std::{ + env, + ffi::OsStr, + io::{self, Result}, + path::Path, + time::Duration, +}; + +pub struct RuntimeConfig { + pub address: String, + pub manage_file: String, + pub password: String, + pub sessions_limit: u32, + pub session_lifetime: Duration, +} + +impl Default for RuntimeConfig { + fn default() -> Self { + Self { + address: "localhost:3306".to_string(), + manage_file: "./main.manage.blz".to_string(), + password: "password".to_string(), + sessions_limit: 100, + session_lifetime: Duration::from_secs(30), + } + } +} + +impl RuntimeConfig { + fn get_env_value(user_key: String) -> Option { + dotenv().expect("Environment file couldn't be found"); + + let user_key = Regex::new(r"env\.(\w+)") + .unwrap() + .captures(&user_key)? + .get(1)? + .as_str(); + + env::vars() + .filter(|(key, _)| *key == user_key) + .map(|(_, value)| value) + .next() + } + + pub fn blz_exists(path: &String) -> bool { + let blz_file = Path::new(&path); + + if !blz_file.exists() || blz_file.extension().unwrap_or(OsStr::new("")) != "blz" { + return false; + } + true + } + + pub fn parse_arguments(console_args: Vec) -> Result { + let default = Self::default(); + let mut address = default.address; + let mut password = default.password; + let mut manage_file: String = "main.manage.blz".to_string(); + let mut sessions_limit: u32 = default.sessions_limit; + let mut session_lifetime: Duration = default.session_lifetime; + + let manage_file_fullname_regex = Regex::new(r".*\.manage\.blz$").unwrap(); + + for console_arg_index in 0..console_args.len() { + let argument_keyword = &console_args[console_arg_index]; + if argument_keyword.chars().nth(0).unwrap() != '-' { + if console_arg_index == 0 { + manage_file = if !manage_file_fullname_regex.is_match(argument_keyword) { + format!("{}.manage.blz", argument_keyword) + } else { + argument_keyword.to_string() + }; + }; + continue; + } + + let argument_value = match console_args.get(console_arg_index + 1) { + Some(value) => { + if let Some(env_value) = + RuntimeConfig::get_env_value(argument_keyword.to_string()) + { + env_value + } else { + value.to_string() + } + } + None => { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!( + "{}: The value for '{}' argument isn't provided", + "Input Error".bright_red(), + &argument_keyword[1..argument_keyword.len()] + ), + )); + } + }; + + match argument_keyword.as_str() { + "-address" => address.clone_from(&argument_value), + "-password" => password.clone_from(&argument_value), + "-session_lifetime" => session_lifetime.clone_from(&Duration::from_secs( + argument_value + .parse() + .expect("Provide the right amount of seconds a session lives"), + )), + "-sessions_limit" => sessions_limit.clone_from( + &argument_value + .parse() + .expect("Unable to parse 'sessions_limit' field due to the incorrect type"), + ), + _ => (), + } + } + Ok(RuntimeConfig { + address, + manage_file, + password, + sessions_limit, + session_lifetime, + }) + } +} diff --git a/src/server/server_bz.rs b/src/server/server_bz.rs deleted file mode 100644 index b5bdb9b..0000000 --- a/src/server/server_bz.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::{ - server::{config::Config, headers}, - shell::handling::analyze_syntatically, -}; -use std::io::{self, Read}; -use std::net::{TcpListener, TcpStream}; - -pub fn server_run(args: Vec) -> io::Result<()> { - let config = Config::parse_arguments(args).unwrap(); - - if !Config::blz_exists(&config.manager_file) { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "blz_file not found", - )); - } - - let host = format!("{}:{}", config.host, config.port); - let listener = TcpListener::bind(host)?; - - for stream in listener.incoming() { - let stream = stream?; - let password = std::mem::take(&mut config.password.clone()); - - std::thread::spawn(move || handle_connection(stream, password)); - } - - Ok(()) -} - -fn handle_connection(mut stream: TcpStream, password: String) -> io::Result<()> { - let mut buffer = [0; 1024]; - let mut request = String::new(); - let bytes_read = stream.read(&mut buffer)?; - request.push_str(&String::from_utf8_lossy(&buffer[..bytes_read])); - - let header = headers::parse_header(request.clone()).unwrap(); - if let Some(value) = header.get("Password") { - if password == *value { - analyze_syntatically(headers::remove_empty_line(request).unwrap())? - }; - }; - - Ok(()) -} diff --git a/src/shell/command_handler.rs b/src/shell/command_handler.rs new file mode 100644 index 0000000..6b8790c --- /dev/null +++ b/src/shell/command_handler.rs @@ -0,0 +1,158 @@ +use crate::routine::formatting::MessagesFormatting; +use crate::routine::info_channel::{get_console_info_channel, InfoChannel}; +use crate::scripting::{ast::tokens::Token, lexer, parser}; +use crate::server::instance::ServerInstance; +use colored::Colorize; +use std::fs::{self, File}; +use std::io::{self, Result, Write}; + +pub const OFFICIAL_REPOSITORY: &str = "https://github.com/Reeliks/blaze"; + +pub struct ShellCommandHandler { + info_channel: InfoChannel, +} + +impl Default for ShellCommandHandler { + fn default() -> Self { + Self { + info_channel: get_console_info_channel(), + } + } +} + +impl ShellCommandHandler { + pub fn new() -> Self { + Self::default() + } + + pub async fn handle_command(&mut self) -> Result<()> { + let mut args: Vec = std::env::args().collect::>()[1..].to_vec(); + if args.is_empty() { + args.push("help".to_string()); + }; + let command = args[0].as_str(); + match command { + "init" => { + let mut manage_file_name = "main"; + if let Some(first_arg) = args.get(1) { + if !first_arg.starts_with('-') { + manage_file_name = first_arg + } + } + + if Self::create_management_file(manage_file_name) { + println!("Thanks for using {}!", "Blaze Database".yellow()); + println!(" * To contribute the development process, check out the official repository:\n {}", OFFICIAL_REPOSITORY); + } else { + println!( + "{}: The file/folder with a name '{}' already exists\n{}", + "Init Error".bright_red(), + manage_file_name, + String::from("Try specifying another name").into_hint() + ); + } + } + "run" | "raise" => self.run_server(args).await?, + "lexer" => { + let text = Self::read_console_input()?; + self.run_lexer(text, true)?; + } + "parser" => { + let text = Self::read_console_input()?; + self.run_parser(text)?; + } + "--help" | "help" => Self::print_help_section(), + _ => { + eprintln!("'{}' is not a blaze commmand. See 'blaze --help'", command); + std::process::exit(1); + } + } + Ok(()) + } + + async fn run_server(&mut self, args: Vec) -> Result<()> { + if let Err(error) = ServerInstance::new(self.info_channel.clone()) + .launch(&args[1..]) + .await + { + eprintln!("{}", error); + }; + Ok(()) + } + + // Do not indent raw strings below" + fn print_help_section() { + let help_list = r#">> Blaze Database 0.0.1a - available commands: +Datablaze Management + init - create a new datablaze template to start working blazingly fast + run - raise a datablaze configurated in manage.blz | raise + * manage_file_name, -cons_limit, con_lifetime (secs), -address, -password + +Blaze Language (Dev) + lexer - tokens parsing + parser - nodes parsing (lexing included) +"#; + + println!("{}", help_list); + } + + pub fn create_management_file(name: &str) -> bool { + let manage_file_content = br#"manage ( + // address = "localhost:3306", + // sessions_limit = 100, + // session_lifetime = 60 +); + +attach "data";"#; + + let manage_file_fullname = &format!( + "{}.manage.blz", + if !name.is_empty() { name } else { "main" } + ); + if fs::metadata(manage_file_fullname).is_err() { + let mut manage_file = File::create(manage_file_fullname).unwrap(); + manage_file.write_all(manage_file_content).unwrap(); + return true; + }; + false + } + + pub fn run_lexer(&self, code_to_parse: String, show_final_message: bool) -> Result> { + let mut code_lexer = lexer::Lexer::new(code_to_parse, self.info_channel.clone()); + code_lexer + .get_context() + .set_code_source("Shell".to_string()); + let tokens = code_lexer.analyze()?; + + if !tokens.is_empty() && show_final_message { + self.info_channel.clone().send(format!( + "Lexiical Analysis successfully completed! Tokens count: {}", + tokens.len() + ))?; + } + Ok(tokens) + } + + pub fn run_parser(&self, code: String) -> Result<()> { + let tokens = self.run_lexer(code, false)?; + let mut code_parser = parser::Parser::new(tokens, self.info_channel.clone()); + code_parser + .get_context() + .set_code_source("Shell".to_string()); + let nodes = code_parser.parse()?.nodes; + if !nodes.is_empty() { + self.info_channel.clone().send(format!( + "Parsing successfully completed! Nodes Count: {}", + nodes.len() + ))?; + } + + Ok(()) + } + + fn read_console_input() -> io::Result { + let mut text = String::new(); + std::io::stdin().read_line(&mut text)?; + Ok(text.trim().to_string()) + } +} diff --git a/src/shell/handling.rs b/src/shell/handling.rs deleted file mode 100644 index d7ea8a7..0000000 --- a/src/shell/handling.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::db::create_db; -use crate::fs::filesystem::Fs; -use crate::scripting::tokens::Token; -use crate::scripting::{lexer, parser}; -use crate::server::server_bz; -use serde::{Deserialize, Serialize}; -use std::io::{self, Result}; - -#[derive(Debug, Serialize, Deserialize)] -struct Person { - name: String, - age: i32, - email: Option, -} - -pub fn handle_command_arguments() -> Result<()> { - let args: Vec = std::env::args().collect(); - if args.len() < 2 { - print_help_section(); - return Ok(()); - }; - match args[1].as_str() { - "create" => create_db_with_console()?, - "run" => server_bz::server_run(args)?, - "test" => { - let person = Person { - name: "SVD".to_string(), - age: 30, - email: Some("SVD@example.com".to_string()), - }; - - let bson_doc = bson::to_document(&person).unwrap(); - - Fs::db_open("./test.bson".to_string()) - .unwrap() - .db_write("Usser".to_string(), bson_doc) - .unwrap(); - } - "lexer" => { - let text = input_text()?; - analyze_lexically(text)?; - } - "parser" => { - let text = input_text()?; - analyze_syntatically(text)?; - } - _ => { - eprintln!("Invalid arguments"); - std::process::exit(1); - } - } - Ok(()) -} - -fn print_help_section() { - let help_list = r#"Blaze Db 0.0.1a - available commands: - Database management - create - create a new datablaze - Blaze Language - lexer - get to see how the code is subjected to lexical analysis under the hood - parser - try the first version of a parser - run - start server"#; - - println!("{}", help_list); -} - -pub fn create_db_with_console() -> Result<()> { - let mut path = String::new(); - println!("Specify a path to a datablaze"); - io::stdin().read_line(&mut path)?; - create_db::create_db_structure(path.trim())?; - - Ok(()) -} - -fn analyze_lexically(code_to_parse: String) -> Result> { - let mut code_lexer = lexer::Lexer::new(code_to_parse); - code_lexer - .get_context() - .set_code_source("Shell".to_string()); - Ok(code_lexer.analyze().unwrap()) -} - -pub fn analyze_syntatically(code: String) -> Result<()> { - let tokens = analyze_lexically(code)?; - let mut code_parser = parser::Parser::new(tokens); - code_parser - .get_context() - .set_code_source("Shell".to_string()); - let nodes = code_parser.parse()?.nodes; - if !nodes.is_empty() { - println!( - "Parsing successfully completed! Nodes Count: {}", - nodes.len() - ); - } - Ok(()) -} - -fn input_text() -> io::Result { - let mut code_to_parse = String::new(); - std::io::stdin().read_line(&mut code_to_parse)?; - Ok(code_to_parse) -} diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 17af089..d86bd4d 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1 +1 @@ -pub mod handling; +pub mod command_handler; diff --git a/test.bson b/test.bson index 057128e..c796f81 100644 Binary files a/test.bson and b/test.bson differ diff --git a/tests/test.rs b/tests/test.rs index b510a2c..fe75d4c 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,8 +1,8 @@ -use blaze::db::create_db; -use blaze::scripting::lexer::Lexer; -use blaze::scripting::parser::Parser; -use blaze::scripting::tokens::TokenType; -use blaze::server::headers; +use blaze::{ + routine::info_channel::get_console_info_channel, + scripting::{ast::tokens::TokenType, lexer::Lexer, parser::Parser}, + server::header_parsing::parse_header, +}; #[test] fn test_lexer() { @@ -14,7 +14,7 @@ fn test_lexer() { TokenType::Else, ]; - let code_lexer = Lexer::new(code_to_parse); + let code_lexer = Lexer::new(code_to_parse, get_console_info_channel()); let tokens = code_lexer.analyze().unwrap(); let actual_token_types: Vec = tokens @@ -26,12 +26,12 @@ fn test_lexer() { } fn parser(code: &str) -> std::io::Result { - let mut code_lexer = Lexer::new(code.to_string()); + let mut code_lexer = Lexer::new(code.to_string(), get_console_info_channel()); let code_source = String::from("Tests"); code_lexer.get_context().code_source = code_source.clone(); let tokens = code_lexer.analyze()?; - let mut code_parser = Parser::new(tokens.clone()); + let mut code_parser = Parser::new(tokens.clone(), get_console_info_channel()); code_parser.get_context().code_source = code_source; let ast = code_parser.parse(); @@ -53,18 +53,12 @@ fn test_parser() { .unwrap()); } -#[test] -fn test_cteate_db() { - let is_create = create_db::create_db_structure("./db".trim()).is_ok(); - assert!(is_create); -} - #[test] fn test_header_parser() { let response = "POST / HTTP/1.1\nHost: localhost:3300\nUser-Agent: curl/8.7.1\nAccept: */*\nPassword: 1221\n" .to_string(); - let hashmap = headers::parse_header(response).unwrap(); + let hashmap = parse_header(response).unwrap(); if let Some(value) = hashmap.get("Password") { assert!(value == "1221"); }