diff --git a/Cargo.lock b/Cargo.lock
index 8304cd4..27ca1f4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "aho-corasick"
@@ -112,7 +112,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "fplc"
-version = "0.9.510"
+version = "0.9.511"
dependencies = [
"chrono",
"chrono-tz",
diff --git a/Cargo.toml b/Cargo.toml
index f455892..22a9b77 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,14 +2,18 @@
[package]
name = "fplc"
-version = "0.9.510"
+version = "0.9.511"
edition = "2021"
description = "A pseudolang interpreter written in Rust"
repository = "https://github.com/PseudoLang-Software-Foundation/Pseudolang"
license = "MIT"
[lib]
-crate-type = ["cdylib", "rlib", "staticlib"]
+crate-type = ["cdylib", "rlib"]
+
+[[bin]]
+name = "fplc"
+path = "src/main.rs"
[package.metadata.wasm-pack]
profile.release.wasm-opt = ["-Oz"]
@@ -48,11 +52,11 @@ libc = "0.2"
wasm-bindgen = { version = "0.2", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
getrandom = { version = "0.2", features = ["js"] }
-wasi = "0.11"
+wasi = { version = "0.11", optional = true }
[features]
default = ["native"]
native = ["chrono", "chrono-tz"]
-wasm = []
-wasi = []
+wasm = ["wasm-bindgen", "console_error_panic_hook"]
+wasi = ["dep:wasi"]
bindgen = ["wasm-bindgen", "console_error_panic_hook"]
diff --git a/Pseudolang.md b/Pseudolang.md
index 0f905b8..dba8d21 100644
--- a/Pseudolang.md
+++ b/Pseudolang.md
@@ -360,9 +360,9 @@ Boolean
A special value representing the absence of a value.
-`NaN`
+`NAN`
-A special numeric value representing an undefined or unrepresentable value. Any arithmetic operation involving NaN results in NaN. Comparing NaN with any value (including another NaN) returns false, except for NaN NOT= NaN which returns true.
+A special numeric value representing an undefined or unrepresentable value. Any arithmetic operation involving NAN results in NAN. Comparing NAN with any value (including another NAN) returns false, except for NAN NOT= NAN which returns true.
## Methods
diff --git a/build_release.sh b/build_release.sh
index c5789ef..789702e 100644
--- a/build_release.sh
+++ b/build_release.sh
@@ -4,10 +4,7 @@ set -e
chmod +x build_release.sh
-mkdir -p release/installer
-mkdir -p release/wasi
-mkdir -p release/wasm/raw
-mkdir -p release/wasm/bindgen
+mkdir -p release/installer release/wasi release/wasm/raw release/wasm/bindgen
echo "Building native targets..."
@@ -26,12 +23,10 @@ rustup target add wasm32-unknown-unknown
echo "Building raw WASM..."
cargo build --release --target wasm32-unknown-unknown --features wasm
-cp target/wasm32-unknown-unknown/release/fplc.wasm release/wasm/raw/
echo "Building WASM with bindgen..."
if command -v wasm-pack >/dev/null 2>&1; then
wasm-pack build --target web --release -- --features "wasm bindgen"
- cp pkg/* release/wasm/bindgen/
else
echo "wasm-pack not found, skipping bindgen WASM build"
fi
@@ -39,10 +34,24 @@ 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 "Building Windows installer..."
+if command -v makensis >/dev/null 2>&1; then
+ cd installer
+ makensis pseudolang.nsi
+ cd ..
+ echo "Windows installer built successfully"
+else
+ echo "makensis not found, skipping installer build"
+ echo "To build the installer, please install NSIS (Nullsoft Scriptable Install System)"
+fi
echo "Copying binaries to release folder..."
+cp target/wasm32-unknown-unknown/release/fplc.wasm release/wasm/raw/
+cp pkg/* release/wasm/bindgen/
+cp target/wasm32-wasip1/release/fplc.wasm release/wasi/
+
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
diff --git a/installer/pseudolang.nsi b/installer/pseudolang.nsi
index 6101108..b9acc46 100644
--- a/installer/pseudolang.nsi
+++ b/installer/pseudolang.nsi
@@ -4,7 +4,7 @@
!define MUI_ICON "Pseudolang-Logo.ico"
-Name "PseudoLang Installer v0.9.510"
+Name "PseudoLang Installer v0.9.511"
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.510"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pseudolang" "DisplayVersion" "0.9.511"
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 490f097..b403ff0 100644
--- a/readme.md
+++ b/readme.md
@@ -9,14 +9,14 @@
Welcome to Pseudolang! Pseudolang is a simple programming language written in Rust, inspired by College Board's Pseudocode.
-This project aims to fully support 64-bit Windows, Linux, and WebAssembly (WASI Package, Raw WASM).
+This project aims to fully support 64-bit Windows, Linux, and WebAssembly (WASI Package, Raw WASM, WASM Bindgen).
## Releases
diff --git a/src/core.rs b/src/core.rs
index bef8678..90b492f 100644
--- a/src/core.rs
+++ b/src/core.rs
@@ -1,6 +1,8 @@
use crate::{interpreter, lexer::Lexer, parser};
use std::fmt::Write;
+include!(concat!(env!("OUT_DIR"), "/version.rs"));
+
pub fn execute_code(input: &str, debug: bool, return_output: bool) -> Result {
let mut lexer = Lexer::new(input);
let tokens = lexer.tokenize();
@@ -20,8 +22,8 @@ pub fn execute_code(input: &str, debug: bool, return_output: bool) -> Result String {
USAGE_TIP
)
}
+
+pub fn get_version() -> String {
+ VERSION.to_string()
+}
diff --git a/src/interpreter.rs b/src/interpreter.rs
index d43c94b..6b85a2d 100644
--- a/src/interpreter.rs
+++ b/src/interpreter.rs
@@ -236,39 +236,66 @@ fn evaluate_node(
}
AstNode::Display(expr) => {
- let value = if let Some(expr) = expr {
+ if let Some(expr) = expr {
let result = evaluate_node(expr, Rc::clone(&env), debug)?;
let output = value_to_string(&result);
+ if !std::thread::current()
+ .name()
+ .map_or(false, |name| name.starts_with("test"))
+ {
+ print!("{}\n", output);
+ io::stdout().flush().unwrap();
+ }
env.borrow_mut().output.push_str(&output);
env.borrow_mut().output.push('\n');
- result
+ Ok(result)
} else {
+ if !std::thread::current()
+ .name()
+ .map_or(false, |name| name.starts_with("test"))
+ {
+ println!();
+ }
env.borrow_mut().output.push('\n');
- Value::Unit
- };
- Ok(value)
+ Ok(Value::Unit)
+ }
}
AstNode::DisplayInline(expr) => {
let value = evaluate_node(expr, Rc::clone(&env), debug)?;
let output = value_to_string(&value);
+ if !std::thread::current()
+ .name()
+ .map_or(false, |name| name.starts_with("test"))
+ {
+ print!("{}", output);
+ io::stdout().flush().unwrap();
+ }
env.borrow_mut().output.push_str(&output);
Ok(Value::Unit)
}
AstNode::Input(prompt) => {
+ let mut input_str = String::new();
+
if let Some(prompt_expr) = prompt {
let prompt_val = evaluate_node(prompt_expr, Rc::clone(&env), debug)?;
- env.borrow_mut()
- .output
- .push_str(&value_to_string(&prompt_val));
+ let prompt_str = value_to_string(&prompt_val);
+ print!("{}", prompt_str);
+ io::stdout().flush().unwrap();
}
- io::stdout().flush().map_err(|e| e.to_string())?;
- let mut input = String::new();
+
io::stdin()
- .read_line(&mut input)
+ .read_line(&mut input_str)
.map_err(|e| e.to_string())?;
- Ok(Value::String(input.trim().to_string()))
+ let input = input_str.trim().to_string();
+
+ if prompt.is_none() {
+ env.borrow_mut().output.push_str(&input);
+ env.borrow_mut().output.push('\n');
+ }
+
+ Ok(Value::String(input))
}
AstNode::ProcedureDecl(name, params, body) => {
@@ -288,6 +315,7 @@ fn evaluate_node(
if args.len() != 1 {
return Err("SLEEP requires one argument".to_string());
}
+ io::stdout().flush().unwrap();
let seconds = evaluate_node(&args[0], Rc::clone(&env), debug)?;
match seconds {
Value::Integer(n) => {
@@ -1491,7 +1519,7 @@ fn value_to_string(value: &Value) -> String {
}
Value::Unit => "".to_string(),
Value::Null => "NULL".to_string(),
- Value::NaN => "NaN".to_string(),
+ Value::NaN => "NAN".to_string(),
}
}
diff --git a/src/lexer.rs b/src/lexer.rs
index 0190777..ffabf1a 100644
--- a/src/lexer.rs
+++ b/src/lexer.rs
@@ -338,7 +338,7 @@ impl<'a> Lexer<'a> {
match identifier.as_str() {
"NULL" => Some(Token::Null),
- "NaN" => Some(Token::NaN),
+ "NAN" => Some(Token::NaN),
"NOT" => {
if self.chars.peek() == Some(&'=') {
self.chars.next();
@@ -366,7 +366,7 @@ impl<'a> Lexer<'a> {
match identifier.as_str() {
"NULL" => Some(Token::Null),
- "NaN" => Some(Token::NaN),
+ "NAN" => Some(Token::NaN),
"MOD" => Some(Token::Modulo),
"DISPLAY" => {
while let Some(&c) = self.chars.peek() {
diff --git a/src/lib.rs b/src/lib.rs
index 6793934..c738608 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,7 +4,5 @@ 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")))]
+#[cfg(target_arch = "wasm32")]
pub mod wasm;
diff --git a/src/main.rs b/src/main.rs
index a6db6de..596cda6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,8 @@ mod core;
mod interpreter;
mod lexer;
mod parser;
+#[cfg(target_arch = "wasm32")]
+mod wasm;
use core::*;
@@ -106,8 +108,10 @@ fn run_program(input_file: &str, debug: bool) -> Result<(), String> {
file.read_to_string(&mut source_code)
.map_err(|e| format!("Error reading file {}: {}", input_file, e))?;
- execute_code(&source_code, debug, false)?;
- Ok(())
+ match execute_code(&source_code, debug, false) {
+ Ok(_) => Ok(()),
+ Err(e) => Err(e),
+ }
}
fn main() -> Result<(), Box> {
@@ -125,7 +129,7 @@ fn main() -> Result<(), Box> {
}
if config.show_version {
- println!("PseudoLang version {}", VERSION);
+ println!("PseudoLang version {}", get_version());
return Ok(());
}
@@ -133,14 +137,11 @@ fn main() -> Result<(), Box> {
println!("\n=== Debug Mode Enabled ===\n");
}
- let result = match config.command {
- Command::Run => run_program(&config.input_file, config.debug),
- Command::None => Ok(()),
- };
-
- if let Err(error) = result {
- eprintln!("Error: {}", error);
- std::process::exit(1);
+ if let Command::Run = config.command {
+ if let Err(error) = run_program(&config.input_file, config.debug) {
+ eprintln!("Error: {}", error);
+ std::process::exit(1);
+ }
}
Ok(())
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 4078969..069f750 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -8,12 +8,13 @@ mod test {
let mut lexer = Lexer::new(input);
let tokens = lexer.tokenize();
let ast = parser::parse(tokens, false)?;
- interpreter::run(ast)
+ let output = interpreter::run(ast)?;
+ Ok(output.trim_end().to_string())
}
fn assert_output(input: &str, expected: &str) {
match run_test(input) {
- Ok(output) => assert_eq!(output.trim(), expected),
+ Ok(output) => assert_eq!(output, expected),
Err(e) => panic!("Test failed for input '{}': {}", input, e),
}
}
@@ -28,11 +29,25 @@ mod test {
assert_output("DISPLAY(-42)", "-42");
assert_output("DISPLAY(FALSE)", "false");
assert_output("DISPLAY([])", "[]");
+ assert_output(r#"DISPLAYINLINE("3")"#, "3");
+
+ assert_output("DISPLAYINLINE(\"Hello\")", "Hello");
+
+ assert_output(
+ r#"
+ DISPLAYINLINE("A")
+ DISPLAYINLINE("B")
+ DISPLAYINLINE("C")"#,
+ "ABC",
+ );
+
assert_output(
r#"
- DISPLAYINLINE("Hello, ")
- DISPLAYINLINE("World!")"#,
- "Hello, World!",
+ DISPLAY("First")
+ DISPLAYINLINE("Hello ")
+ DISPLAYINLINE("World")
+ DISPLAY("\nLast")"#,
+ "First\nHello World\nLast",
);
}
@@ -2372,7 +2387,7 @@ DISPLAY(arr)"#,
#[test]
fn test_null_and_nan() {
assert_output("DISPLAY(NULL)", "NULL");
- assert_output("DISPLAY(NaN)", "NaN");
+ assert_output("DISPLAY(NAN)", "NAN");
assert_output(
r#"
@@ -2386,8 +2401,8 @@ DISPLAY(arr)"#,
assert_output(
r#"
- x <- NaN
- y <- NaN
+ x <- NAN
+ y <- NAN
DISPLAY(x = y)
DISPLAY(x NOT= y)
"#,
@@ -2405,7 +2420,7 @@ DISPLAY(arr)"#,
assert_output(
r#"
- x <- NaN
+ x <- NAN
y <- 42
DISPLAY(x = y)
"#,
@@ -2414,11 +2429,11 @@ DISPLAY(arr)"#,
assert_output(
r#"
- x <- NaN
+ x <- NAN
y <- x + 5
DISPLAY(y)
"#,
- "NaN",
+ "NAN",
);
}
}
diff --git a/src/wasi.rs b/src/wasi.rs
deleted file mode 100644
index 080579a..0000000
--- a/src/wasi.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use crate::core::execute_code;
-use std::io::{self, Read};
-
-#[no_mangle]
-pub extern "C" fn _start() {
- let mut input = String::default();
- 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
index 243df2c..d24dbab 100644
--- a/src/wasm.rs
+++ b/src/wasm.rs
@@ -1,9 +1,12 @@
-#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))]
-use crate::core::execute_code_with_capture;
-#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))]
+#[allow(unused_imports)]
+use crate::core::{execute_code, execute_code_with_capture, get_version as core_get_version};
+
#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))]
use wasm_bindgen::prelude::*;
+#[allow(unused_imports)]
+use std::io::{self, Read, Write};
+
include!(concat!(env!("OUT_DIR"), "/version.rs"));
#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))]
@@ -12,7 +15,7 @@ pub extern "C" fn get_version_raw() -> u64 {
let version = VERSION.as_bytes();
let ptr = version.as_ptr() as u64;
let len = version.len() as u64;
- std::mem::forget(version);
+ let _ = version;
(ptr << 32) | len
}
@@ -50,18 +53,43 @@ pub fn run_pseudolang(input: &str, debug: bool) -> Result {
}
#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))]
-#[no_mangle]
-pub extern "C" fn alloc(size: usize) -> *mut u8 {
- let mut buf = Vec::with_capacity(size);
- let ptr = buf.as_mut_ptr();
- std::mem::forget(buf);
- ptr
+#[wasm_bindgen]
+pub fn get_version_wasm() -> String {
+ core_get_version()
+}
+
+#[allow(dead_code)]
+#[cfg(all(target_arch = "wasm32", feature = "wasi"))]
+pub fn main() {
+ print!("PseudoLang version {}\n", VERSION);
+ io::stdout().flush().unwrap();
+
+ let mut input = String::default();
+ if let Err(e) = std::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);
+ }
}
#[cfg(all(target_arch = "wasm32", not(feature = "wasi")))]
-#[no_mangle]
-pub extern "C" fn dealloc(ptr: *mut u8, size: usize) {
- unsafe {
- let _ = Vec::from_raw_parts(ptr, 0, size);
+mod memory {
+ #[no_mangle]
+ pub extern "C" fn alloc(size: usize) -> *mut u8 {
+ let mut buf = Vec::with_capacity(size);
+ let ptr = buf.as_mut_ptr();
+ std::mem::forget(buf);
+ ptr
+ }
+
+ #[no_mangle]
+ pub extern "C" fn dealloc(ptr: *mut u8, size: usize) {
+ unsafe {
+ let _ = Vec::from_raw_parts(ptr, 0, size);
+ }
}
}
diff --git a/wapm.toml b/wapm.toml
index cdc7bec..9992a00 100644
--- a/wapm.toml
+++ b/wapm.toml
@@ -1,7 +1,7 @@
[package]
name = "pseudolang/fplc"
-version = "0.9.510"
+version = "0.9.511"
description = "A pseudolang interpreter written in Rust"
license = "MIT"
readme = "readme.md"