From ecbfce64ed74e5c077077f752aff347da31e1598 Mon Sep 17 00:00:00 2001 From: Caznix Date: Sun, 1 Dec 2024 16:02:06 -0500 Subject: [PATCH] Basic repl --- .cargo/config.toml | 2 + .gitattributes | 1 + .github/workflows/rust.yml | 29 +++++ .gitignore | 4 + LICENSE.md | 21 +++ README.md | 0 cargo.toml | 7 + editor/Cargo.toml | 7 + editor/src/main.rs | 18 +++ engine/.gitignore | 1 + engine/Cargo.toml | 16 +++ engine/src/core/commands.rs | 36 ++++++ engine/src/core/mod.rs | 3 + engine/src/core/repl.rs | 248 ++++++++++++++++++++++++++++++++++++ engine/src/core/splash.rs | 26 ++++ engine/src/main.rs | 21 +++ xtask/Cargo.toml | 32 +++++ xtask/src/editor.rs | 1 + xtask/src/engine.rs | 22 ++++ xtask/src/main.rs | 70 ++++++++++ 20 files changed, 565 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitattributes create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 cargo.toml create mode 100644 editor/Cargo.toml create mode 100644 editor/src/main.rs create mode 100644 engine/.gitignore create mode 100644 engine/Cargo.toml create mode 100644 engine/src/core/commands.rs create mode 100644 engine/src/core/mod.rs create mode 100644 engine/src/core/repl.rs create mode 100644 engine/src/core/splash.rs create mode 100644 engine/src/main.rs create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/editor.rs create mode 100644 xtask/src/engine.rs create mode 100644 xtask/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..0c612da --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --quiet --package xtask --" \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c690e03 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.obj filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..d4c3ccb --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,29 @@ +name: Rust + +on: + push: + branches: [ "main", "master" ] + pull_request: + branches: [ "main", "master" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Build + run: cargo build --verbose + + - name: Run tests + run: cargo test --verbose + + - name: Check formatting + run: cargo fmt -- --check + + - name: Clippy + run: cargo clippy -- -D warnings \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..583276e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +.idea +Cargo.lock +*.log \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c918216 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +## Copyright (c) 2024 Caznix + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.** diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/cargo.toml b/cargo.toml new file mode 100644 index 0000000..5f81e11 --- /dev/null +++ b/cargo.toml @@ -0,0 +1,7 @@ +[workspace] +resolver = "2" +members = [ + "engine", + "editor", + "xtask" +] \ No newline at end of file diff --git a/editor/Cargo.toml b/editor/Cargo.toml new file mode 100644 index 0000000..800cdbe --- /dev/null +++ b/editor/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "editor" +version = "0.1.0" +edition = "2021" + +[dependencies] +floem = "0.2.0" diff --git a/editor/src/main.rs b/editor/src/main.rs new file mode 100644 index 0000000..e1a1530 --- /dev/null +++ b/editor/src/main.rs @@ -0,0 +1,18 @@ +use floem::{prelude::*, style::Style}; + +fn main() { + floem::launch(counter_view); +} + +fn counter_view() -> impl IntoView { + let mut counter = RwSignal::new(20); + v_stack({ + label("test") + }).style(Style::d); + h_stack(( + button("Increment").action(move || counter += 1), + label(move || format!("Value: {counter}")), + button("Decrement").action(move || counter -= 1), + )) + .style(|s| s.size_full().items_center().justify_center().gap(10)) +} \ No newline at end of file diff --git a/engine/.gitignore b/engine/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/engine/.gitignore @@ -0,0 +1 @@ +/target diff --git a/engine/Cargo.toml b/engine/Cargo.toml new file mode 100644 index 0000000..b5bc16e --- /dev/null +++ b/engine/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "zenyx" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = "0.4.38" +colored = "2.1.0" +lazy_static = "1.5.0" +#log = "0.4.22" +log2 = "0.1.14" +parking_lot = "0.12.3" +reedline = "0.37.0" +regex = "1.11.1" +thiserror = "2.0.3" +tokio = { version = "1.41.1", features = ["macros", "rt", "rt-multi-thread"] } diff --git a/engine/src/core/commands.rs b/engine/src/core/commands.rs new file mode 100644 index 0000000..45884ac --- /dev/null +++ b/engine/src/core/commands.rs @@ -0,0 +1,36 @@ +use std::process::Command; + +use log2::{debug, info}; + +use crate::core::repl::COMMAND_LIST; + +pub fn say_hello() { + println!("Hello from your new command!"); +} + +pub fn echo(args: Vec) { + debug!("{}", args.join(" ")); + println!("{}", args.join(" ")) +} + +pub fn exit() { + debug!("Exiting..."); + std::process::exit(0) +} +pub fn clear() { + info!("Clearing screen..., running command"); + let _result = if cfg!(target_os = "windows") { + debug!("target_os is windows"); + Command::new("cmd").args(["/c", "cls"]).spawn() + } else { + debug!("target_os was unix"); + // "clear" or "tput reset" + Command::new("tput").arg("reset").spawn() + }; +} +pub fn cmds() { + println!("Commands:"); + for cmd in COMMAND_LIST.commands.read().iter() { + println!("{:#}", cmd); + } +} diff --git a/engine/src/core/mod.rs b/engine/src/core/mod.rs new file mode 100644 index 0000000..7c5620e --- /dev/null +++ b/engine/src/core/mod.rs @@ -0,0 +1,3 @@ +pub mod commands; +pub mod repl; +pub mod splash; diff --git a/engine/src/core/repl.rs b/engine/src/core/repl.rs new file mode 100644 index 0000000..899c606 --- /dev/null +++ b/engine/src/core/repl.rs @@ -0,0 +1,248 @@ +use super::commands; +use chrono::Local; +use lazy_static::lazy_static; +use log2::{debug, error, info}; +use parking_lot::RwLock; +use reedline::{Prompt, Reedline, Signal}; +use regex::Regex; +use std::{borrow::Borrow, collections::HashMap, sync::Arc}; + +struct ZPrompt { + left_text: String, + right_text: String, +} + +#[derive(Clone, Debug)] +enum Callable { + Simple(fn()), + WithArgs(fn(Vec)), +} +#[derive(Debug)] +pub struct Command { + pub name: &'static str, + pub description: Option<&'static str>, + function: Callable, + pub arg_count: u8, +} + +impl Command { + pub fn execute(&self, args: Option>) { + //debug!("Executing command: {}", self.name); + match &self.function { + Callable::Simple(f) => { + if let Some(args) = args { + error!( + "Command expected 0 arguments but {} args were given. Ignoring..", + args.len() + ); + } + f() + } + Callable::WithArgs(f) => match args { + Some(args) => f(args), + None => error!("Command expected arguments but received 0"), + }, + } + } +} + +impl std::fmt::Display for Command { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Name: {}\n\t{}", + self.name, + self.description.unwrap_or("No description") + ) + } +} + +lazy_static! { + pub static ref COMMAND_LIST: Arc = Arc::new(CommandList::new()); +} + +pub struct CommandList { + pub commands: RwLock>, + pub aliases: RwLock>, +} + +impl CommandList { + fn new() -> Self { + CommandList { + commands: RwLock::new(Vec::new()), + aliases: RwLock::new(HashMap::new()), + } + } + + fn add_command( + &self, + name: &'static str, + description: Option<&'static str>, + func: Callable, + arg_count: Option, + ) { + debug!("Adding command: {}", name); + let mut commands = self.commands.write(); + + commands.push(Command { + name, + description, + function: func, + arg_count: arg_count.unwrap_or(0), + }); + } + fn add_alias(&self, name: String, alias: String) { + //println!("Input alias: {}", alias); + if self.aliases.read().contains_key(&alias) { + error!("Alias: '{}' already exists", alias); + return; + } + let mut commands = self.commands.write(); + if let Some(command) = commands.iter_mut().find(|cmd| cmd.name == name) { + info!("Adding alias: {} for cmd: {}", alias, command.name); + self.aliases + .write() + .insert(alias.to_string(), name.to_string()); + } else { + error!("Command: '{}' was not found", name); + } + } + + fn execute_command(&self, mut name: String, args: Option>) { + //info!("received input command: {}", name); + let commands = self.commands.borrow(); + if self.aliases.read().contains_key(&name) { + name = self + .aliases + .read() + .get_key_value(&name) + .unwrap() + .1 + .to_string(); + debug!("changed to {}", name); + } + if let Some(command) = commands.read().iter().find(|cmd| cmd.name == name) { match (command.arg_count, args.as_ref()) { + (expected, Some(args_vec)) if args_vec.len() != expected as usize => { + eprintln!( + "Command: '{}' expected {} arguments but received {}", + name, + expected, + args_vec.len() + ); + } + (_, _) => command.execute(args), + } } + } +} + +impl Prompt for ZPrompt { + fn render_prompt_left(&self) -> std::borrow::Cow { + std::borrow::Cow::Borrowed(&self.left_text) + } + + fn render_prompt_right(&self) -> std::borrow::Cow { + std::borrow::Cow::Borrowed(&self.right_text) + } + + fn render_prompt_history_search_indicator( + &self, + _history_search: reedline::PromptHistorySearch, + ) -> std::borrow::Cow { + std::borrow::Cow::Borrowed("") + } + + fn render_prompt_indicator( + &self, + prompt_mode: reedline::PromptEditMode, + ) -> std::borrow::Cow { + match prompt_mode { + reedline::PromptEditMode::Default => std::borrow::Cow::Borrowed(">>"), + reedline::PromptEditMode::Emacs => { + let timestamp = Local::now().format("[%H:%M:%S.%3f/SHELL] >>\t").to_string(); + std::borrow::Cow::Owned(timestamp) + } + reedline::PromptEditMode::Vi(_) => std::borrow::Cow::Borrowed("vi>>"), + reedline::PromptEditMode::Custom(_) => std::borrow::Cow::Borrowed("custom>>"), + } + } + + fn render_prompt_multiline_indicator(&self) -> std::borrow::Cow { + std::borrow::Cow::Borrowed("><") + } +} + +fn setup() { + COMMAND_LIST.add_command( + "hello", + Some("test"), + Callable::Simple(commands::say_hello), + None, + ); + COMMAND_LIST.add_command("exit", None, Callable::Simple(commands::exit), None); + COMMAND_LIST.add_command("clear", None, Callable::Simple(commands::clear), None); + COMMAND_LIST.add_command("echo", None, Callable::WithArgs(commands::echo), Some(1)); + COMMAND_LIST.add_command("cmds", None, Callable::Simple(commands::cmds), None); + COMMAND_LIST.add_alias("cmds".to_string(), "help".to_string()); + COMMAND_LIST.add_alias("cmds".to_string(), "cmd_list".to_string()); + COMMAND_LIST.add_alias("hello".to_string(), "exit".to_string()); + COMMAND_LIST.add_alias("clear".to_string(), "exit".to_string()); +} + +pub async fn handle_repl() { + let mut line_editor = Reedline::create(); + setup(); + + loop { + let sig = line_editor.read_line(&ZPrompt { + left_text: String::new(), + right_text: "<<".to_string(), + }); + + match sig { + Ok(Signal::Success(buffer)) => { + if buffer == "exit" { + std::process::exit(0); + } else { + evaluate_command(&buffer); + } + } + Ok(Signal::CtrlC) => { + println!("\nCONTROL+C RECEIVED, TERMINATING"); + std::process::exit(0); + } + err => { + eprintln!("Error: {:?}", err); + } + } + } +} + +fn evaluate_command(input: &str) { + if input.trim().is_empty() { + return; + } + + let pattern = Regex::new(r"[;|\n]").unwrap(); + let commands: Vec<&str> = pattern.split(input).collect(); + + for command in commands { + let command = command.trim(); + if command.is_empty() { + println!("Empty command, skipping."); + continue; + } + + let tokens: Vec<&str> = command.split_whitespace().collect(); + if tokens.is_empty() { + return; + } + + let cmd_name = tokens[0]; + let args: Vec = tokens[1..].iter().map(|&s| s.to_string()).collect(); + + COMMAND_LIST.execute_command( + cmd_name.to_string(), + if args.is_empty() { None } else { Some(args) }, + ); + } +} diff --git a/engine/src/core/splash.rs b/engine/src/core/splash.rs new file mode 100644 index 0000000..f35c870 --- /dev/null +++ b/engine/src/core/splash.rs @@ -0,0 +1,26 @@ +use colored::Colorize; + +pub fn print_splash() { + println!( + r#" + &&&&&&&&&&& + &&&&&&&&&&&&&&&&& + &&&&&&&&&&&&&&&&&&&&& + && &&&&&&&&& + && &&&&&&&&& +&&&&&&&&&&&& &&&&&&&&&&& +&&&&&&&&&&&&& &&&&&&&&&&&& +&&&&&&&&&&&&& &&&&&&&&&&&&& +&&&&&&&&&&&& &&&&&&&&&&&&& +&&&&&&&&&&& &&&&&&&&&&&& + &&&&&&&&& && + &&&&&&&&& && + &&&&&&&&&&&&&&&&&&&&& + &&&&&&&&&&&&&&&&& + &&&&&&&&&&& + + Version: {} + "#, + env!("CARGO_PKG_VERSION").yellow().italic().underline() + ); +} diff --git a/engine/src/main.rs b/engine/src/main.rs new file mode 100644 index 0000000..b94b1eb --- /dev/null +++ b/engine/src/main.rs @@ -0,0 +1,21 @@ +use std::io; + +use log2::info; + +pub mod core; + +#[tokio::main] +async fn main() -> Result<(), io::Error> { + let _log2 = log2::open("z.log").tee(true).level("trace").start(); + info!("Initalizing Engine"); + let shell_thread = tokio::task::spawn(async { + info!("Shell thread started"); + core::repl::handle_repl().await; + } +); + + core::splash::print_splash(); + info!("Engine Initalized"); + shell_thread.await?; + Ok(()) +} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..7e4ef1a --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + + +[dependencies] +clap = { version = "4.5.20", features = ["derive"] } + +[profile.dev] +rpath = false +panic = "abort" +lto = "off" +opt-level = 0 +debug = false +overflow-checks = false +incremental = true +codegen-units = 256 + +strip = "symbols" +debug-assertions = false + +[profile.dev.package."*"] +opt-level = 0 +debug = false +overflow-checks = false +incremental = true +codegen-units = 256 + +strip = "symbols" +debug-assertions = false + diff --git a/xtask/src/editor.rs b/xtask/src/editor.rs new file mode 100644 index 0000000..8cfa650 --- /dev/null +++ b/xtask/src/editor.rs @@ -0,0 +1 @@ +pub fn build_editor() {} \ No newline at end of file diff --git a/xtask/src/engine.rs b/xtask/src/engine.rs new file mode 100644 index 0000000..ecdf677 --- /dev/null +++ b/xtask/src/engine.rs @@ -0,0 +1,22 @@ +use std::process::Stdio; + + +pub fn build_engine() { + +} + +pub fn build_core() { + let threads = format!("-j{}",std::thread::available_parallelism().unwrap().get()); + let mut run = std::process::Command::new("cargo") + .arg("run") + + .arg(threads) + .arg("--bin") + .arg("zenyx") + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + run.wait().unwrap(); +} \ No newline at end of file diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..b146421 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,70 @@ + +use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; +pub mod engine; +pub mod editor; + +#[derive(Parser)] +#[command(version, about, long_about = None,disable_version_flag = true,disable_help_flag = true)] +struct Cli { + #[arg(short,long)] + release: bool, + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand)] +enum Commands { + Run { + #[arg()] + task: Task + }, + Config, + +} +#[derive(Clone,ValueEnum)] +enum Task { + Engine, // Builds both editor and core + Editor, // Builds editor only + Core, // Builds engine core only + Help, +} + + + +fn main() { + let cli = Cli::parse(); + + + + + if cli.release { + println!("Running in release mode") + } + + match &cli.command { + + None => { + Cli::command().print_help().map_err(|e| { + println!("Could not run Xtask: {e}"); + + }).unwrap(); + } + Some(Commands::Run { task }) => { + match task { + Task::Engine => engine::build_engine(), + Task::Editor => todo!("Editor is not being actively worked on"), + Task::Core => { + engine::build_core(); + }, + Task::Help => { + println!("The following options are avalible to run"); + todo!() + }, + } + } + Some(Commands::Config) => { + todo!() + } + } + +} \ No newline at end of file