From 0ad82fdd2d4ac4f9e37f44289f39a464d2bc55c0 Mon Sep 17 00:00:00 2001 From: RobbyV2 Date: Tue, 26 Nov 2024 18:00:46 -0600 Subject: [PATCH] 0.9.500 - WASM - Fixes --- .github/workflows/build.yml | 43 ++++++++++++++- .gitignore | 3 +- Cargo.lock | 42 ++++++++++++++- Cargo.toml | 30 +++++++++-- Cross.toml | 5 ++ build_release.sh | 48 +++++++++++++---- installer/pseudolang.nsi | 4 +- readme.md | 2 +- src/core.rs | 77 ++++++++++++++++++++++++++ src/interpreter.rs | 12 ++--- src/lib.rs | 5 ++ src/main.rs | 105 ++++++------------------------------ src/wasi.rs | 16 ++++++ src/wasm.rs | 17 ++++++ wapm.toml | 16 ++++++ 15 files changed, 306 insertions(+), 119 deletions(-) create mode 100644 Cross.toml create mode 100644 src/core.rs create mode 100644 src/wasi.rs create mode 100644 src/wasm.rs create mode 100644 wapm.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b65c021..53385c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,6 +38,8 @@ jobs: target: - x86_64-pc-windows-gnu - x86_64-unknown-linux-gnu + - wasm32-unknown-unknown + - wasm32-wasip1 steps: - uses: actions/checkout@v3 @@ -51,10 +53,31 @@ jobs: target: ${{ matrix.target }} - name: Install Cross + if: matrix.target != 'wasm32-unknown-unknown' && matrix.target != 'wasm32-wasip1' run: cargo install cross - - name: Build Release - run: cross build --release --target ${{ matrix.target }} + - name: Install wasm-pack + if: matrix.target == 'wasm32-unknown-unknown' + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Install wasm32-wasip1 target + if: matrix.target == 'wasm32-wasip1' + run: rustup target add wasm32-wasip1 + + - name: Build Native + if: matrix.target != 'wasm32-unknown-unknown' && matrix.target != 'wasm32-wasip1' + run: cross build --release --target ${{ matrix.target }} --features native + + - name: Build WASM + if: matrix.target == 'wasm32-unknown-unknown' + run: | + wasm-pack build --target web --release -- --features wasm + mkdir -p release/wasm + cp pkg/* release/wasm/ + + - name: Build for WASI target + if: matrix.target == 'wasm32-wasip1' + run: cargo build --release --target wasm32-wasip1 --features wasi - name: Prepare Artifacts run: | @@ -99,6 +122,22 @@ jobs: retention-days: 7 if-no-files-found: error + - name: Upload WASM Artifacts + if: matrix.target == 'wasm32-unknown-unknown' + uses: actions/upload-artifact@v4 + with: + name: pseudolang-wasm + path: pkg/* + retention-days: 7 + if-no-files-found: error + + - name: Upload WASI artifact + if: matrix.target == 'wasm32-wasip1' + uses: actions/upload-artifact@v4 + with: + name: fplc-wasi + path: target/wasm32-wasip1/release/fplc.wasm + publish: name: Publish Release needs: build diff --git a/.gitignore b/.gitignore index 62cbd88..59316db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ */.vscode /target /release -*fplc* \ No newline at end of file +*fplc* +/pkg \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a25d11c..dbef64f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,6 +94,16 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -102,12 +112,18 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "fplc" -version = "0.9.498" +version = "0.9.500" dependencies = [ "chrono", "chrono-tz", + "console_error_panic_hook", + "getrandom", "libc", "rand", + "wasi", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", "winapi", ] @@ -118,8 +134,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -381,6 +399,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.95" @@ -410,6 +440,16 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 72e84ec..4642674 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,17 @@ [package] name = "fplc" -version = "0.9.498" +version = "0.9.500" edition = "2021" description = "A pseudolang interpreter written in Rust" +repository = "https://github.com/PseudoLang-Software-Foundation/Pseudolang" +license = "MIT" [lib] -name = "fplc" -path = "src/lib.rs" +crate-type = ["cdylib", "rlib", "staticlib"] + +[package.metadata.wasm-pack] +profile.release.wasm-opt = ["-Oz"] [profile.dev] panic = "abort" @@ -22,6 +26,12 @@ strip = true [dependencies] rand = "0.8.5" +chrono = { version = "0.4", optional = true } +chrono-tz = { version = "0.10.0", optional = true } +wasm-bindgen = "0.2" +console_error_panic_hook = "0.1" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] chrono = "0.4" chrono-tz = "0.10.0" @@ -33,3 +43,17 @@ libc = "0.2" [target.'cfg(target_os = "macos")'.dependencies] libc = "0.2" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" +console_error_panic_hook = "0.1" +wasm-bindgen-futures = "0.4" +web-sys = { version = "0.3", features = ["console"] } +getrandom = { version = "0.2", features = ["js"] } +wasi = "0.11" + +[features] +default = ["native"] +native = ["chrono", "chrono-tz"] +wasm = [] +wasi = [] diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..7cdfb38 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,5 @@ +[target.x86_64-unknown-linux-gnu] +image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main" + +[target.x86_64-pc-windows-gnu] +image = "ghcr.io/cross-rs/x86_64-pc-windows-gnu:main" \ No newline at end of file diff --git a/build_release.sh b/build_release.sh index c90cb0e..9478e5a 100644 --- a/build_release.sh +++ b/build_release.sh @@ -1,22 +1,48 @@ #!/bin/bash +set -e + chmod +x build_release.sh mkdir -p release/installer - -echo "Building for Windows..." -cross build --target x86_64-pc-windows-gnu --release - -echo "Building for Linux..." -cross build --target x86_64-unknown-linux-gnu --release +mkdir -p release/wasm +mkdir -p release/wasi + +echo "Building native targets..." + +echo "Building Windows target..." +cross build --release --target x86_64-pc-windows-gnu --features native || { + echo "Windows build failed but continuing..." +} + +echo "Building Linux target..." +cross build --release --target x86_64-unknown-linux-gnu --features native || { + echo "Linux build failed but continuing..." +} + +echo "Building WASM target..." +if command -v wasm-pack >/dev/null 2>&1; then + wasm-pack build --target web --release -- --features wasm + cp pkg/* release/wasm/ +else + echo "wasm-pack not found, skipping WASM build" +fi + +echo "Building WASI target..." +rustup target add wasm32-wasip1 +cargo build --release --target wasm32-wasip1 --features wasi +cp target/wasm32-wasip1/release/fplc.wasm release/wasi/ echo "Copying binaries to release folder..." -cp target/x86_64-pc-windows-gnu/release/fplc.exe release/fplc-x64.exe -cp release/fplc-x64.exe installer/fplc.exe - -cp target/x86_64-unknown-linux-gnu/release/fplc release/fplc-linux-x64 +if [ -f "target/x86_64-pc-windows-gnu/release/fplc.exe" ]; then + cp target/x86_64-pc-windows-gnu/release/fplc.exe release/fplc-x64.exe + cp release/fplc-x64.exe installer/fplc.exe +fi -chmod +x release/fplc-linux-* +if [ -f "target/x86_64-unknown-linux-gnu/release/fplc" ]; then + cp target/x86_64-unknown-linux-gnu/release/fplc release/fplc-linux-x64 + chmod +x release/fplc-linux-x64 +fi echo "Build complete! Binaries are in the release folder." diff --git a/installer/pseudolang.nsi b/installer/pseudolang.nsi index cea16b6..a25d0c1 100644 --- a/installer/pseudolang.nsi +++ b/installer/pseudolang.nsi @@ -4,7 +4,7 @@ !define MUI_ICON "Pseudolang-Logo.ico" -Name "PseudoLang Installer v0.9.498" +Name "PseudoLang Installer v0.9.500" InstallDir "$PROGRAMFILES\PseudoLang\" OutFile "../release/installer/pseudolang-setup-x64.exe" BrandingText "(c) 2024 PseudoLang Software Foundation" @@ -33,7 +33,7 @@ Section "" WriteRegStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "Path" "$INSTDIR;$R0" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pseudolang" "DisplayName" "Pseudolang" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pseudolang" "DisplayVersion" "0.9.498" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pseudolang" "DisplayVersion" "0.9.500" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pseudolang" "Publisher" "Pseudolang Software Foundation" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pseudolang" "DisplayIcon" "$INSTDIR\Pseudolang-Logo.ico" diff --git a/readme.md b/readme.md index 4caaf02..749cfce 100644 --- a/readme.md +++ b/readme.md @@ -9,7 +9,7 @@

