diff --git a/examples/math.wasm b/examples/math.wasm new file mode 100644 index 0000000..eafd92c Binary files /dev/null and b/examples/math.wasm differ diff --git a/examples/math.woke b/examples/math.woke new file mode 100644 index 0000000..f072cda --- /dev/null +++ b/examples/math.woke @@ -0,0 +1,36 @@ +// Math functions for WASM compilation + +to add(a: Int, b: Int) -> Int { + give back a + b; +} + +to multiply(a: Int, b: Int) -> Int { + give back a * b; +} + +to factorial(n: Int) -> Int { + when n <= 1 { + give back 1; + } + give back n * factorial(n - 1); +} + +to fibonacci(n: Int) -> Int { + when n <= 0 { + give back 0; + } + when n == 1 { + give back 1; + } + give back fibonacci(n - 1) + fibonacci(n - 2); +} + +to sum_to_n(n: Int) -> Int { + remember total = 0; + remember i = n; + repeat n times { + total = total + i; + i = i - 1; + } + give back total; +} diff --git a/include/wokelang.h b/include/wokelang.h new file mode 100644 index 0000000..78bf1e4 --- /dev/null +++ b/include/wokelang.h @@ -0,0 +1,213 @@ +/** + * WokeLang C API + * + * This header provides the C-compatible interface for WokeLang. + * It can be used from C, C++, Zig, or any language supporting the C ABI. + * + * Example usage: + * + * #include "wokelang.h" + * + * int main() { + * WokeInterpreter* interp = woke_interpreter_new(); + * if (!interp) return 1; + * + * const char* code = + * "to greet(name: String) -> String {\n" + * " give back \"Hello, \" + name + \"!\";\n" + * "}\n"; + * + * WokeResult result = woke_exec(interp, code); + * if (result != WOKE_OK) { + * printf("Error: %d\n", result); + * } + * + * woke_interpreter_free(interp); + * return 0; + * } + */ + +#ifndef WOKELANG_H +#define WOKELANG_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque types */ +typedef struct WokeInterpreter WokeInterpreter; +typedef struct WokeValue WokeValue; + +/* Result codes */ +typedef enum WokeResult { + WOKE_OK = 0, + WOKE_ERROR = 1, + WOKE_PARSE_ERROR = 2, + WOKE_RUNTIME_ERROR = 3, + WOKE_NULL_POINTER = 4 +} WokeResult; + +/* Value type tags */ +typedef enum WokeValueType { + WOKE_TYPE_INT = 0, + WOKE_TYPE_FLOAT = 1, + WOKE_TYPE_STRING = 2, + WOKE_TYPE_BOOL = 3, + WOKE_TYPE_ARRAY = 4, + WOKE_TYPE_UNIT = 5 +} WokeValueType; + +/* === Interpreter lifecycle === */ + +/** + * Create a new WokeLang interpreter. + * + * @return Pointer to the interpreter, or NULL on failure. + * The caller is responsible for freeing with woke_interpreter_free. + */ +WokeInterpreter* woke_interpreter_new(void); + +/** + * Free a WokeLang interpreter. + * + * @param interp The interpreter to free. May be NULL (no-op). + */ +void woke_interpreter_free(WokeInterpreter* interp); + +/** + * Execute WokeLang source code. + * + * @param interp The interpreter instance. + * @param source Null-terminated WokeLang source code. + * @return WOKE_OK on success, error code otherwise. + */ +WokeResult woke_exec(WokeInterpreter* interp, const char* source); + +/** + * Evaluate an expression and get the result. + * + * @param interp The interpreter instance. + * @param source Null-terminated expression to evaluate. + * @param out_value Pointer to receive the result value. + * @return WOKE_OK on success, error code otherwise. + */ +WokeResult woke_eval(WokeInterpreter* interp, const char* source, WokeValue** out_value); + +/* === Value operations === */ + +/** + * Free a WokeValue. + * + * @param value The value to free. May be NULL (no-op). + */ +void woke_value_free(WokeValue* value); + +/** + * Get the type of a WokeValue. + * + * @param value The value to inspect. + * @return The type tag. + */ +WokeValueType woke_value_type(const WokeValue* value); + +/** + * Get an integer from a WokeValue. + * + * @param value The value to read. + * @param out Pointer to receive the integer. + * @return WOKE_OK on success, WOKE_ERROR if type mismatch. + */ +WokeResult woke_value_as_int(const WokeValue* value, int64_t* out); + +/** + * Get a float from a WokeValue. + * + * @param value The value to read. + * @param out Pointer to receive the float. + * @return WOKE_OK on success, WOKE_ERROR if type mismatch. + */ +WokeResult woke_value_as_float(const WokeValue* value, double* out); + +/** + * Get a boolean from a WokeValue. + * + * @param value The value to read. + * @param out Pointer to receive the boolean (0 or 1). + * @return WOKE_OK on success, WOKE_ERROR if type mismatch. + */ +WokeResult woke_value_as_bool(const WokeValue* value, int* out); + +/** + * Get a string from a WokeValue. + * + * @param value The value to read. + * @return Newly allocated string, or NULL on error. + * Caller must free with woke_string_free. + */ +char* woke_value_as_string(const WokeValue* value); + +/** + * Free a string returned by woke_value_as_string. + * + * @param s The string to free. May be NULL (no-op). + */ +void woke_string_free(char* s); + +/* === Value creation === */ + +/** + * Create an integer WokeValue. + * + * @param n The integer value. + * @return New value, or NULL on allocation failure. + */ +WokeValue* woke_value_from_int(int64_t n); + +/** + * Create a float WokeValue. + * + * @param f The float value. + * @return New value, or NULL on allocation failure. + */ +WokeValue* woke_value_from_float(double f); + +/** + * Create a boolean WokeValue. + * + * @param b The boolean value (0 = false, nonzero = true). + * @return New value, or NULL on allocation failure. + */ +WokeValue* woke_value_from_bool(int b); + +/** + * Create a string WokeValue. + * + * @param s Null-terminated string to copy. + * @return New value, or NULL on allocation failure. + */ +WokeValue* woke_value_from_string(const char* s); + +/* === Utility === */ + +/** + * Get the WokeLang version string. + * + * @return Static version string (do not free). + */ +const char* woke_version(void); + +/** + * Get the last error message. + * + * @return Error message, or NULL if no error. + * Valid until the next woke_* call. + */ +const char* woke_last_error(void); + +#ifdef __cplusplus +} +#endif + +#endif /* WOKELANG_H */ diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs new file mode 100644 index 0000000..6e89c9c --- /dev/null +++ b/src/codegen/mod.rs @@ -0,0 +1,3 @@ +mod wasm; + +pub use wasm::WasmCompiler; diff --git a/src/codegen/wasm.rs b/src/codegen/wasm.rs new file mode 100644 index 0000000..1604086 --- /dev/null +++ b/src/codegen/wasm.rs @@ -0,0 +1,509 @@ +use crate::ast::*; +use std::collections::HashMap; +use thiserror::Error; +use wasm_encoder::{ + CodeSection, ExportKind, ExportSection, Function, FunctionSection, Instruction, Module, + TypeSection, ValType, +}; + +#[derive(Error, Debug)] +pub enum CompileError { + #[error("Unsupported feature: {0}")] + Unsupported(String), + + #[error("Undefined variable: {0}")] + UndefinedVariable(String), + + #[error("Undefined function: {0}")] + UndefinedFunction(String), + + #[error("Type error: {0}")] + TypeError(String), +} + +type Result = std::result::Result; + +/// Compiles WokeLang to WebAssembly +pub struct WasmCompiler { + /// Function name to index mapping + functions: HashMap, + /// Function signatures (param count, return count) + signatures: HashMap, Vec)>, + /// Local variable mappings per function + locals: HashMap, + /// Current local index + local_index: u32, +} + +impl WasmCompiler { + pub fn new() -> Self { + Self { + functions: HashMap::new(), + signatures: HashMap::new(), + locals: HashMap::new(), + local_index: 0, + } + } + + /// Compile a WokeLang program to WASM binary + pub fn compile(&mut self, program: &Program) -> Result> { + let mut module = Module::new(); + + // Collect function definitions first + let mut func_defs: Vec<&FunctionDef> = Vec::new(); + for item in &program.items { + if let TopLevelItem::Function(f) = item { + func_defs.push(f); + } + } + + // Build type section (function signatures) + let mut types = TypeSection::new(); + for (idx, func) in func_defs.iter().enumerate() { + let params: Vec = func.params.iter().map(|_| ValType::I64).collect(); + let results: Vec = if func.return_type.is_some() { + vec![ValType::I64] + } else { + vec![] + }; + + types.ty().function(params.clone(), results.clone()); + self.functions.insert(func.name.clone(), idx as u32); + self.signatures + .insert(func.name.clone(), (params, results)); + } + module.section(&types); + + // Build function section (type indices) + let mut functions = FunctionSection::new(); + for idx in 0..func_defs.len() { + functions.function(idx as u32); + } + module.section(&functions); + + // Build export section + let mut exports = ExportSection::new(); + for (name, idx) in &self.functions { + exports.export(name, ExportKind::Func, *idx); + } + module.section(&exports); + + // Build code section + let mut codes = CodeSection::new(); + for func in &func_defs { + let wasm_func = self.compile_function(func)?; + codes.function(&wasm_func); + } + module.section(&codes); + + Ok(module.finish()) + } + + fn compile_function(&mut self, func: &FunctionDef) -> Result { + self.locals.clear(); + self.local_index = 0; + + // Register parameters as locals + for param in &func.params { + self.locals.insert(param.name.clone(), self.local_index); + self.local_index += 1; + } + + // Count additional locals needed + let additional_locals = self.count_locals(&func.body); + + let mut wasm_func = Function::new(vec![(additional_locals, ValType::I64)]); + + // Compile function body + for stmt in &func.body { + self.compile_statement(stmt, &mut wasm_func)?; + } + + // Add implicit return if no explicit return + if func.return_type.is_none() { + wasm_func.instruction(&Instruction::End); + } else { + // If there's a return type but no return statement, push 0 + wasm_func.instruction(&Instruction::End); + } + + Ok(wasm_func) + } + + fn count_locals(&self, stmts: &[Statement]) -> u32 { + let mut count = 0; + for stmt in stmts { + match stmt { + Statement::VarDecl(_) => count += 1, + Statement::Conditional(c) => { + count += self.count_locals(&c.then_branch); + if let Some(else_branch) = &c.else_branch { + count += self.count_locals(else_branch); + } + } + Statement::Loop(l) => { + count += self.count_locals(&l.body); + } + Statement::AttemptBlock(a) => { + count += self.count_locals(&a.body); + } + _ => {} + } + } + count + } + + fn compile_statement(&mut self, stmt: &Statement, func: &mut Function) -> Result<()> { + match stmt { + Statement::VarDecl(decl) => { + // Compile the value expression + self.compile_expr(&decl.value, func)?; + + // Store in local + let local_idx = self.local_index; + self.locals.insert(decl.name.clone(), local_idx); + self.local_index += 1; + + func.instruction(&Instruction::LocalSet(local_idx)); + } + + Statement::Assignment(assign) => { + // Compile the value expression + self.compile_expr(&assign.value, func)?; + + // Store in local + let local_idx = *self + .locals + .get(&assign.target) + .ok_or_else(|| CompileError::UndefinedVariable(assign.target.clone()))?; + + func.instruction(&Instruction::LocalSet(local_idx)); + } + + Statement::Return(ret) => { + self.compile_expr(&ret.value, func)?; + func.instruction(&Instruction::Return); + } + + Statement::Conditional(cond) => { + // Compile condition + self.compile_expr(&cond.condition, func)?; + + // If-else block + func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty)); + + for s in &cond.then_branch { + self.compile_statement(s, func)?; + } + + if let Some(else_branch) = &cond.else_branch { + func.instruction(&Instruction::Else); + for s in else_branch { + self.compile_statement(s, func)?; + } + } + + func.instruction(&Instruction::End); + } + + Statement::Loop(loop_stmt) => { + // Compile loop count + self.compile_expr(&loop_stmt.count, func)?; + + // Store count in a local + let count_local = self.local_index; + self.local_index += 1; + func.instruction(&Instruction::LocalSet(count_local)); + + // Loop structure + func.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty)); + func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty)); + + // Check if count > 0 + func.instruction(&Instruction::LocalGet(count_local)); + func.instruction(&Instruction::I64Const(0)); + func.instruction(&Instruction::I64LeS); + func.instruction(&Instruction::BrIf(1)); // Break out if count <= 0 + + // Execute body + for s in &loop_stmt.body { + self.compile_statement(s, func)?; + } + + // Decrement counter + func.instruction(&Instruction::LocalGet(count_local)); + func.instruction(&Instruction::I64Const(1)); + func.instruction(&Instruction::I64Sub); + func.instruction(&Instruction::LocalSet(count_local)); + + // Continue loop + func.instruction(&Instruction::Br(0)); + + func.instruction(&Instruction::End); // End loop + func.instruction(&Instruction::End); // End block + } + + Statement::Expression(expr) => { + self.compile_expr(expr, func)?; + func.instruction(&Instruction::Drop); // Discard result + } + + Statement::ConsentBlock(_) => { + // Consent blocks are runtime-only, skip in WASM + // Could be implemented with host imports + } + + Statement::AttemptBlock(attempt) => { + // Try-catch can be implemented with WASM exception handling + // For now, just compile the body + for s in &attempt.body { + self.compile_statement(s, func)?; + } + } + + Statement::Complain(_) => { + // Would need host import for console output + } + + Statement::WorkerSpawn(_) => { + return Err(CompileError::Unsupported( + "Workers not supported in WASM".into(), + )); + } + + Statement::EmoteAnnotated(annotated) => { + // Emote tags are metadata, compile the inner statement + self.compile_statement(&annotated.statement, func)?; + } + + Statement::Decide(decide) => { + // Pattern matching - simplified to if-else chain + self.compile_expr(&decide.scrutinee, func)?; + let scrutinee_local = self.local_index; + self.local_index += 1; + func.instruction(&Instruction::LocalSet(scrutinee_local)); + + for (i, arm) in decide.arms.iter().enumerate() { + let is_last = i == decide.arms.len() - 1; + + match &arm.pattern { + Pattern::Wildcard => { + // Wildcard always matches + for s in &arm.body { + self.compile_statement(s, func)?; + } + break; + } + Pattern::Literal(lit) => { + // Compare with literal + func.instruction(&Instruction::LocalGet(scrutinee_local)); + self.compile_literal(lit, func)?; + func.instruction(&Instruction::I64Eq); + + func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty)); + for s in &arm.body { + self.compile_statement(s, func)?; + } + + if !is_last { + func.instruction(&Instruction::Else); + } + } + Pattern::Identifier(name) => { + // Bind the value to the identifier + func.instruction(&Instruction::LocalGet(scrutinee_local)); + let bind_local = self.local_index; + self.locals.insert(name.clone(), bind_local); + self.local_index += 1; + func.instruction(&Instruction::LocalSet(bind_local)); + + for s in &arm.body { + self.compile_statement(s, func)?; + } + break; + } + } + } + + // Close all if blocks + for arm in &decide.arms { + if !matches!(arm.pattern, Pattern::Wildcard | Pattern::Identifier(_)) { + func.instruction(&Instruction::End); + } + } + } + } + + Ok(()) + } + + fn compile_expr(&mut self, expr: &Spanned, func: &mut Function) -> Result<()> { + match &expr.node { + Expr::Literal(lit) => { + self.compile_literal(lit, func)?; + } + + Expr::Identifier(name) => { + let local_idx = *self + .locals + .get(name) + .ok_or_else(|| CompileError::UndefinedVariable(name.clone()))?; + func.instruction(&Instruction::LocalGet(local_idx)); + } + + Expr::Binary(op, left, right) => { + self.compile_expr(left, func)?; + self.compile_expr(right, func)?; + + match op { + BinaryOp::Add => func.instruction(&Instruction::I64Add), + BinaryOp::Sub => func.instruction(&Instruction::I64Sub), + BinaryOp::Mul => func.instruction(&Instruction::I64Mul), + BinaryOp::Div => func.instruction(&Instruction::I64DivS), + BinaryOp::Mod => func.instruction(&Instruction::I64RemS), + BinaryOp::Eq => func.instruction(&Instruction::I64Eq), + BinaryOp::NotEq => func.instruction(&Instruction::I64Ne), + BinaryOp::Lt => func.instruction(&Instruction::I64LtS), + BinaryOp::Gt => func.instruction(&Instruction::I64GtS), + BinaryOp::LtEq => func.instruction(&Instruction::I64LeS), + BinaryOp::GtEq => func.instruction(&Instruction::I64GeS), + BinaryOp::And => func.instruction(&Instruction::I64And), + BinaryOp::Or => func.instruction(&Instruction::I64Or), + }; + } + + Expr::Unary(op, operand) => { + match op { + UnaryOp::Neg => { + func.instruction(&Instruction::I64Const(0)); + self.compile_expr(operand, func)?; + func.instruction(&Instruction::I64Sub); + } + UnaryOp::Not => { + self.compile_expr(operand, func)?; + func.instruction(&Instruction::I64Eqz); + } + }; + } + + Expr::Call(name, args) => { + // Compile arguments + for arg in args { + self.compile_expr(arg, func)?; + } + + // Call function + let func_idx = *self + .functions + .get(name) + .ok_or_else(|| CompileError::UndefinedFunction(name.clone()))?; + func.instruction(&Instruction::Call(func_idx)); + } + + Expr::Array(_) => { + return Err(CompileError::Unsupported( + "Arrays not yet supported in WASM compilation".into(), + )); + } + + Expr::UnitMeasurement(inner, _) => { + // Just compile the inner expression, ignore units + self.compile_expr(inner, func)?; + } + + Expr::GratitudeLiteral(_) => { + // Push 0 as placeholder + func.instruction(&Instruction::I64Const(0)); + } + } + + Ok(()) + } + + fn compile_literal(&self, lit: &Literal, func: &mut Function) -> Result<()> { + match lit { + Literal::Integer(n) => { + func.instruction(&Instruction::I64Const(*n)); + } + Literal::Float(f) => { + // Convert to i64 bits for now (simplified) + func.instruction(&Instruction::I64Const(f.to_bits() as i64)); + } + Literal::Bool(b) => { + func.instruction(&Instruction::I64Const(if *b { 1 } else { 0 })); + } + Literal::String(_) => { + // Strings would need memory allocation + // For now, push 0 as placeholder + func.instruction(&Instruction::I64Const(0)); + } + } + Ok(()) + } +} + +impl Default for WasmCompiler { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lexer::Lexer; + use crate::parser::Parser; + + fn compile(source: &str) -> Result> { + let lexer = Lexer::new(source); + let tokens = lexer.tokenize().expect("Lexer failed"); + let mut parser = Parser::new(tokens, source); + let program = parser.parse().expect("Parser failed"); + let mut compiler = WasmCompiler::new(); + compiler.compile(&program) + } + + #[test] + fn test_compile_simple_function() { + let source = r#" + to add(a: Int, b: Int) -> Int { + give back a + b; + } + "#; + let wasm = compile(source).unwrap(); + assert!(!wasm.is_empty()); + // WASM magic number + assert_eq!(&wasm[0..4], b"\0asm"); + } + + #[test] + fn test_compile_factorial() { + let source = r#" + to factorial(n: Int) -> Int { + when n <= 1 { + give back 1; + } + give back n * factorial(n - 1); + } + "#; + let wasm = compile(source).unwrap(); + assert!(!wasm.is_empty()); + } + + #[test] + fn test_compile_loop() { + let source = r#" + to sum_to_n(n: Int) -> Int { + remember total = 0; + remember i = n; + repeat n times { + total = total + i; + i = i - 1; + } + give back total; + } + "#; + let wasm = compile(source).unwrap(); + assert!(!wasm.is_empty()); + } +} diff --git a/src/ffi/c_api.rs b/src/ffi/c_api.rs new file mode 100644 index 0000000..3a4e4ef --- /dev/null +++ b/src/ffi/c_api.rs @@ -0,0 +1,323 @@ +//! C-compatible API for WokeLang +//! +//! This module provides extern "C" functions that can be called from Zig, C, +//! or any language supporting the C ABI. + +use crate::interpreter::{Interpreter, Value}; +use crate::lexer::Lexer; +use crate::parser::Parser; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_double, c_int, c_longlong}; +use std::ptr; + +/// Opaque handle to a WokeLang interpreter instance +pub struct WokeInterpreter { + inner: Interpreter, +} + +/// Opaque handle to a WokeLang value +pub struct WokeValue { + inner: Value, +} + +/// Result code for FFI operations +#[repr(C)] +pub enum WokeResult { + Ok = 0, + Error = 1, + ParseError = 2, + RuntimeError = 3, + NullPointer = 4, +} + +/// Value type tag for FFI +#[repr(C)] +pub enum WokeValueType { + Int = 0, + Float = 1, + String = 2, + Bool = 3, + Array = 4, + Unit = 5, +} + +// === Interpreter lifecycle === + +/// Create a new WokeLang interpreter +/// +/// Returns a pointer to the interpreter, or null on failure. +/// The caller is responsible for freeing with `woke_interpreter_free`. +#[no_mangle] +pub extern "C" fn woke_interpreter_new() -> *mut WokeInterpreter { + Box::into_raw(Box::new(WokeInterpreter { + inner: Interpreter::new(), + })) +} + +/// Free a WokeLang interpreter +/// +/// # Safety +/// The pointer must be valid and not null. +#[no_mangle] +pub unsafe extern "C" fn woke_interpreter_free(interp: *mut WokeInterpreter) { + if !interp.is_null() { + drop(Box::from_raw(interp)); + } +} + +/// Execute WokeLang source code +/// +/// # Safety +/// - `interp` must be a valid pointer from `woke_interpreter_new` +/// - `source` must be a valid null-terminated C string +#[no_mangle] +pub unsafe extern "C" fn woke_exec(interp: *mut WokeInterpreter, source: *const c_char) -> WokeResult { + if interp.is_null() || source.is_null() { + return WokeResult::NullPointer; + } + + let interp = &mut *interp; + let source = match CStr::from_ptr(source).to_str() { + Ok(s) => s, + Err(_) => return WokeResult::Error, + }; + + let lexer = Lexer::new(source); + let tokens = match lexer.tokenize() { + Ok(t) => t, + Err(_) => return WokeResult::ParseError, + }; + + let mut parser = Parser::new(tokens, source); + let program = match parser.parse() { + Ok(p) => p, + Err(_) => return WokeResult::ParseError, + }; + + match interp.inner.run(&program) { + Ok(_) => WokeResult::Ok, + Err(_) => WokeResult::RuntimeError, + } +} + +/// Execute WokeLang source and get return value +/// +/// # Safety +/// - All pointers must be valid +/// - The returned WokeValue must be freed with `woke_value_free` +#[no_mangle] +pub unsafe extern "C" fn woke_eval( + interp: *mut WokeInterpreter, + source: *const c_char, + out_value: *mut *mut WokeValue, +) -> WokeResult { + if interp.is_null() || source.is_null() || out_value.is_null() { + return WokeResult::NullPointer; + } + + let interp = &mut *interp; + let source = match CStr::from_ptr(source).to_str() { + Ok(s) => s, + Err(_) => return WokeResult::Error, + }; + + // Wrap the expression in a function that returns it + let wrapped = format!( + "to __ffi_eval__() {{ give back {}; }} to main() {{ __ffi_eval__(); }}", + source.trim_end_matches(';') + ); + + let lexer = Lexer::new(&wrapped); + let tokens = match lexer.tokenize() { + Ok(t) => t, + Err(_) => return WokeResult::ParseError, + }; + + let mut parser = Parser::new(tokens, &wrapped); + let program = match parser.parse() { + Ok(p) => p, + Err(_) => return WokeResult::ParseError, + }; + + match interp.inner.run(&program) { + Ok(_) => { + // Return unit value for now (full implementation would capture return value) + *out_value = Box::into_raw(Box::new(WokeValue { inner: Value::Unit })); + WokeResult::Ok + } + Err(_) => WokeResult::RuntimeError, + } +} + +// === Value operations === + +/// Free a WokeValue +/// +/// # Safety +/// The pointer must be valid and from a woke_* function. +#[no_mangle] +pub unsafe extern "C" fn woke_value_free(value: *mut WokeValue) { + if !value.is_null() { + drop(Box::from_raw(value)); + } +} + +/// Get the type of a WokeValue +#[no_mangle] +pub unsafe extern "C" fn woke_value_type(value: *const WokeValue) -> WokeValueType { + if value.is_null() { + return WokeValueType::Unit; + } + + match &(*value).inner { + Value::Int(_) => WokeValueType::Int, + Value::Float(_) => WokeValueType::Float, + Value::String(_) => WokeValueType::String, + Value::Bool(_) => WokeValueType::Bool, + Value::Array(_) => WokeValueType::Array, + Value::Unit => WokeValueType::Unit, + } +} + +/// Get an integer from a WokeValue +#[no_mangle] +pub unsafe extern "C" fn woke_value_as_int(value: *const WokeValue, out: *mut c_longlong) -> WokeResult { + if value.is_null() || out.is_null() { + return WokeResult::NullPointer; + } + + match &(*value).inner { + Value::Int(n) => { + *out = *n; + WokeResult::Ok + } + _ => WokeResult::Error, + } +} + +/// Get a float from a WokeValue +#[no_mangle] +pub unsafe extern "C" fn woke_value_as_float(value: *const WokeValue, out: *mut c_double) -> WokeResult { + if value.is_null() || out.is_null() { + return WokeResult::NullPointer; + } + + match &(*value).inner { + Value::Float(f) => { + *out = *f; + WokeResult::Ok + } + Value::Int(n) => { + *out = *n as c_double; + WokeResult::Ok + } + _ => WokeResult::Error, + } +} + +/// Get a boolean from a WokeValue +#[no_mangle] +pub unsafe extern "C" fn woke_value_as_bool(value: *const WokeValue, out: *mut c_int) -> WokeResult { + if value.is_null() || out.is_null() { + return WokeResult::NullPointer; + } + + match &(*value).inner { + Value::Bool(b) => { + *out = if *b { 1 } else { 0 }; + WokeResult::Ok + } + _ => WokeResult::Error, + } +} + +/// Get a string from a WokeValue +/// +/// The returned string must be freed with `woke_string_free`. +#[no_mangle] +pub unsafe extern "C" fn woke_value_as_string(value: *const WokeValue) -> *mut c_char { + if value.is_null() { + return ptr::null_mut(); + } + + match &(*value).inner { + Value::String(s) => match CString::new(s.as_str()) { + Ok(cs) => cs.into_raw(), + Err(_) => ptr::null_mut(), + }, + other => match CString::new(other.to_string()) { + Ok(cs) => cs.into_raw(), + Err(_) => ptr::null_mut(), + }, + } +} + +/// Free a string returned by woke_value_as_string +#[no_mangle] +pub unsafe extern "C" fn woke_string_free(s: *mut c_char) { + if !s.is_null() { + drop(CString::from_raw(s)); + } +} + +// === Value creation === + +/// Create an integer WokeValue +#[no_mangle] +pub extern "C" fn woke_value_from_int(n: c_longlong) -> *mut WokeValue { + Box::into_raw(Box::new(WokeValue { + inner: Value::Int(n), + })) +} + +/// Create a float WokeValue +#[no_mangle] +pub extern "C" fn woke_value_from_float(f: c_double) -> *mut WokeValue { + Box::into_raw(Box::new(WokeValue { + inner: Value::Float(f), + })) +} + +/// Create a boolean WokeValue +#[no_mangle] +pub extern "C" fn woke_value_from_bool(b: c_int) -> *mut WokeValue { + Box::into_raw(Box::new(WokeValue { + inner: Value::Bool(b != 0), + })) +} + +/// Create a string WokeValue +/// +/// # Safety +/// `s` must be a valid null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn woke_value_from_string(s: *const c_char) -> *mut WokeValue { + if s.is_null() { + return ptr::null_mut(); + } + + match CStr::from_ptr(s).to_str() { + Ok(str) => Box::into_raw(Box::new(WokeValue { + inner: Value::String(str.to_string()), + })), + Err(_) => ptr::null_mut(), + } +} + +// === Utility === + +/// Get the WokeLang version string +#[no_mangle] +pub extern "C" fn woke_version() -> *const c_char { + static VERSION: &[u8] = b"0.1.0\0"; + VERSION.as_ptr() as *const c_char +} + +/// Get the last error message (if any) +/// +/// Returns null if no error. The returned string is valid until the next woke_* call. +#[no_mangle] +pub extern "C" fn woke_last_error() -> *const c_char { + // TODO: Implement thread-local error storage + ptr::null() +} diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs new file mode 100644 index 0000000..a3b09e6 --- /dev/null +++ b/src/ffi/mod.rs @@ -0,0 +1,8 @@ +//! Foreign Function Interface for WokeLang +//! +//! This module provides a C-compatible API that can be used from Zig, C, or any +//! language that supports the C ABI. + +mod c_api; + +pub use c_api::*; diff --git a/src/repl.rs b/src/repl.rs new file mode 100644 index 0000000..bb910bc --- /dev/null +++ b/src/repl.rs @@ -0,0 +1,234 @@ +use crate::ast::TopLevelItem; +use crate::interpreter::Interpreter; +use crate::lexer::Lexer; +use crate::parser::Parser; +use rustyline::error::ReadlineError; +use rustyline::DefaultEditor; + +const BANNER: &str = r#" + __ __ _ _ + \ \ / /__ | | _____| | __ _ _ __ __ _ + \ \ /\ / / _ \| |/ / _ \ | / _` | '_ \ / _` | + \ V V / (_) | < __/ |__| (_| | | | | (_| | + \_/\_/ \___/|_|\_\___|_____\__,_|_| |_|\__, | + |___/ +"#; + +const HELP: &str = r#" +WokeLang REPL Commands: + :help, :h Show this help message + :quit, :q Exit the REPL + :clear, :c Clear the screen + :reset, :r Reset interpreter state + :load Load and run a file + :ast Show AST for an expression + :env Show current environment variables + +Examples: + remember x = 42; + print(x + 8); + to double(n) { give back n * 2; } + double(21) +"#; + +pub struct Repl { + interpreter: Interpreter, + editor: DefaultEditor, +} + +impl Repl { + pub fn new() -> Result> { + let editor = DefaultEditor::new()?; + Ok(Self { + interpreter: Interpreter::new(), + editor, + }) + } + + pub fn run(&mut self) -> Result<(), Box> { + println!("{}", BANNER); + println!("WokeLang v0.1.0 - Interactive REPL"); + println!("Type :help for commands, :quit to exit\n"); + + loop { + let readline = self.editor.readline("woke> "); + + match readline { + Ok(line) => { + let line = line.trim(); + if line.is_empty() { + continue; + } + + let _ = self.editor.add_history_entry(line); + + if line.starts_with(':') { + if self.handle_command(line)? { + break; + } + } else { + self.eval_input(line); + } + } + Err(ReadlineError::Interrupted) => { + println!("^C"); + continue; + } + Err(ReadlineError::Eof) => { + println!("\nGoodbye!"); + break; + } + Err(err) => { + eprintln!("Error: {:?}", err); + break; + } + } + } + + Ok(()) + } + + fn handle_command(&mut self, line: &str) -> Result> { + let parts: Vec<&str> = line.splitn(2, ' ').collect(); + let cmd = parts[0]; + let arg = parts.get(1).map(|s| s.trim()); + + match cmd { + ":quit" | ":q" => { + println!("Goodbye!"); + return Ok(true); + } + ":help" | ":h" => { + println!("{}", HELP); + } + ":clear" | ":c" => { + print!("\x1B[2J\x1B[1;1H"); + } + ":reset" | ":r" => { + self.interpreter = Interpreter::new(); + println!("Interpreter state reset."); + } + ":load" | ":l" => { + if let Some(path) = arg { + self.load_file(path); + } else { + println!("Usage: :load "); + } + } + ":ast" => { + if let Some(code) = arg { + self.show_ast(code); + } else { + println!("Usage: :ast "); + } + } + ":env" => { + println!("(Environment inspection not yet implemented)"); + } + _ => { + println!("Unknown command: {}. Type :help for available commands.", cmd); + } + } + + Ok(false) + } + + fn eval_input(&mut self, input: &str) { + // Try to parse as a program (statements/definitions) + let lexer = Lexer::new(input); + let tokens = match lexer.tokenize() { + Ok(t) => t, + Err(e) => { + eprintln!("Lexer error: {:?}", e); + return; + } + }; + + let mut parser = Parser::new(tokens.clone(), input); + + // First, try parsing as a full program + match parser.parse() { + Ok(program) => { + if let Err(e) = self.interpreter.run(&program) { + eprintln!("Runtime error: {}", e); + } else { + // Check if the last item was a simple expression, print its value + if let Some(TopLevelItem::Function(f)) = program.items.last() { + if f.name == "__repl_expr__" { + // This was a wrapped expression, value already printed + } + } + } + } + Err(_) => { + // Try wrapping as an expression in a function and evaluating + let wrapped = format!( + "to __repl_expr__() {{ remember __result__ = {}; print(__result__); }} + to main() {{ __repl_expr__(); }}", + input.trim_end_matches(';') + ); + + let lexer = Lexer::new(&wrapped); + if let Ok(tokens) = lexer.tokenize() { + let mut parser = Parser::new(tokens, &wrapped); + if let Ok(program) = parser.parse() { + if let Err(e) = self.interpreter.run(&program) { + // If that also fails, show original parse error + eprintln!("Error: {}", e); + } + } else { + eprintln!("Parse error in input"); + } + } + } + } + } + + fn load_file(&mut self, path: &str) { + match std::fs::read_to_string(path) { + Ok(source) => { + println!("Loading {}...", path); + let lexer = Lexer::new(&source); + match lexer.tokenize() { + Ok(tokens) => { + let mut parser = Parser::new(tokens, &source); + match parser.parse() { + Ok(program) => { + if let Err(e) = self.interpreter.run(&program) { + eprintln!("Runtime error: {}", e); + } else { + println!("Loaded successfully."); + } + } + Err(e) => eprintln!("Parse error: {:?}", e), + } + } + Err(e) => eprintln!("Lexer error: {:?}", e), + } + } + Err(e) => eprintln!("Could not read file: {}", e), + } + } + + fn show_ast(&self, code: &str) { + let lexer = Lexer::new(code); + match lexer.tokenize() { + Ok(tokens) => { + let mut parser = Parser::new(tokens, code); + match parser.parse() { + Ok(program) => { + println!("{:#?}", program); + } + Err(e) => eprintln!("Parse error: {:?}", e), + } + } + Err(e) => eprintln!("Lexer error: {:?}", e), + } + } +} + +impl Default for Repl { + fn default() -> Self { + Self::new().expect("Failed to create REPL") + } +} diff --git a/zig/build.zig b/zig/build.zig new file mode 100644 index 0000000..60af38b --- /dev/null +++ b/zig/build.zig @@ -0,0 +1,46 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // Example executable using WokeLang FFI + const exe = b.addExecutable(.{ + .name = "wokelang-zig-example", + .root_source_file = b.path("example.zig"), + .target = target, + .optimize = optimize, + }); + + // Link against the WokeLang library + exe.addLibraryPath(.{ .cwd_relative = "../target/release" }); + exe.linkSystemLibrary("wokelang"); + exe.linkLibC(); + + // Add include path for header + exe.addIncludePath(.{ .cwd_relative = "../include" }); + + b.installArtifact(exe); + + // Run command + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + const run_step = b.step("run", "Run the example"); + run_step.dependOn(&run_cmd.step); + + // Tests + const unit_tests = b.addTest(.{ + .root_source_file = b.path("wokelang.zig"), + .target = target, + .optimize = optimize, + }); + + unit_tests.addLibraryPath(.{ .cwd_relative = "../target/release" }); + unit_tests.linkSystemLibrary("wokelang"); + unit_tests.linkLibC(); + + const run_unit_tests = b.addRunArtifact(unit_tests); + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/zig/example.zig b/zig/example.zig new file mode 100644 index 0000000..41ec454 --- /dev/null +++ b/zig/example.zig @@ -0,0 +1,61 @@ +//! Example of using WokeLang from Zig +//! +//! Build with: zig build +//! Run with: zig build run + +const std = @import("std"); +const woke = @import("wokelang.zig"); + +pub fn main() !void { + const stdout = std.io.getStdOut().writer(); + + // Print version + try stdout.print("WokeLang version: {s}\n\n", .{woke.version()}); + + // Create interpreter + var interp = woke.Interpreter.init() orelse { + try stdout.print("Failed to create interpreter\n", .{}); + return; + }; + defer interp.deinit(); + + // Define some WokeLang functions + const woke_code = + \\// Math functions defined in WokeLang + \\to add(a: Int, b: Int) -> Int { + \\ give back a + b; + \\} + \\ + \\to multiply(a: Int, b: Int) -> Int { + \\ give back a * b; + \\} + \\ + \\to factorial(n: Int) -> Int { + \\ when n <= 1 { + \\ give back 1; + \\ } + \\ give back n * factorial(n - 1); + \\} + \\ + \\to greet(name: String) { + \\ print("Hello, " + name + "!"); + \\} + \\ + \\to main() { + \\ greet("Zig"); + \\ print("5! = " + toString(factorial(5))); + \\ print("3 + 4 = " + toString(add(3, 4))); + \\} + ; + + try stdout.print("Executing WokeLang code...\n", .{}); + try stdout.print("---\n", .{}); + + interp.exec(woke_code) catch |err| { + try stdout.print("Error: {}\n", .{err}); + return; + }; + + try stdout.print("---\n", .{}); + try stdout.print("Done!\n", .{}); +} diff --git a/zig/wokelang.zig b/zig/wokelang.zig new file mode 100644 index 0000000..121c98d --- /dev/null +++ b/zig/wokelang.zig @@ -0,0 +1,229 @@ +//! WokeLang FFI bindings for Zig +//! +//! This module provides Zig-native bindings to the WokeLang interpreter. +//! +//! Example usage: +//! ```zig +//! const woke = @import("wokelang.zig"); +//! +//! pub fn main() !void { +//! var interp = woke.Interpreter.init() orelse return error.InitFailed; +//! defer interp.deinit(); +//! +//! try interp.exec( +//! \\to greet(name: String) -> String { +//! \\ give back "Hello, " + name + "!"; +//! \\} +//! ); +//! } +//! ``` + +const std = @import("std"); + +/// Result codes from WokeLang FFI operations +pub const Result = enum(c_int) { + ok = 0, + @"error" = 1, + parse_error = 2, + runtime_error = 3, + null_pointer = 4, + + pub fn isOk(self: Result) bool { + return self == .ok; + } + + pub fn toError(self: Result) ?Error { + return switch (self) { + .ok => null, + .@"error" => Error.GenericError, + .parse_error => Error.ParseError, + .runtime_error => Error.RuntimeError, + .null_pointer => Error.NullPointer, + }; + } +}; + +/// Value type tags +pub const ValueType = enum(c_int) { + int = 0, + float = 1, + string = 2, + bool = 3, + array = 4, + unit = 5, +}; + +/// Errors that can occur when using the WokeLang FFI +pub const Error = error{ + GenericError, + ParseError, + RuntimeError, + NullPointer, + InitFailed, +}; + +// === External C API declarations === + +const WokeInterpreter = opaque {}; +const WokeValue = opaque {}; + +extern fn woke_interpreter_new() ?*WokeInterpreter; +extern fn woke_interpreter_free(interp: *WokeInterpreter) void; +extern fn woke_exec(interp: *WokeInterpreter, source: [*:0]const u8) Result; +extern fn woke_eval(interp: *WokeInterpreter, source: [*:0]const u8, out_value: *?*WokeValue) Result; + +extern fn woke_value_free(value: *WokeValue) void; +extern fn woke_value_type(value: *const WokeValue) ValueType; +extern fn woke_value_as_int(value: *const WokeValue, out: *i64) Result; +extern fn woke_value_as_float(value: *const WokeValue, out: *f64) Result; +extern fn woke_value_as_bool(value: *const WokeValue, out: *c_int) Result; +extern fn woke_value_as_string(value: *const WokeValue) ?[*:0]u8; +extern fn woke_string_free(s: [*:0]u8) void; + +extern fn woke_value_from_int(n: i64) ?*WokeValue; +extern fn woke_value_from_float(f: f64) ?*WokeValue; +extern fn woke_value_from_bool(b: c_int) ?*WokeValue; +extern fn woke_value_from_string(s: [*:0]const u8) ?*WokeValue; + +extern fn woke_version() [*:0]const u8; +extern fn woke_last_error() ?[*:0]const u8; + +// === High-level Zig API === + +/// A WokeLang interpreter instance +pub const Interpreter = struct { + handle: *WokeInterpreter, + + /// Initialize a new WokeLang interpreter + pub fn init() ?Interpreter { + const handle = woke_interpreter_new() orelse return null; + return .{ .handle = handle }; + } + + /// Clean up the interpreter + pub fn deinit(self: *Interpreter) void { + woke_interpreter_free(self.handle); + } + + /// Execute WokeLang source code + pub fn exec(self: *Interpreter, source: [:0]const u8) Error!void { + const result = woke_exec(self.handle, source.ptr); + if (result.toError()) |err| { + return err; + } + } + + /// Execute WokeLang source code (string literal version) + pub fn execLiteral(self: *Interpreter, comptime source: [:0]const u8) Error!void { + return self.exec(source); + } + + /// Evaluate an expression and get the result + pub fn eval(self: *Interpreter, source: [:0]const u8) Error!Value { + var out_value: ?*WokeValue = null; + const result = woke_eval(self.handle, source.ptr, &out_value); + if (result.toError()) |err| { + return err; + } + return Value{ .handle = out_value.? }; + } +}; + +/// A WokeLang value +pub const Value = struct { + handle: *WokeValue, + + /// Free the value + pub fn deinit(self: *Value) void { + woke_value_free(self.handle); + } + + /// Get the type of the value + pub fn getType(self: Value) ValueType { + return woke_value_type(self.handle); + } + + /// Get as integer + pub fn asInt(self: Value) Error!i64 { + var out: i64 = 0; + const result = woke_value_as_int(self.handle, &out); + if (result.toError()) |err| { + return err; + } + return out; + } + + /// Get as float + pub fn asFloat(self: Value) Error!f64 { + var out: f64 = 0; + const result = woke_value_as_float(self.handle, &out); + if (result.toError()) |err| { + return err; + } + return out; + } + + /// Get as boolean + pub fn asBool(self: Value) Error!bool { + var out: c_int = 0; + const result = woke_value_as_bool(self.handle, &out); + if (result.toError()) |err| { + return err; + } + return out != 0; + } + + /// Get as string (allocates) + pub fn asString(self: Value, allocator: std.mem.Allocator) Error![]u8 { + const c_str = woke_value_as_string(self.handle) orelse return Error.NullPointer; + defer woke_string_free(c_str); + + const len = std.mem.len(c_str); + const buf = allocator.alloc(u8, len) catch return Error.GenericError; + @memcpy(buf, c_str[0..len]); + return buf; + } + + /// Create from integer + pub fn fromInt(n: i64) ?Value { + const handle = woke_value_from_int(n) orelse return null; + return .{ .handle = handle }; + } + + /// Create from float + pub fn fromFloat(f: f64) ?Value { + const handle = woke_value_from_float(f) orelse return null; + return .{ .handle = handle }; + } + + /// Create from boolean + pub fn fromBool(b: bool) ?Value { + const handle = woke_value_from_bool(if (b) 1 else 0) orelse return null; + return .{ .handle = handle }; + } + + /// Create from string + pub fn fromString(s: [:0]const u8) ?Value { + const handle = woke_value_from_string(s.ptr) orelse return null; + return .{ .handle = handle }; + } +}; + +/// Get the WokeLang version string +pub fn version() []const u8 { + const ver = woke_version(); + return std.mem.span(ver); +} + +/// Get the last error message (if any) +pub fn lastError() ?[]const u8 { + const err = woke_last_error() orelse return null; + return std.mem.span(err); +} + +// === Tests === + +test "version" { + const ver = version(); + try std.testing.expectEqualStrings("0.1.0", ver); +}