diff --git a/lang/.gitignore b/lang/.gitignore index d01bd1a..4415353 100644 --- a/lang/.gitignore +++ b/lang/.gitignore @@ -18,4 +18,10 @@ Cargo.lock # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +# Modu +.modu/ + +# Other +tmp \ No newline at end of file diff --git a/lang/Cargo.toml b/lang/Cargo.toml index e1dc4b6..e068bf9 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -15,4 +15,8 @@ bat = "0.24.0" chrono = "0.4.39" logos = "0.15.0" rand = "0.8.5" +reqwest = { version = "0.12.11", features = ["blocking", "json"] } rouille = "3.6.2" +serde_json = "1.0.134" +toml = "0.8.19" +zip = "2.2.2" diff --git a/lang/examples/library/.gitignore b/lang/examples/library/.gitignore new file mode 100644 index 0000000..835fd9e --- /dev/null +++ b/lang/examples/library/.gitignore @@ -0,0 +1 @@ +.test \ No newline at end of file diff --git a/lang/examples/library/lib.modu b/lang/examples/library/lib.modu new file mode 100644 index 0000000..56d2b5d --- /dev/null +++ b/lang/examples/library/lib.modu @@ -0,0 +1,8 @@ +fn hello() { + print("Hello, world!") +} + +fn new_feature(a) { + print("yo this is 2.0.0!!!! new features (rea)") + return a; +} \ No newline at end of file diff --git a/lang/examples/library/project.toml b/lang/examples/library/project.toml new file mode 100644 index 0000000..b6841b2 --- /dev/null +++ b/lang/examples/library/project.toml @@ -0,0 +1,4 @@ +[package] +name = "test" +version = "2.0.7" +description = "A package im using to test the package system, this is not a package for units tests or shit" \ No newline at end of file diff --git a/lang/examples/library/readme.md b/lang/examples/library/readme.md new file mode 100644 index 0000000..bc475e9 --- /dev/null +++ b/lang/examples/library/readme.md @@ -0,0 +1,20 @@ +# cool package +> very rea + +this has cool featureas like +1. potatoes +2. cookies +3. cheese + +install with +```bash +modu install test +``` + +very cool right + +**yo** +*yo* +__yo__ +_yo_ +~~yo~~ diff --git a/lang/examples/library/tests/equals.modu b/lang/examples/library/tests/equals.modu new file mode 100644 index 0000000..5b11c9f --- /dev/null +++ b/lang/examples/library/tests/equals.modu @@ -0,0 +1,7 @@ +fn equals(a,b) { + if a == b { + return true; + } + + return false; +} \ No newline at end of file diff --git a/lang/examples/nesting.modu b/lang/examples/nesting.modu index 1bbfb08..1d51489 100644 --- a/lang/examples/nesting.modu +++ b/lang/examples/nesting.modu @@ -6,6 +6,14 @@ fn example(a) { print("HELLO, WORLD"); } } + + if a != 3 { + print("Goodbye, World"); + + if a - 1 - -1 == 3 { + print("GOODBYE, WORLD"); + } + } } fn test_nesting(a, b, c) { @@ -14,16 +22,32 @@ fn test_nesting(a, b, c) { if c == 3 { example(c); + + if a == 2 { + example(5); + } + + if a != 2 { + example(a); + } } } if a != b { print("a is not equal to b"); - if c == 3 { + if a == 1 { example(c); } } + + if a != c { + print("a is not equal to c"); + + if b == 3 { + example(b); + } + } } test_nesting(1, 1, 3) \ No newline at end of file diff --git a/lang/examples/using_packages/main.modu b/lang/examples/using_packages/main.modu new file mode 100644 index 0000000..47d5df1 --- /dev/null +++ b/lang/examples/using_packages/main.modu @@ -0,0 +1,6 @@ +import "test" as test +import "basic_loops" as loops + +print(test.new_feature("abc")) + +loops.loop(print, 0, 5); \ No newline at end of file diff --git a/lang/examples/using_packages/project.toml b/lang/examples/using_packages/project.toml new file mode 100644 index 0000000..b9822a6 --- /dev/null +++ b/lang/examples/using_packages/project.toml @@ -0,0 +1,7 @@ +[dependencies] +basic_loops = "1.0.1" +test = "2.0.7" + +[package] +name = "using_packages" +version = "1.0.0" diff --git a/lang/src/ast.rs b/lang/src/ast.rs index ae4a38e..4fae6c8 100644 --- a/lang/src/ast.rs +++ b/lang/src/ast.rs @@ -110,6 +110,8 @@ pub enum AST { Rparen, + RBracket, + Comma, Dot, diff --git a/lang/src/cli/init.rs b/lang/src/cli/init.rs new file mode 100644 index 0000000..b9644af --- /dev/null +++ b/lang/src/cli/init.rs @@ -0,0 +1,85 @@ +pub fn init() { + use std::io::Write; + + let mut package_name = String::new(); + let mut package_version = String::new(); + + let mut is_library = String::new(); + + let mut file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .append(false) + .open("project.toml") + .unwrap(); + + if file.metadata().unwrap().len() > 0 { + println!("Project already initialized"); + print!("Overwrite? (y/N) "); + std::io::stdout().flush().unwrap(); + + let mut overwrite = String::new(); + + std::io::stdin().read_line(&mut overwrite).unwrap(); + + if overwrite.trim() == "y" { + file.set_len(0).unwrap(); + } else { + println!("Aborted"); + return; + } + } + + print!("Enter package name: "); + std::io::stdout().flush().unwrap(); + std::io::stdin().read_line(&mut package_name).unwrap(); + + package_name = package_name.trim().to_string(); + + if package_name.is_empty() { + println!("Package name cannot be empty"); + return; + } + + if !package_name.chars().all(|c| c.is_alphanumeric() || c == '_') { + println!("Package name can only contain alphanumeric characters and underscores"); + return; + } + + print!("Enter package version: "); + std::io::stdout().flush().unwrap(); + std::io::stdin().read_line(&mut package_version).unwrap(); + + if package_version.trim().is_empty() { + println!("Package version cannot be empty"); + return; + } + + print!("Is this a library? (y/N) "); + std::io::stdout().flush().unwrap(); + std::io::stdin().read_line(&mut is_library).unwrap(); + + let package_name = package_name.trim(); + let package_version = package_version.trim(); + + file.write_all(format!("[package]\nname = \"{}\"\nversion = \"{}\"", package_name, package_version).as_bytes()).unwrap(); + + let mut file_name = "main.modu"; + + if is_library.trim() == "y" { + file_name = "lib.modu"; + } + + let mut main_file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .append(false) + .open(file_name) + .unwrap(); + + if is_library.trim() == "y" { + main_file.write_all(b"fn hello() {\n print(\"Hello, world!\")\n}").unwrap(); + } else { + main_file.write_all("print(\"Hello, world!\")".as_bytes()).unwrap(); + } +} \ No newline at end of file diff --git a/lang/src/cli/install.rs b/lang/src/cli/install.rs new file mode 100644 index 0000000..3de92fb --- /dev/null +++ b/lang/src/cli/install.rs @@ -0,0 +1,141 @@ +use std::io::{Read, Write}; + +use toml; + +fn install_package(name: &str, version: &str) -> Result { + let mut client = reqwest::blocking::Client::new(); + + let response = client.get(&format!("https://modu-packages.vercel.app/api/v1/packages/{}/{}?isDownload=true", name, version)).send().unwrap(); + + if response.status().as_u16() != 200 { + let text = response.text().unwrap(); + + println!("Error: {}", text); + + return Err(text); + } + + let package = response.json::().unwrap(); + + println!("Installing package {}@{}", name, package["version"].as_str().unwrap()); + println!("> {}", match package["description"].as_str() { + Some(description) => description, + None => "No description" + }); + + let zip = client.get(package["zipUrl"].as_str().unwrap()).send().unwrap().bytes().unwrap(); + + let mut archive = zip::ZipArchive::new(std::io::Cursor::new(zip)).unwrap(); + + if std::fs::exists(".modu/packages/".to_string() + name).unwrap() { + std::fs::remove_dir_all(".modu/packages/".to_string() + name).unwrap(); + } + + std::fs::create_dir_all(".modu/packages/".to_string() + name).unwrap(); + + for i in 0..archive.len() { + let mut file = archive.by_index(i).unwrap(); + let path = ".modu/packages/".to_string() + name + "/" + file.name(); + + if file.name().contains("/") || file.name().contains("\\") { + if file.name().contains("/") { + let mut path = path.split("/").collect::>(); + path.pop(); + + std::fs::create_dir_all(path.join("/")).unwrap(); + } else { + let mut path = path.split("\\").collect::>(); + path.pop(); + + std::fs::create_dir_all(path.join("\\")).unwrap(); + } + + #[cfg(windows)] + let path = (".modu/packages/".to_string() + name + "/" + file.name()).replace("/", "\\"); + + #[cfg(not(windows))] + let path = (".modu/packages/".to_string() + name + "/" + file.name()).replace("\\", "/"); + + let mut out = std::fs::File::create(path).unwrap(); + std::io::copy(&mut file, &mut out).unwrap(); + + } else { + #[cfg(windows)] + let path = path.replace("/", "\\"); + + #[cfg(not(windows))] + let path = path.replace("\\", "/"); + + let mut out = std::fs::File::create(path).unwrap(); + std::io::copy(&mut file, &mut out).unwrap(); + } + } + + Ok(package) +} + +pub fn install() { + let mut content = String::new(); + let file = std::fs::File::open("project.toml"); + + if file.is_err() { + println!("No project.toml found. Run `modu init` to create a new project"); + return; + } + + file.unwrap().read_to_string(&mut content).unwrap(); + + let args = std::env::args().collect::>(); + + + let toml = toml::from_str::(&content).unwrap(); + let mut toml = toml.as_table().unwrap().clone(); + + let dependencies = match toml.get_mut("dependencies") { + Some(dependencies) => dependencies.as_table_mut().unwrap(), + + None => { + toml.insert("dependencies".to_string(), toml::Value::Table(toml::value::Table::new())); + toml.get_mut("dependencies").unwrap().as_table_mut().unwrap() + } + }; + + if args.len() < 3 { + for (name, version) in dependencies.iter() { + let package = match install_package(name, version.as_str().unwrap()) { + Ok(package) => { + println!("Package {} installed\n", name); + package + }, + Err(_) => { + println!("Failed to install package {}", name); + return; + } + }; + } + + return; + } + + let name = &(args[2].clone().split("@").collect::>()[0].to_string()); + + let version = match args[2].clone().split("@").collect::>().len() { + 1 => "latest".to_string(), + _ => args[2].clone().split("@").collect::>()[1].to_string() + }; + + let package = match install_package(name, &version) { + Ok(package) => package, + Err(_) => { + println!("Failed to install package {}", name); + return; + } + }; + + dependencies.insert(name.clone(), toml::Value::String(package["version"].as_str().unwrap().to_string())); + + let mut file = std::fs::File::create("project.toml").unwrap(); + file.write_all(toml::to_string(&toml).unwrap().as_bytes()).unwrap(); + + println!("Package {} installed", name); +} \ No newline at end of file diff --git a/lang/src/cli/login.rs b/lang/src/cli/login.rs new file mode 100644 index 0000000..9310786 --- /dev/null +++ b/lang/src/cli/login.rs @@ -0,0 +1,67 @@ +use reqwest; +use std::{env, io::Write}; + + +pub fn login() { + let mut path = String::new(); + + if cfg!(windows) { + let home = env::var("USERPROFILE").unwrap(); + + path = format!("{}\\.modu\\token", home); + + std::fs::create_dir_all(format!("{}\\.modu", home)).unwrap(); + } else { + let home = env::var("HOME").unwrap(); + + path = format!("{}/.modu/token", home); + + std::fs::create_dir_all(format!("{}/.modu", home)).unwrap(); + } + + let mut token_file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path) + .unwrap(); + + if token_file.metadata().unwrap().len() > 0 { + use std::io::Write; + + println!("Already logged in"); + print!("Overwrite? (y/N) "); + std::io::stdout().flush().unwrap(); + + let mut overwrite = String::new(); + std::io::stdin().read_line(&mut overwrite).unwrap(); + + if overwrite.trim() != "y" { + println!("Aborted"); + return; + } + } + + println!("Paste the code from https://modu-packages.vercel.app/token"); + + let mut token = String::new(); + std::io::stdin().read_line(&mut token).unwrap(); + + let token = token.trim(); + + let client = reqwest::blocking::Client::new(); + let res = client.get("https://modu-packages.vercel.app/api/v1/code/verify") + .header("Authorization", token) + .send().unwrap(); + + if res.status().as_u16() != 200 { + println!("Invalid code"); + return; + } + + let user_id = res.text().unwrap(); + + println!("Authenticated as user with ID {}", user_id); + + token_file.write_all(token.as_bytes()).unwrap(); +} \ No newline at end of file diff --git a/lang/src/cli/mod.rs b/lang/src/cli/mod.rs index d193bba..5cdc13f 100644 --- a/lang/src/cli/mod.rs +++ b/lang/src/cli/mod.rs @@ -1,3 +1,8 @@ pub mod run; pub mod repl; -pub mod server; \ No newline at end of file +pub mod server; +pub mod login; +pub mod init; +pub mod publish; +pub mod install; +pub mod uninstall; \ No newline at end of file diff --git a/lang/src/cli/publish.rs b/lang/src/cli/publish.rs new file mode 100644 index 0000000..a0670e0 --- /dev/null +++ b/lang/src/cli/publish.rs @@ -0,0 +1,170 @@ +use std::io::{Read, Write}; + +use toml; +use zip; +use serde_json::json; + +static BLOCKLIST: [&str; 4] = [".git", ".gitignore", ".modu", ".github"]; + +fn read_dir(dir: &std::path::Path, archive: &mut zip::ZipWriter) { + for entry in std::fs::read_dir(dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + + let mut do_break = false; + + for item in BLOCKLIST.iter() { + if path.to_str().unwrap().replace("\\", "/") == format!("./{}", item) { + println!("Ignoring {}", path.to_str().unwrap()); + do_break = true; + } + } + + if do_break { + continue; + } + + let mut gitignore_content = String::new(); + + let gitignore: Result<_, _> = std::fs::File::open(".gitignore"); + + match gitignore { + Ok(mut file) => { + file.read_to_string(&mut gitignore_content).unwrap(); + + for line in gitignore_content.lines() { + if path.to_str().unwrap().replace("\\", "/") == format!("./{}", line) { + println!("Ignoring {}", path.to_str().unwrap()); + do_break = true; + } + } + }, + + Err(_) => {} + } + + if do_break { + continue; + } + + if path.is_dir() { + read_dir(&path, archive); + } else { + let name = path.strip_prefix(".").unwrap(); + + archive.start_file(name.to_str().unwrap(), zip::write::SimpleFileOptions::default()).unwrap(); + let mut file = std::fs::File::open(path).unwrap(); + let mut contents = String::new(); + + let r = file.read_to_string(&mut contents); + + match r { + Ok(_) => {}, + Err(_) => { + println!("Could not read file {}", entry.path().to_str().unwrap()); + } + } + + archive.write_all(contents.as_bytes()).unwrap(); + } + } +} + +pub fn publish() { + let mut file = std::fs::File::open("project.toml").unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + let toml: toml::Value = contents.parse().unwrap(); + let package = toml.get("package").unwrap(); + + let name = package.get("name").unwrap(); + let version = package.get("version").unwrap(); + let description = match package.get("description") { + Some(desc) => desc.as_str().unwrap(), + None => "" + }; + + println!("Publishing {} v{}", name, version); + + print!("Confirm action (y/N): "); + std::io::stdout().flush().unwrap(); + + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + + if input.trim() != "y" { + println!("Aborted"); + return; + } + + let lib_exists = std::path::Path::new("lib.modu").exists(); + + if !lib_exists { + println!("No lib.modu file found, primary package file must be named lib.modu"); + return; + } + + std::fs::create_dir_all(".modu").unwrap(); + + let mut archive = zip::ZipWriter::new(std::fs::File::create(".modu/package.zip").unwrap()); + read_dir(std::path::Path::new("."), &mut archive); + archive.finish().unwrap(); + + println!("[1/2] Package compressed"); + + let token: String; + + if cfg!(windows) { + let home = std::env::var("USERPROFILE").unwrap(); + let path = format!("{}\\.modu\\token", home); + let mut token_file = std::fs::File::open(path).unwrap(); + let mut token_contents = String::new(); + token_file.read_to_string(&mut token_contents).unwrap(); + token = token_contents; + } else { + let home = std::env::var("HOME").unwrap(); + let path = format!("{}/.modu/token", home); + let mut token_file = std::fs::File::open(path).unwrap(); + let mut token_contents = String::new(); + token_file.read_to_string(&mut token_contents).unwrap(); + token = token_contents; + } + + if token.len() == 0 { + println!("Not logged in, run modu login"); + return; + } + + let client = reqwest::blocking::Client::new(); + + let mut readme = String::new(); + + if std::path::Path::new("README.md").exists() { + let mut file = std::fs::File::open("README.md").unwrap(); + file.read_to_string(&mut readme).unwrap(); + } else if std::path::Path::new("readme.md").exists() { + let mut file = std::fs::File::open("readme.md").unwrap(); + file.read_to_string(&mut readme).unwrap(); + } + + let body = json!({ + "name": name, + "version": version, + "description": description, + "file": std::fs::read(".modu/package.zip").unwrap(), + "readme": readme + }); + + let res = client.post("https://modu-packages.vercel.app/api/v1/packages") + .header("Authorization", token) + .json(&body) + .send().unwrap(); + + if res.status().as_u16() != 200 { + let text = res.text().unwrap(); + + println!("Error: {}", text); + } else { + println!("[2/2] Package uploaded"); + } +} \ No newline at end of file diff --git a/lang/src/cli/uninstall.rs b/lang/src/cli/uninstall.rs new file mode 100644 index 0000000..404c3d3 --- /dev/null +++ b/lang/src/cli/uninstall.rs @@ -0,0 +1,19 @@ +pub fn uninstall() { + let args = std::env::args().collect::>(); + + if args.len() < 3 { + println!("Usage: modu uninstall "); + return; + } + + let name = &(args[2].clone().split("@").collect::>()[0].to_string()); + + if !std::fs::exists(".modu/packages/".to_string() + name).unwrap() { + println!("Package {} is not installed", name); + return; + } + + std::fs::remove_dir_all(".modu/packages/".to_string() + name).unwrap(); + + println!("Package {} uninstalled", name); +} \ No newline at end of file diff --git a/lang/src/eval.rs b/lang/src/eval.rs index 5dadab0..42505cf 100644 --- a/lang/src/eval.rs +++ b/lang/src/eval.rs @@ -23,17 +23,25 @@ pub fn eval(expr: AST, context: &mut HashMap) -> Result 100 { return Err("Maximum recursion depth exceeded".to_string()); } depth += 1; - eval(expr.clone(), &mut new_context)?; + match eval(expr.clone(), &mut new_context) { + Ok(AST::Null) => { + continue; + } + + Ok(v) => { + return Ok(v); + } + + Err(e) => { + return Err(e); + } + } } } else { return Err(format!("{} takes {} argument(s)", name, f_args.len())); @@ -166,13 +174,39 @@ pub fn eval(expr: AST, context: &mut HashMap) -> Result { + let insert_as = as_.unwrap(); + + if insert_as == "*" { + for (name, value) in new_context { + context.insert(name, value); + } + + return Ok(AST::Null); + } else { + context.insert(insert_as, AST::Object { properties: new_context, line }); + } + } + + _ => { + return Err(format!("Failed to parse package {}", file)); + } + } + } else { + return Err(format!("Package {} not found", file)); + } } } } AST::PropertyCall { object, property, args, line: _ } => { - match object { + match object.clone() { Some(name) => { match context.get(&name) { Some(value) => { @@ -189,6 +223,12 @@ pub fn eval(expr: AST, context: &mut HashMap) -> Result) -> Result { if b { for expr in body { - eval(expr, context)?; + match eval(expr, context) { + Ok(AST::Null) => { + continue; + } + + Ok(v) => { + return Ok(v); + }, + + Err(e) => { + return Err(e); + } + } } } } @@ -439,6 +491,10 @@ pub fn eval(expr: AST, context: &mut HashMap) -> Result { + return Ok(*value); + } + _ => { return Err(format!("Unknown expression, got {:?}", expr)); } diff --git a/lang/src/lexer.rs b/lang/src/lexer.rs index f5d73d4..e5c400e 100644 --- a/lang/src/lexer.rs +++ b/lang/src/lexer.rs @@ -20,7 +20,7 @@ impl From for LexingError { #[derive(Logos, Debug, PartialEq, Clone)] #[logos(error = LexingError)] -#[logos(skip r"[ \t\n\f]+")] +#[logos(skip r"[ \t\n\f\r]+")] pub enum Token { #[regex("//[^\n]*|/\\*([^*]|\\*[^/])*\\*/")] Comment, diff --git a/lang/src/main.rs b/lang/src/main.rs index 55b2433..fd27c13 100644 --- a/lang/src/main.rs +++ b/lang/src/main.rs @@ -15,9 +15,14 @@ fn main() { if args.len() < 2 { println!("Commands: - run - Run a Modu file + run - Run a Modu file repl - Start the Modu REPL - server [port] - Start the Modu server, default port is 2424"); + server [port] - Start the Modu server, default port is 2424 + init - Initialize a new Modu package + login - Login with Modu Packages + publish - Publish a Modu package + install - Install a Modu package + uninstall - Uninstall a Modu package"); return; } @@ -25,10 +30,13 @@ fn main() { match action.as_str() { "run" => cli::run::run(), - "repl" => cli::repl::repl(), - "server" => cli::server::server(), + "login" => cli::login::login(), + "init" => cli::init::init(), + "publish" => cli::publish::publish(), + "install" => cli::install::install(), + "uninstall" => cli::uninstall::uninstall(), _ => { println!("Invalid action"); diff --git a/lang/src/parser.rs b/lang/src/parser.rs index 0900581..1e1ccc5 100644 --- a/lang/src/parser.rs +++ b/lang/src/parser.rs @@ -6,25 +6,181 @@ use logos::Logos; use std::collections::HashMap; use std::vec; +pub fn insert_right_bracket(mut obj: AST) -> AST { + match obj { + AST::Function { name, args, mut body, line } => { + match body.pop().unwrap_or(AST::Null) { + AST::IfStatement { condition, body: mut if_body, line: if_line } => { + match if_body.pop().unwrap_or(AST::Null) { + AST::RBracket => { + if_body.push(AST::RBracket); + + body.push(AST::IfStatement { + condition, + body: if_body, + line: if_line, + }); + + body.push(AST::RBracket); + } + + val => { + if_body.push(val); + + let new_body = insert_right_bracket(AST::IfStatement { + condition, + body: if_body, + line, + }); + + body.push(new_body); + } + } + } + + AST::Null => { + body.push(AST::RBracket); + } + + val => { + body.push(val); + + body.push(AST::RBracket); + } + } + + return AST::Function { + name, + args, + body, + line, + }; + } + + AST::IfStatement { condition, mut body, line } => { + match body.pop().unwrap_or(AST::Null) { + AST::IfStatement { condition: if_condition, body: mut if_body, line: if_line } => { + match if_body.pop().unwrap_or(AST::Null) { + AST::RBracket => { + if_body.push(AST::RBracket); + + body.push(AST::IfStatement { + condition: if_condition, + body: if_body, + line: if_line, + }); + + body.push(AST::RBracket); + } + + val => { + if_body.push(val); + + let new_body = insert_right_bracket(AST::IfStatement { + condition: if_condition, + body: if_body, + line, + }); + + body.push(new_body); + } + } + } + + AST::Null => { + body.push(AST::RBracket); + } + + val => { + body.push(val); + + body.push(AST::RBracket); + } + } + + return AST::IfStatement { + condition, + body, + line, + }; + } + + _ => { + return obj; + } + } +} + pub fn handle_nested_ast(mut ast: Vec, temp_ast: Vec, current_line: usize) -> Result, (String, usize)> { if ast.is_empty() { return Ok(temp_ast); } let last = ast.pop().unwrap(); - + match last { AST::Function { name, args, mut body, line } => { if let Some(last_body_expr) = body.pop() { match last_body_expr { - AST::IfStatement { condition, body: if_body, line: if_line } => { - let updated_body = handle_nested_ast(if_body, temp_ast, current_line)?; + AST::IfStatement { condition, body: mut if_body, line: if_line } => { + match if_body.pop().unwrap_or(AST::Null) { + AST::RBracket => { + if_body.push(AST::RBracket); + + body.push(AST::IfStatement { + condition, + body: if_body, + line: if_line, + }); + + body.extend(temp_ast); + } + + AST::Null => { + let updated_body = handle_nested_ast(if_body, temp_ast, current_line)?; + + body.push(AST::IfStatement { + condition, + body: updated_body, + line: if_line, + }); + } + + val => { + if_body.push(val); + + let updated_body = handle_nested_ast(if_body, temp_ast, current_line)?; + + body.push(AST::IfStatement { + condition, + body: updated_body, + line: if_line, + }); + } + } + + /*let updated_body = handle_nested_ast(if_body, temp_ast, current_line)?; body.push(AST::IfStatement { condition, body: updated_body, line: if_line, + });*/ + } + + AST::RBracket => { + body.push(AST::RBracket); + + ast.push(AST::Function { + name, + args, + body, + line, }); + + ast.extend(temp_ast); + + return Ok(ast); } AST::Null => { @@ -53,14 +209,64 @@ pub fn handle_nested_ast(mut ast: Vec, temp_ast: Vec, current_line: us AST::IfStatement { condition, mut body, line } => { if let Some(last_body_expr) = body.pop() { match last_body_expr { - AST::IfStatement { condition: if_condition, body: if_body, line: if_line } => { - let updated_body = handle_nested_ast(if_body, temp_ast, current_line)?; + AST::IfStatement { condition, body: mut if_body, line: if_line } => { + match if_body.pop().unwrap_or(AST::Null) { + AST::RBracket => { + if_body.push(AST::RBracket); + + body.push(AST::IfStatement { + condition, + body: if_body, + line: if_line, + }); + + body.extend(temp_ast); + } + + AST::Null => { + let updated_body = handle_nested_ast(if_body, temp_ast, current_line)?; + + body.push(AST::IfStatement { + condition, + body: updated_body, + line: if_line, + }); + } + + val => { + if_body.push(val); + + let updated_body = handle_nested_ast(if_body, temp_ast, current_line)?; + + body.push(AST::IfStatement { + condition, + body: updated_body, + line: if_line, + }); + } + } + + /*let updated_body = handle_nested_ast(if_body, temp_ast, current_line)?; body.push(AST::IfStatement { - condition: if_condition, + condition, body: updated_body, line: if_line, + });*/ + } + + AST::RBracket => { + body.push(AST::RBracket); + + ast.push(AST::IfStatement { + condition, + body, + line, }); + + ast.extend(temp_ast); + + return Ok(ast); } AST::Null => { @@ -592,7 +798,13 @@ pub fn clean_args(obj: AST) -> AST { let mut new_body = vec![]; for expr in body { - new_body.push(clean_args(expr)); + match expr { + AST::RBracket => {} + + _ => { + new_body.push(clean_args(expr)); + } + } } AST::IfStatement { @@ -606,7 +818,13 @@ pub fn clean_args(obj: AST) -> AST { let mut new_body = vec![]; for expr in body { - new_body.push(clean_args(expr)); + match expr { + AST::RBracket => {} + + _ => { + new_body.push(clean_args(expr)); + } + } } AST::Function { @@ -2597,31 +2815,33 @@ pub fn parse(input: &str, context: &mut HashMap) -> Result<(), (Str let value = ast.pop().unwrap_or(AST::Null); match value { - AST::Function { name, args, body, line } => { - ast.push(AST::Function { + AST::Function { name, args, mut body, line } => { + let new_obj = insert_right_bracket(AST::Function { name, args, body, line, }); - bodies_deep -= 1; + ast.push(new_obj); } - AST::IfStatement { condition, body, line } => { - ast.push(AST::IfStatement { + AST::IfStatement { condition, mut body, line } => { + let new_obj = insert_right_bracket(AST::IfStatement { condition, body, line, }); - bodies_deep -= 1; + ast.push(new_obj); } _ => { return Err(("Expected a function or if statement before '}'".to_string(), current_line)); } } + + bodies_deep -= 1; } Err(err) => { @@ -2654,7 +2874,7 @@ pub fn parse(input: &str, context: &mut HashMap) -> Result<(), (Str } if verbose { - dbg!(&ast); + //dbg!(&ast); } } diff --git a/web/src/lib/docs/quickstart.md b/web/src/lib/docs/quickstart.md index 0d760c4..cc4c8f5 100644 --- a/web/src/lib/docs/quickstart.md +++ b/web/src/lib/docs/quickstart.md @@ -6,7 +6,7 @@ Can be used through [The Web IDE](../ide) btw From [crates.io](https://crates.io/crates/modu) ```bash -$ cargo install modu +$ cargo +nightly install modu ``` More methods coming soon \