Build and Test Pseudolang - Version + Version Nightly Releases

diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..3e00ff4 --- /dev/null +++ b/src/core.rs @@ -0,0 +1,77 @@ +use crate::{interpreter, lexer::Lexer, parser}; + +pub fn execute_code(input: &str, debug: bool, return_output: bool) -> Result { + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize(); + + if debug { + println!("\n=== Lexer Output ==="); + println!("Tokens: {:?}", tokens); + println!("\n=== Parser Starting ==="); + } + + let ast = parser::parse(tokens, debug)?; + + if debug { + println!("\n=== Parser Output ==="); + println!("AST: {:#?}", ast); + println!("\n=== Starting Execution ==="); + } + + let output = interpreter::run(ast)?; + if !return_output && !output.is_empty() { + print!("{}", output); + } + Ok(output) +} + +pub const HELP_MESSAGE: &str = r#"PseudoLang Usage: + fplc [OPTIONS] COMMAND [ARGS] + +COMMANDS: + run Execute a PseudoLang program + +OPTIONS: + -h, --help Display this help message + -v, --version Display version information + -d, --debug Enable debug output during execution + +Examples: + fplc run program.psl + fplc run --debug source.psl +"#; + +pub const UNKNOWN_OPTION_ERROR: &str = "Unknown option: {}"; +pub const UNKNOWN_COMMAND_ERROR: &str = "Unknown command: {}"; +pub const MISSING_INPUT_ERROR: &str = "Missing required argument: input_file"; +pub const NO_COMMAND_ERROR: &str = "No command provided"; +pub const INVALID_EXTENSION_ERROR: &str = "Input file must have .psl extension, got: {}"; +pub const USAGE_TIP: &str = "\n\nTip: Use -h or --help for detailed usage information."; + +pub fn format_unknown_option_error(option: &str) -> String { + format!( + "{}{}", + UNKNOWN_OPTION_ERROR.replace("{}", option), + USAGE_TIP + ) +} + +pub fn format_unknown_command_error(cmd: &str) -> String { + format!("{}{}", UNKNOWN_COMMAND_ERROR.replace("{}", cmd), USAGE_TIP) +} + +pub fn format_missing_input_error() -> String { + format!("{}{}", MISSING_INPUT_ERROR, USAGE_TIP) +} + +pub fn format_no_command_error() -> String { + format!("{}{}", NO_COMMAND_ERROR, USAGE_TIP) +} + +pub fn format_invalid_extension_error(file: &str) -> String { + format!( + "{}{}", + INVALID_EXTENSION_ERROR.replace("{}", file), + USAGE_TIP + ) +} diff --git a/src/interpreter.rs b/src/interpreter.rs index d3ac701..9fdd64b 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -2,7 +2,7 @@ use crate::parser::{AstNode, BinaryOperator, UnaryOperator}; use rand::Rng; use std::cell::RefCell; use std::collections::HashMap; -use std::io::{self, stdout, Write}; +use std::io::{self, Write}; use std::rc::Rc; use std::thread; use std::time::Duration; @@ -235,14 +235,10 @@ fn evaluate_node( let value = if let Some(expr) = expr { let result = evaluate_node(expr, Rc::clone(&env), debug)?; let output = value_to_string(&result); - println!("{}", output); - stdout().flush().map_err(|e| e.to_string())?; env.borrow_mut().output.push_str(&output); env.borrow_mut().output.push('\n'); result } else { - println!(); - stdout().flush().map_err(|e| e.to_string())?; env.borrow_mut().output.push('\n'); Value::Unit }; @@ -252,8 +248,6 @@ fn evaluate_node( AstNode::DisplayInline(expr) => { let value = evaluate_node(expr, Rc::clone(&env), debug)?; let output = value_to_string(&value); - print!("{}", output); - stdout().flush().map_err(|e| e.to_string())?; env.borrow_mut().output.push_str(&output); Ok(Value::Unit) } @@ -261,7 +255,9 @@ fn evaluate_node( AstNode::Input(prompt) => { if let Some(prompt_expr) = prompt { let prompt_val = evaluate_node(prompt_expr, Rc::clone(&env), debug)?; - print!("{}", value_to_string(&prompt_val)); + env.borrow_mut() + .output + .push_str(&value_to_string(&prompt_val)); } io::stdout().flush().map_err(|e| e.to_string())?; let mut input = String::new(); diff --git a/src/lib.rs b/src/lib.rs index 7f3e4ea..6793934 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,10 @@ +pub mod core; pub mod interpreter; pub mod lexer; pub mod parser; #[cfg(test)] mod tests; +#[cfg(all(target_arch = "wasm32", feature = "wasi"))] +pub mod wasi; +#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))] +pub mod wasm; diff --git a/src/main.rs b/src/main.rs index bc272cc..f552f54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,15 @@ use std::fs; use std::io::Read; +mod core; mod interpreter; mod lexer; mod parser; -use lexer::Lexer; +use core::*; include!(concat!(env!("OUT_DIR"), "/version.rs")); -const HELP_MESSAGE: &str = r#"PseudoLang Usage: - fplc [OPTIONS] COMMAND [ARGS] - -COMMANDS: - run Execute a PseudoLang program - -OPTIONS: - -h, --help Display this help message - -v, --version Display version information - -d, --debug Enable debug output during execution - -Examples: - fplc run program.psl - fplc run --debug source.psl -"#; - #[derive(Debug)] struct Config { command: Command, @@ -44,10 +29,7 @@ fn parse_args() -> Result { let mut args: Vec = std::env::args().skip(1).collect(); if args.is_empty() { - return Err(format!( - "{}\n\nTip: Use -h or --help for detailed usage information.", - HELP_MESSAGE - )); + return Err(format!("{}\n{}", HELP_MESSAGE, USAGE_TIP)); } let mut debug = false; @@ -62,12 +44,7 @@ fn parse_args() -> Result { "--debug" => debug = true, "--help" => show_help = true, "--version" => show_version = true, - _ => { - return Err(format!( - "Unknown option: {}\n\nTip: Use -h or --help for detailed usage information.", - arg - )); - } + _ => return Err(format_unknown_option_error(arg)), } indices_to_remove.push(index); } else if arg.starts_with('-') && arg.len() > 1 { @@ -77,12 +54,7 @@ fn parse_args() -> Result { 'd' => debug = true, 'h' => show_help = true, 'v' => show_version = true, - _ => { - return Err(format!( - "Unknown option: -{}\n\nTip: Use -h or --help for detailed usage information.", - c - )); - } + _ => return Err(format_unknown_option_error(&format!("-{}", c))), } } indices_to_remove.push(index); @@ -106,14 +78,11 @@ fn parse_args() -> Result { match args.get(0).map(String::as_str) { Some("run") => { if args.len() < 2 { - return Err("Missing required argument: input_file\n\nTip: Use -h or --help for detailed usage information.".to_string()); + return Err(format_missing_input_error()); } let input_file = args[1].clone(); if !input_file.ends_with(".psl") { - return Err(format!( - "Input file must have .psl extension, got: {}\n\nTip: Use -h or --help for detailed usage information.", - input_file - )); + return Err(format_invalid_extension_error(&input_file)); } Ok(Config { @@ -124,64 +93,20 @@ fn parse_args() -> Result { show_help, }) } - Some(cmd) => Err(format!( - "Unknown command: {}\n\nTip: Use -h or --help for detailed usage information.", - cmd - )), - None => Err( - "No command provided\n\nTip: Use -h or --help for detailed usage information." - .to_string(), - ), + Some(cmd) => Err(format_unknown_command_error(cmd)), + None => Err(format_no_command_error()), } } -fn process_file( - input_file: &str, - debug: bool, -) -> Result<(Vec, parser::AstNode), String> { - let mut file = match fs::File::open(input_file) { - Ok(file) => file, - Err(error) => { - return Err(format!( - "Error opening file {}: {}\nPlease ensure the file exists and you have read permissions.", - input_file, error - )); - } - }; - - let mut source_code = String::new(); - if let Err(error) = file.read_to_string(&mut source_code) { - return Err(format!("Error reading file {}: {}", input_file, error)); - } - - let mut lexer = Lexer::new(&source_code); - let tokens = lexer.tokenize(); - - if debug { - println!("\n=== Lexer Output ==="); - println!("Tokens: {:?}", tokens); - println!("\n=== Parser Starting ==="); - } - - let ast = parser::parse(tokens.clone(), debug)?; - - if debug { - println!("\n=== Parser Output ==="); - println!("AST: {:#?}", ast); - } - - Ok((tokens, ast)) -} - fn run_program(input_file: &str, debug: bool) -> Result<(), String> { - let (_, ast) = process_file(input_file, debug)?; + let mut file = fs::File::open(input_file) + .map_err(|e| format!("Error opening file {}: {}", input_file, e))?; - if debug { - println!("\n=== Starting Execution ==="); - println!("Executing program...\n"); - } + let mut source_code = String::new(); + file.read_to_string(&mut source_code) + .map_err(|e| format!("Error reading file {}: {}", input_file, e))?; - interpreter::run(ast)?; + execute_code(&source_code, debug, false)?; Ok(()) } diff --git a/src/wasi.rs b/src/wasi.rs new file mode 100644 index 0000000..e44f36d --- /dev/null +++ b/src/wasi.rs @@ -0,0 +1,16 @@ +use crate::core::execute_code; +use std::io::{self, Read}; + +#[no_mangle] +pub extern "C" fn _start() { + let mut input = String::new(); + if let Err(e) = io::stdin().read_to_string(&mut input) { + eprintln!("Error reading input: {}", e); + std::process::exit(1); + } + + if let Err(e) = execute_code(&input, false, false) { + eprintln!("Error: {}", e); + std::process::exit(1); + } +} diff --git a/src/wasm.rs b/src/wasm.rs new file mode 100644 index 0000000..2eba635 --- /dev/null +++ b/src/wasm.rs @@ -0,0 +1,17 @@ +#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))] +use crate::core::execute_code; +#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))] +use wasm_bindgen::prelude::*; + +#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))] +#[wasm_bindgen] +pub fn run_pseudolang(input: &str) -> Result { + console_error_panic_hook::set_once(); + execute_code(input, false, true).map_err(|e| JsValue::from_str(&e)) +} + +#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))] +#[wasm_bindgen] +pub fn initialize_panic_hook() { + console_error_panic_hook::set_once(); +} diff --git a/wapm.toml b/wapm.toml new file mode 100644 index 0000000..07a6e61 --- /dev/null +++ b/wapm.toml @@ -0,0 +1,16 @@ + +[package] +name = "pseudolang/fplc" +version = "0.9.500" +description = "A pseudolang interpreter written in Rust" +license = "MIT" +readme = "readme.md" + +[[module]] +name = "fplc" +source = "target/wasm32-wasip11/release/fplc.wasm" +abi = "wasi" + +[[command]] +name = "fplc" +module = "fplc" \ No newline at end of file