diff --git a/compiler/TRANSPILER_DEMO.md b/compiler/TRANSPILER_DEMO.md new file mode 100644 index 0000000..a9a6779 --- /dev/null +++ b/compiler/TRANSPILER_DEMO.md @@ -0,0 +1,218 @@ +# W Language Transpiler + +A transpiler that converts W language (Wolfram-like syntax) to idiomatic Rust code, leveraging Rust's compilation process for safety and performance. + +## Implementation Strategy + +Following the approach outlined in `REVISED_STRATEGY.md`, this transpiler: +- Converts W code directly to Rust source code +- Maps W types to Rust stdlib types (`List` → `Vec`, `Map` → `HashMap`) +- Inherits all of Rust's safety guarantees through rustc +- Generates idiomatic Rust code with proper naming conventions + +## Features Implemented + +### 1. Function Definitions +W syntax with type annotations transpiles to Rust functions: + +**W Code:** +```wolfram +Square[x: int] := x * x +``` + +**Generated Rust:** +```rust +fn square(x: i32) -> i32 { + (x * x) +} +``` + +### 2. Function Calls +Built-in `Print` function maps to Rust's `println!`: + +**W Code:** +```wolfram +Print["Hello, World!"] +``` + +**Generated Rust:** +```rust +fn main() { + println!("{}", "Hello, World!".to_string()); +} +``` + +### 3. Lists → Vec +Lists automatically convert to Rust vectors: + +**W Code:** +```wolfram +Print[[1, 2, 3, 4, 5]] +``` + +**Generated Rust:** +```rust +fn main() { + println!("{:?}", vec![1, 2, 3, 4, 5]); +} +``` + +### 4. Arithmetic Operations +Binary operations with proper precedence: + +**W Code:** +```wolfram +Print[2 + 3 * 4] +``` + +**Generated Rust:** +```rust +fn main() { + println!("{}", ((2 + 3) * 4)); +} +``` + +### 5. Multiple Arguments +Functions with multiple arguments: + +**W Code:** +```wolfram +Print["The", "answer", "is", 42] +``` + +**Generated Rust:** +```rust +fn main() { + println!("{} {} {} {}", "The".to_string(), "answer".to_string(), "is".to_string(), 42); +} +``` + +### 6. Nested Function Calls +Functions can be composed: + +**W Code:** +```wolfram +Print[Square[5]] +``` + +**Generated Rust:** +```rust +fn main() { + println!("{}", square(5)); +} +``` + +## Type System + +### Rust-like Defaults +Following Rust's conventions: +- Integer literals default to `i32` (not i64) +- Float literals default to `f64` +- Backward compatible: `int` → `i32`, `float` → `f64` + +### Complete Type Mapping + +| W Type | Rust Type | Description | +|--------|-----------|-------------| +| **Signed Integers** | | | +| `Int8` | `i8` | 8-bit signed integer | +| `Int16` | `i16` | 16-bit signed integer | +| `Int32` / `int` | `i32` | 32-bit signed (default) | +| `Int64` | `i64` | 64-bit signed integer | +| `Int128` | `i128` | 128-bit signed integer | +| `Int` | `isize` | Pointer-sized signed | +| **Unsigned Integers** | | | +| `UInt8` | `u8` | 8-bit unsigned integer | +| `UInt16` | `u16` | 16-bit unsigned integer | +| `UInt32` | `u32` | 32-bit unsigned integer | +| `UInt64` | `u64` | 64-bit unsigned integer | +| `UInt128` | `u128` | 128-bit unsigned integer | +| `UInt` | `usize` | Pointer-sized unsigned | +| **Floating Point** | | | +| `Float32` | `f32` | 32-bit float | +| `Float64` / `float` | `f64` | 64-bit float (default) | +| **Other Primitives** | | | +| `Bool` / `bool` | `bool` | Boolean | +| `Char` / `char` | `char` | Unicode scalar | +| `String` / `string` | `String` | Owned string | +| **Container Types** | | | +| `List[T]` | `Vec` | Dynamic array | +| `Array[T, N]` | `[T; N]` | Fixed-size array | +| `Slice[T]` | `&[T]` | Slice reference | +| `Map[K,V]` | `HashMap` | Hash map | +| `HashSet[T]` | `HashSet` | Hash set | +| `BTreeMap[K,V]` | `BTreeMap` | Sorted map | +| `BTreeSet[T]` | `BTreeSet` | Sorted set | + +### Examples + +**Primitive Types:** +```wolfram +AddBytes[a: UInt8, b: UInt8] := a + b +BigNum[x: Int64] := x * 2 +Precision[x: Float32] := x + 1.5 +``` + +**Container Types:** +```wolfram +ProcessList[items: List[Int32]] := items (* Vec *) +FixedBuffer[arr: Array[UInt8, 256]] := arr (* [u8; 256] *) +ReadSlice[data: Slice[UInt8]] := data (* &[u8] *) +UniqueWords[words: HashSet[String]] := words (* HashSet *) +SortedIndex[idx: BTreeMap[Int32, String]] := idx (* BTreeMap *) +OrderedSet[nums: BTreeSet[Int64]] := nums (* BTreeSet *) +``` + +**Backward Compatible (lowercase):** +```wolfram +Square[x: int] := x * x (* int → i32 *) +Average[x: float] := x / 2 (* float → f64 *) +``` + +## Naming Conventions + +The transpiler follows Rust conventions: +- PascalCase function names → snake_case (e.g., `Square` → `square`) +- Type annotations preserved and mapped to Rust types + +## Usage + +```bash +cargo build +./target/debug/w +# Generates generated.rs and compiles to ./output +./output +``` + +## Examples + +See the `examples/` directory for more demonstrations: +- `hello_world.w` - Basic printing +- `arithmetic.w` - Mathematical operations +- `list_example.w` - List/Vec usage +- `function_def.w` - Function definitions +- `multiple_args.w` - Multiple function arguments + +## Architecture + +1. **Lexer** (`lexer.rs`): Tokenizes W source code +2. **Parser** (`parser.rs`): Builds AST from tokens with lookahead for disambiguation +3. **Code Generator** (`rust_codegen.rs`): Translates AST to Rust source +4. **Compiler** (`main.rs`): Coordinates the pipeline and invokes `rustc` + +## Key Implementation Details + +- **Parser lookahead**: Uses `peek_token()` to distinguish function calls from identifiers +- **Type inference**: Infers return types from function bodies +- **Debug formatting**: Automatically uses `{:?}` for types without `Display` trait +- **Expression vs Statement**: Properly generates Rust expressions without trailing semicolons + +## Future Enhancements + +Potential additions following the REVISED_STRATEGY.md vision: +- Pattern matching (`Match` expressions) +- Module system +- Ownership annotations (borrows, moves) +- Iterator/map operations +- Result/Option types for error handling +- Async/await support diff --git a/compiler/examples/all_containers.w b/compiler/examples/all_containers.w new file mode 100644 index 0000000..f916798 --- /dev/null +++ b/compiler/examples/all_containers.w @@ -0,0 +1,25 @@ +(* + This file demonstrates all container type annotations. + The function signatures show the W→Rust type mappings. +*) + +(* Vec *) +UseList[items: List[Int32]] := items + +(* [i32; 10] - fixed-size array *) +UseArray[buffer: Array[Int32, 10]] := buffer + +(* &[u8] - slice reference *) +UseSlice[data: Slice[UInt8]] := data + +(* HashMap *) +UseHashMap[mapping: Map[String, Int32]] := mapping + +(* HashSet *) +UseHashSet[unique: HashSet[String]] := unique + +(* BTreeMap - sorted map *) +UseBTreeMap[sorted: BTreeMap[Int32, String]] := sorted + +(* BTreeSet - sorted set *) +UseBTreeSet[ordered: BTreeSet[Int64]] := ordered diff --git a/compiler/examples/arithmetic.w b/compiler/examples/arithmetic.w new file mode 100644 index 0000000..b247322 --- /dev/null +++ b/compiler/examples/arithmetic.w @@ -0,0 +1 @@ +Print[2 + 3 * 4] diff --git a/compiler/examples/comprehensive_types.w b/compiler/examples/comprehensive_types.w new file mode 100644 index 0000000..8fe12a4 --- /dev/null +++ b/compiler/examples/comprehensive_types.w @@ -0,0 +1 @@ +Print["Testing Int8:", 127] diff --git a/compiler/examples/containers_array.w b/compiler/examples/containers_array.w new file mode 100644 index 0000000..e752085 --- /dev/null +++ b/compiler/examples/containers_array.w @@ -0,0 +1 @@ +FixedArray[arr: Array[Int32, 5]] := arr diff --git a/compiler/examples/containers_btreemap.w b/compiler/examples/containers_btreemap.w new file mode 100644 index 0000000..2b7c6fb --- /dev/null +++ b/compiler/examples/containers_btreemap.w @@ -0,0 +1 @@ +SortedMap[data: BTreeMap[Int32, String]] := data diff --git a/compiler/examples/containers_btreeset.w b/compiler/examples/containers_btreeset.w new file mode 100644 index 0000000..da09f85 --- /dev/null +++ b/compiler/examples/containers_btreeset.w @@ -0,0 +1 @@ +SortedSet[items: BTreeSet[Int64]] := items diff --git a/compiler/examples/containers_demo.w b/compiler/examples/containers_demo.w new file mode 100644 index 0000000..7f8f126 --- /dev/null +++ b/compiler/examples/containers_demo.w @@ -0,0 +1 @@ +Print["Testing container type annotations"] diff --git a/compiler/examples/containers_hashset.w b/compiler/examples/containers_hashset.w new file mode 100644 index 0000000..56244d9 --- /dev/null +++ b/compiler/examples/containers_hashset.w @@ -0,0 +1 @@ +UniqueItems[items: HashSet[String]] := items diff --git a/compiler/examples/containers_list.w b/compiler/examples/containers_list.w new file mode 100644 index 0000000..a97c7a3 --- /dev/null +++ b/compiler/examples/containers_list.w @@ -0,0 +1 @@ +ProcessList[items: List[Int32]] := items diff --git a/compiler/examples/containers_slice.w b/compiler/examples/containers_slice.w new file mode 100644 index 0000000..7dc064f --- /dev/null +++ b/compiler/examples/containers_slice.w @@ -0,0 +1 @@ +ProcessSlice[data: Slice[UInt8]] := data diff --git a/compiler/examples/function_def.w b/compiler/examples/function_def.w new file mode 100644 index 0000000..e65ba71 --- /dev/null +++ b/compiler/examples/function_def.w @@ -0,0 +1 @@ +Square[x: int] := x * x diff --git a/compiler/examples/function_with_call.w b/compiler/examples/function_with_call.w new file mode 100644 index 0000000..f2411c3 --- /dev/null +++ b/compiler/examples/function_with_call.w @@ -0,0 +1 @@ +Print[Square[5]] diff --git a/compiler/examples/list_example.w b/compiler/examples/list_example.w new file mode 100644 index 0000000..39108d0 --- /dev/null +++ b/compiler/examples/list_example.w @@ -0,0 +1 @@ +Print[[1, 2, 3, 4, 5]] diff --git a/compiler/examples/multiple_args.w b/compiler/examples/multiple_args.w new file mode 100644 index 0000000..be09705 --- /dev/null +++ b/compiler/examples/multiple_args.w @@ -0,0 +1 @@ +Print["The", "answer", "is", 42] diff --git a/compiler/examples/types_demo.w b/compiler/examples/types_demo.w new file mode 100644 index 0000000..ed53cbc --- /dev/null +++ b/compiler/examples/types_demo.w @@ -0,0 +1 @@ +AddBytes[a: UInt8, b: UInt8] := a + b diff --git a/compiler/examples/types_float32.w b/compiler/examples/types_float32.w new file mode 100644 index 0000000..b92f937 --- /dev/null +++ b/compiler/examples/types_float32.w @@ -0,0 +1 @@ +FloatCalc[x: Float32, y: Float32] := x + y diff --git a/compiler/examples/types_i64.w b/compiler/examples/types_i64.w new file mode 100644 index 0000000..b79e8b4 --- /dev/null +++ b/compiler/examples/types_i64.w @@ -0,0 +1 @@ +BigNumber[x: Int64] := x * 2 diff --git a/compiler/generated.rs b/compiler/generated.rs new file mode 100644 index 0000000..a1e9525 --- /dev/null +++ b/compiler/generated.rs @@ -0,0 +1,3 @@ +fn use_btree_set(ordered: std::collections::BTreeSet) { + ordered +} diff --git a/compiler/output b/compiler/output new file mode 100755 index 0000000..9e17a70 Binary files /dev/null and b/compiler/output differ diff --git a/compiler/src/ast.rs b/compiler/src/ast.rs index 3f5ae1e..7277aa4 100644 --- a/compiler/src/ast.rs +++ b/compiler/src/ast.rs @@ -7,18 +7,46 @@ pub enum LogLevel { Error, } -#[repr(u8)] #[derive(Debug, Clone, PartialEq)] #[allow(dead_code)] pub enum Type { - Int = 0, - Float = 1, - String = 2, - Bool = 3, - List(Box) = 4, - Map(Box, Box) = 5, - Function(Vec, Box) = 6, - LogLevel = 7, + // Signed integers + Int8, + Int16, + Int32, + Int64, + Int128, + Int, // isize + + // Unsigned integers + UInt8, + UInt16, + UInt32, + UInt64, + UInt128, + UInt, // usize + + // Floating point + Float32, + Float64, + + // Other primitives + Bool, + Char, + String, + + // Complex types + List(Box), // Vec + Array(Box, usize), // [T; N] - fixed size + Slice(Box), // &[T] + Map(Box, Box), // HashMap + HashSet(Box), // HashSet + BTreeMap(Box, Box), // BTreeMap + BTreeSet(Box), // BTreeSet + Function(Vec, Box), + + // Special types + LogLevel, } #[allow(dead_code)] @@ -31,7 +59,7 @@ pub struct TypeAnnotation { #[allow(dead_code)] #[derive(Debug)] pub enum Expression { - Number(i64), + Number(i32), // Default to i32 like Rust Float(f64), String(String), Boolean(bool), diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs index 3ed8eea..813dea1 100644 --- a/compiler/src/lexer.rs +++ b/compiler/src/lexer.rs @@ -35,9 +35,9 @@ pub enum Token { /// Define token `:=` for function definitions Define, - /// 64-bit integer literal - Number(i64), - /// 64-bit floating-point literal + /// 32-bit integer literal (Rust's default) + Number(i32), + /// 64-bit floating-point literal (Rust's default) Float(f64), /// String literal String(String), @@ -112,6 +112,20 @@ impl Lexer { } } + /// Peek at the next token without consuming it + /// + /// # Returns + /// - `Some(Token)` if a valid token is found + /// - `None` if no more tokens are available + pub fn peek_token(&self) -> Option { + // Create a temporary clone to peek ahead + let mut temp_lexer = Lexer { + input: self.input.clone(), + position: self.position, + }; + temp_lexer.next_token() + } + /// Generates the next token from the input stream. /// /// # Returns @@ -154,7 +168,13 @@ impl Lexer { } ':' => { self.position += 1; - Some(Token::Colon) + // Check for := + if self.position < self.input.len() && self.input[self.position] == '=' { + self.position += 1; + Some(Token::Define) + } else { + Some(Token::Colon) + } } ',' => { self.position += 1; @@ -180,6 +200,36 @@ impl Lexer { self.position += 1; Some(Token::Power) } + '=' => { + self.position += 1; + // Check for == + if self.position < self.input.len() && self.input[self.position] == '=' { + self.position += 1; + Some(Token::Equals) + } else { + // Single = is not a token in this language + None + } + } + '!' => { + self.position += 1; + // Check for != + if self.position < self.input.len() && self.input[self.position] == '=' { + self.position += 1; + Some(Token::NotEquals) + } else { + // Single ! is not a token in this language + None + } + } + '<' => { + self.position += 1; + Some(Token::LessThan) + } + '>' => { + self.position += 1; + Some(Token::GreaterThan) + } '"' => { // Handle string literals Some(Token::String(self.read_string())) @@ -217,17 +267,19 @@ impl Lexer { fn read_identifier(&mut self) -> String { let mut identifier = String::new(); - while self.position < self.input.len() && - self.input[self.position].is_alphabetic() { + while self.position < self.input.len() && + (self.input[self.position].is_alphabetic() || + self.input[self.position].is_digit(10) || + self.input[self.position] == '_') { identifier.push(self.input[self.position]); self.position += 1; } identifier } - fn read_number(&mut self) -> i64 { + fn read_number(&mut self) -> i32 { let mut number = String::new(); - while self.position < self.input.len() && + while self.position < self.input.len() && self.input[self.position].is_digit(10) { number.push(self.input[self.position]); self.position += 1; diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 52f9060..bd08fa9 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -30,7 +30,7 @@ fn main() { let mut parser = parser::Parser::new(input); let expr = parser.parse_expression().expect("Failed to parse expression"); - + // Use Rust code generation instead of assembly let mut rust_codegen = rust_codegen::RustCodeGenerator::new(); let rust_code = rust_codegen.generate(&expr).expect("Failed to generate Rust code"); diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs index bb70657..a6cf14a 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/parser.rs @@ -10,6 +10,12 @@ use crate::ast::{Expression, Operator, Type, TypeAnnotation, LogLevel}; use crate::lexer::{Lexer, Token}; +/// Helper enum to distinguish between function arguments and parameters during parsing +enum ArgumentOrParameter { + Expression(Expression), + Parameter(TypeAnnotation), +} + /// Represents the parser state, holding a lexer and the current token being processed. /// /// The parser maintains the context needed to parse a sequence of tokens into an Abstract Syntax Tree. @@ -65,22 +71,36 @@ impl Parser { /// # Returns /// An optional Expression representing the parsed input, or None if parsing fails pub fn parse_expression(&mut self) -> Option { - // Try parsing function definition first - if let Some(func_def) = self.parse_function_definition() { - return Some(func_def); - } + // Check if this might be a function (call or definition) + // by looking for Identifier followed by [ + if let Some(Token::Identifier(id)) = &self.current_token { + // Special handling for Cond - don't treat it as a regular function call + if id == "Cond" { + self.advance(); + return self.parse_cond_expression(); + } + + // Peek ahead to check if next token is LeftBracket + // We need to check this to avoid consuming tokens unnecessarily + let is_function_syntax = self.lexer.peek_token() + .map(|t| matches!(t, Token::LeftBracket)) + .unwrap_or(false); - // Then try function call - if let Some(func_call) = self.parse_function_call() { - return Some(func_call); + if is_function_syntax { + // Try to parse as function call or definition + if let Some(func_or_call) = self.parse_function_or_call() { + return Some(func_or_call); + } + } } - // Then try binary operations + // Try binary operations self.parse_binary_operation() } - fn parse_function_definition(&mut self) -> Option { - // Check for function definition syntax: f[x, y] := body + /// Parse either a function definition or function call + fn parse_function_or_call(&mut self) -> Option { + // Get the identifier let name = match &self.current_token { Some(Token::Identifier(id)) => id.clone(), _ => return None, @@ -93,106 +113,102 @@ impl Parser { _ => return None, } - // Parse parameters with optional type annotations - let mut parameters = Vec::new(); - while self.current_token.is_some() { - let param_name = match &self.current_token { - Some(Token::Identifier(name)) => name.clone(), - Some(Token::RightBracket) => break, - _ => return None, - }; - self.advance(); - - // Check for type annotation - let param_type = match self.current_token { - Some(Token::Colon) => { + // Parse the contents of the brackets (could be parameters or arguments) + // We'll determine whether this is a function definition or call + // by checking for := after the closing bracket + let mut items = Vec::new(); + loop { + match &self.current_token { + Some(Token::RightBracket) => { self.advance(); - match self.parse_type() { - Some(t) => t, - None => Type::Int, // Default type if not specified + break; + } + Some(Token::Comma) => { + self.advance(); + } + _ => { + if let Some(item) = self.parse_argument_or_parameter() { + items.push(item); + } else { + return None; } - }, - _ => Type::Int, // Default type if not specified - }; - - parameters.push(TypeAnnotation { - name: param_name, - type_: param_type, - }); - - // Handle comma or end of parameter list - match self.current_token { - Some(Token::Comma) => self.advance(), - Some(Token::RightBracket) => break, - _ => return None, + } } } - - // Consume right bracket - match self.current_token { - Some(Token::RightBracket) => self.advance(), - _ => return None, - } - - // Expect define token - match self.current_token { - Some(Token::Define) => self.advance(), - _ => return None, - } - - // Parse function body - let body = match self.parse_expression() { - Some(expr) => Box::new(expr), - None => return None, - }; - - Some(Expression::FunctionDefinition { - name, - parameters, - body, - }) - } - fn parse_function_call(&mut self) -> Option { - // Function call syntax: Function[arg1, arg2, ...] - let function = match &self.current_token { - Some(Token::Identifier(_)) | Some(Token::LeftBracket) => - Box::new(self.parse_expression()?), - _ => return None, - }; + // Now check if next token is := + match &self.current_token { + Some(Token::Define) => { + // It's a function definition + self.advance(); - // Expect left bracket for arguments - match self.current_token { - Some(Token::LeftBracket) => self.advance(), - _ => return None, + // Convert items to parameters + let parameters: Vec = items.into_iter() + .filter_map(|item| { + if let ArgumentOrParameter::Parameter(p) = item { + Some(p) + } else { + None + } + }) + .collect(); + + // Parse body + let body = Box::new(self.parse_expression()?); + + Some(Expression::FunctionDefinition { + name, + parameters, + body, + }) + } + _ => { + // It's a function call + let arguments: Vec = items.into_iter() + .filter_map(|item| { + match item { + ArgumentOrParameter::Expression(e) => Some(e), + ArgumentOrParameter::Parameter(_) => None, + } + }) + .collect(); + + Some(Expression::FunctionCall { + function: Box::new(Expression::Identifier(name)), + arguments, + }) + } } + } - // Parse arguments (now allowing multiple) - let mut arguments = Vec::new(); - while let Some(token) = &self.current_token { - match token { - Token::RightBracket => break, - _ => { - let arg = self.parse_expression()?; - arguments.push(arg); - - // Handle comma between arguments - match self.current_token { - Some(Token::Comma) => self.advance(), - Some(Token::RightBracket) => break, - _ => return None, - } + fn parse_argument_or_parameter(&mut self) -> Option { + // Try to parse as parameter (identifier with optional type) + if let Some(Token::Identifier(name)) = &self.current_token { + // Peek ahead to see if this is a type annotation + let next_is_colon = self.lexer.peek_token() + .map(|t| matches!(t, Token::Colon)) + .unwrap_or(false); + + if next_is_colon { + // Parse as parameter with type annotation + let param_name = name.clone(); + self.advance(); // consume identifier + self.advance(); // consume colon + + if let Some(ty) = self.parse_type() { + return Some(ArgumentOrParameter::Parameter(TypeAnnotation { + name: param_name, + type_: ty, + })); } } } - self.advance(); // Consume right bracket - Some(Expression::FunctionCall { - function, - arguments, - }) + // Parse as general expression (handles identifiers, function calls, etc.) + self.parse_expression().map(ArgumentOrParameter::Expression) } + fn parse_binary_operation(&mut self) -> Option { let mut left = self.parse_primary()?; @@ -203,6 +219,10 @@ impl Parser { Token::Multiply => Operator::Multiply, Token::Divide => Operator::Divide, Token::Power => Operator::Power, + Token::Equals => Operator::Equals, + Token::NotEquals => Operator::NotEquals, + Token::LessThan => Operator::LessThan, + Token::GreaterThan => Operator::GreaterThan, _ => break, }; @@ -302,24 +322,28 @@ impl Parser { Token::LeftBracket => { self.advance(); // Consume left bracket of condition pair - // Parse condition - let condition = self.parse_expression()?; + // Parse first expression + let first_expr = self.parse_expression()?; - // Parse statements for this condition - let statements = self.parse_expression()?; + // Try to parse second expression (if it exists, this is a condition-statement pair) + // If there's a RightBracket next, this is a default statement + let is_default = matches!(self.current_token, Some(Token::RightBracket)); - // Consume right bracket of condition pair - match self.current_token { - Some(Token::RightBracket) => self.advance(), - _ => return None, - } - - // If this is the last condition and no statements parsed yet, - // treat it as default statements - if conditions.is_empty() && default_statements.is_none() { - default_statements = Some(Box::new(statements)); + if is_default { + // This bracket contains only one expression - it's the default + self.advance(); // Consume right bracket + default_statements = Some(Box::new(first_expr)); } else { - conditions.push((condition, statements)); + // Parse the second expression (statements for this condition) + let statements = self.parse_expression()?; + + // Consume right bracket of condition pair + match self.current_token { + Some(Token::RightBracket) => self.advance(), + _ => return None, + } + + conditions.push((first_expr, statements)); } } _ => return None, @@ -432,29 +456,141 @@ impl Parser { } /// Parses a type annotation from the current token. - /// - /// This method recognizes basic type identifiers like "int", "float", "string", and "bool". - /// + /// + /// Recognizes all Rust primitive types and generic container types: + /// - Primitives: Int8-128, UInt8-128, Float32/64, Bool, Char, String + /// - Containers: List[T], Array[T, N], Slice[T], Map[K,V], HashSet[T], BTreeMap[K,V], BTreeSet[T] + /// /// # Returns /// - `Some(Type)` if a valid type is found /// - `None` if the current token is not a recognized type identifier fn parse_type(&mut self) -> Option { match &self.current_token { Some(Token::Identifier(id)) => { - let type_ = match id.as_str() { - "int" => Type::Int, - "float" => Type::Float, + let type_name = id.clone(); + self.advance(); + + // Check if this is a generic type (followed by [) + if matches!(self.current_token, Some(Token::LeftBracket)) { + return self.parse_generic_type(&type_name); + } + + // Otherwise it's a primitive type + let type_ = match type_name.as_str() { + // Signed integers + "Int8" => Type::Int8, + "Int16" => Type::Int16, + "Int32" => Type::Int32, + "Int64" => Type::Int64, + "Int128" => Type::Int128, + "Int" => Type::Int, + + // Unsigned integers + "UInt8" => Type::UInt8, + "UInt16" => Type::UInt16, + "UInt32" => Type::UInt32, + "UInt64" => Type::UInt64, + "UInt128" => Type::UInt128, + "UInt" => Type::UInt, + + // Floating point + "Float32" => Type::Float32, + "Float64" => Type::Float64, + + // Other primitives + "Bool" => Type::Bool, + "Char" => Type::Char, + "String" => Type::String, + + // Backward compatible (lowercase) + "int" => Type::Int32, + "float" => Type::Float64, "string" => Type::String, "bool" => Type::Bool, + "char" => Type::Char, + _ => return None, }; - self.advance(); Some(type_) } _ => None, } } + /// Parse generic type syntax like List[Int32], Array[Int32, 10], Map[String, Int32] + fn parse_generic_type(&mut self, type_name: &str) -> Option { + // Consume the left bracket + self.advance(); + + match type_name { + "List" => { + let inner = Box::new(self.parse_type()?); + self.expect_token(Token::RightBracket)?; + Some(Type::List(inner)) + } + "Array" => { + // Array[T, N] where T is a type and N is a number + let inner = Box::new(self.parse_type()?); + self.expect_token(Token::Comma)?; + + // Parse the size as a number + let size = match &self.current_token { + Some(Token::Number(n)) => { + let size = *n as usize; + self.advance(); + size + } + _ => return None, + }; + + self.expect_token(Token::RightBracket)?; + Some(Type::Array(inner, size)) + } + "Slice" => { + let inner = Box::new(self.parse_type()?); + self.expect_token(Token::RightBracket)?; + Some(Type::Slice(inner)) + } + "HashSet" => { + let inner = Box::new(self.parse_type()?); + self.expect_token(Token::RightBracket)?; + Some(Type::HashSet(inner)) + } + "BTreeSet" => { + let inner = Box::new(self.parse_type()?); + self.expect_token(Token::RightBracket)?; + Some(Type::BTreeSet(inner)) + } + "Map" => { + // Map[K, V] + let key = Box::new(self.parse_type()?); + self.expect_token(Token::Comma)?; + let value = Box::new(self.parse_type()?); + self.expect_token(Token::RightBracket)?; + Some(Type::Map(key, value)) + } + "BTreeMap" => { + // BTreeMap[K, V] + let key = Box::new(self.parse_type()?); + self.expect_token(Token::Comma)?; + let value = Box::new(self.parse_type()?); + self.expect_token(Token::RightBracket)?; + Some(Type::BTreeMap(key, value)) + } + _ => None, + } + } + + /// Helper to expect and consume a specific token + fn expect_token(&mut self, expected: Token) -> Option<()> { + if self.current_token == Some(expected) { + self.advance(); + Some(()) + } else { + None + } + } + /// Advances the parser to the next token in the input stream. /// /// This method updates the current_token by requesting the next token from the lexer. diff --git a/compiler/src/rust_codegen.rs b/compiler/src/rust_codegen.rs index c0d5aab..1b6ef2c 100644 --- a/compiler/src/rust_codegen.rs +++ b/compiler/src/rust_codegen.rs @@ -1,13 +1,15 @@ //! Rust Code Generation Module -//! +//! //! Translates the W language AST into idiomatic Rust source code -use crate::ast::{Expression, Operator, LogLevel}; +use crate::ast::{Expression, Operator, LogLevel, Type, TypeAnnotation}; use std::fmt::Write; pub struct RustCodeGenerator { output: String, indent_level: usize, + /// Track if we're inside a function definition (to avoid wrapping in main) + in_function: bool, } impl RustCodeGenerator { @@ -15,6 +17,7 @@ impl RustCodeGenerator { RustCodeGenerator { output: String::new(), indent_level: 0, + in_function: false, } } @@ -27,145 +30,394 @@ impl RustCodeGenerator { self.output.clear(); self.indent_level = 0; - // Start with a main function - writeln!(self.output, "fn main() {{")?; + // Check if this is a function definition or needs to be wrapped in main + match expr { + Expression::FunctionDefinition { .. } => { + // Generate function definition directly + self.generate_top_level_item(expr)?; + } + _ => { + // Wrap in main function + writeln!(self.output, "fn main() {{")?; + self.indent_level += 1; + self.generate_statement(expr)?; + self.indent_level -= 1; + writeln!(self.output, "}}")?; + } + } + + Ok(self.output.clone()) + } + + /// Generate top-level items (functions, imports, etc.) + fn generate_top_level_item(&mut self, expr: &Expression) -> Result<(), std::fmt::Error> { + match expr { + Expression::FunctionDefinition { name, parameters, body } => { + self.generate_function_definition(name, parameters, body)?; + } + _ => { + // For non-function top-level items, generate as statement + self.generate_statement(expr)?; + } + } + Ok(()) + } + + /// Generate a function definition + fn generate_function_definition( + &mut self, + name: &str, + parameters: &[TypeAnnotation], + body: &Expression, + ) -> Result<(), std::fmt::Error> { + // Convert function name to snake_case (Rust convention) + let rust_name = to_snake_case(name); + + write!(self.output, "{}fn {}(", self.indent(), rust_name)?; + + // Generate parameters + for (i, param) in parameters.iter().enumerate() { + if i > 0 { + write!(self.output, ", ")?; + } + let param_name = to_snake_case(¶m.name); + let param_type = self.type_to_rust(¶m.type_); + write!(self.output, "{}: {}", param_name, param_type)?; + } + + write!(self.output, ")")?; + + // Infer return type from body + let return_type = self.infer_return_type(body); + if return_type != "()" { + write!(self.output, " -> {}", return_type)?; + } + + writeln!(self.output, " {{")?; self.indent_level += 1; + self.in_function = true; - // Generate the main expression - self.generate_expression(expr)?; + // Generate function body as an expression (no trailing semicolon for return) + let body_code = self.generate_expression_value(body)?; + // Write without newline from writeln to keep it as an expression + write!(self.output, "{}{}\n", self.indent(), body_code)?; + self.in_function = false; self.indent_level -= 1; - writeln!(self.output, "}}")?; + writeln!(self.output, "{}}}", self.indent())?; - Ok(self.output.clone()) + Ok(()) } - fn generate_expression(&mut self, expr: &Expression) -> Result<(), std::fmt::Error> { - match expr { - Expression::Number(n) => { - write!(self.output, "{}println!(\"{}\");", self.indent(), n)?; - } - Expression::String(s) => { - write!(self.output, "{}println!(\"{}\");", self.indent(), s)?; + /// Convert W type to Rust type + fn type_to_rust(&self, ty: &Type) -> String { + match ty { + // Signed integers + Type::Int8 => "i8".to_string(), + Type::Int16 => "i16".to_string(), + Type::Int32 => "i32".to_string(), + Type::Int64 => "i64".to_string(), + Type::Int128 => "i128".to_string(), + Type::Int => "isize".to_string(), + + // Unsigned integers + Type::UInt8 => "u8".to_string(), + Type::UInt16 => "u16".to_string(), + Type::UInt32 => "u32".to_string(), + Type::UInt64 => "u64".to_string(), + Type::UInt128 => "u128".to_string(), + Type::UInt => "usize".to_string(), + + // Floating point + Type::Float32 => "f32".to_string(), + Type::Float64 => "f64".to_string(), + + // Other primitives + Type::Bool => "bool".to_string(), + Type::Char => "char".to_string(), + Type::String => "String".to_string(), + + // Complex types + Type::List(inner) => format!("Vec<{}>", self.type_to_rust(inner)), + Type::Array(inner, size) => format!("[{}; {}]", self.type_to_rust(inner), size), + Type::Slice(inner) => format!("&[{}]", self.type_to_rust(inner)), + Type::Map(key, value) => { + format!("std::collections::HashMap<{}, {}>", + self.type_to_rust(key), + self.type_to_rust(value)) } - Expression::LogCall { level, message } => { - let log_macro = match level { - LogLevel::Debug => "debug!", - LogLevel::Info => "info!", - LogLevel::Warn => "warn!", - LogLevel::Error => "error!", - }; - - // Generate message - let message_str = self.generate_log_message(message)?; - write!(self.output, "{}{}({});", self.indent(), log_macro, message_str)?; + Type::HashSet(inner) => format!("std::collections::HashSet<{}>", self.type_to_rust(inner)), + Type::BTreeMap(key, value) => { + format!("std::collections::BTreeMap<{}, {}>", + self.type_to_rust(key), + self.type_to_rust(value)) } - Expression::BinaryOp { left, operator, right } => { - let result_val = self.generate_expression_as_value(expr)?; - write!(self.output, "{}println!(\"{}\");", self.indent(), result_val)?; + Type::BTreeSet(inner) => format!("std::collections::BTreeSet<{}>", self.type_to_rust(inner)), + Type::Function(params, ret) => { + let param_types: Vec = params.iter() + .map(|p| self.type_to_rust(p)) + .collect(); + format!("fn({}) -> {}", param_types.join(", "), self.type_to_rust(ret)) } + + // Special types + Type::LogLevel => "LogLevel".to_string(), + } + } + + /// Infer return type from expression (simplified version) + fn infer_return_type(&self, expr: &Expression) -> String { + match expr { + Expression::Number(_) => "i32".to_string(), // Default to i32 like Rust + Expression::Float(_) => "f64".to_string(), + Expression::String(_) => "String".to_string(), + Expression::Boolean(_) => "bool".to_string(), + Expression::List(_) => "Vec".to_string(), // Simplified + Expression::Map(_) => "HashMap".to_string(), // Simplified + Expression::BinaryOp { .. } => "i32".to_string(), // Simplified - default to i32 + _ => "()".to_string(), + } + } + + /// Generate a statement (expression with side effects, like println or assignments) + fn generate_statement(&mut self, expr: &Expression) -> Result<(), std::fmt::Error> { + match expr { Expression::FunctionCall { function, arguments } => { match function.as_ref() { Expression::Identifier(name) if name == "Print" => { - // Generate print call that converts all arguments to strings - let print_args: Result, std::fmt::Error> = arguments - .iter() - .map(|arg| self.generate_print_argument(arg)) - .collect(); - - let print_args = print_args?; - write!( - self.output, - "{}println!(\"{}\");", - self.indent(), - print_args.join(" ") - )?; + // Generate print call + write!(self.output, "{}println!(", self.indent())?; + + // Generate format string with appropriate formatters + if !arguments.is_empty() { + let format_parts: Vec = arguments.iter() + .map(|arg| { + // Use {:?} for complex types that don't implement Display + match arg { + Expression::List(_) | Expression::Map(_) => "{:?}".to_string(), + _ => "{}".to_string(), + } + }) + .collect(); + write!(self.output, "\"{}\"", format_parts.join(" "))?; + + // Add arguments + for arg in arguments { + write!(self.output, ", ")?; + let arg_val = self.generate_expression_value(arg)?; + write!(self.output, "{}", arg_val)?; + } + } + + writeln!(self.output, ");")?; } _ => { - write!(self.output, "{}// Unsupported function call", self.indent())?; + // Generic function call + let call_expr = self.generate_expression_value(expr)?; + writeln!(self.output, "{}{};", self.indent(), call_expr)?; } } } - Expression::Cond { conditions, default_statements } => { - // Generate Rust equivalent of Cond expression - write!(self.output, "{}{{", self.indent())?; + _ => { + // For other expressions, generate as value and discard + let value = self.generate_expression_value(expr)?; + writeln!(self.output, "{}{};", self.indent(), value)?; + } + } + Ok(()) + } + + /// Generate an expression that returns a value (not a statement) + fn generate_expression_value(&mut self, expr: &Expression) -> Result { + match expr { + Expression::Number(n) => Ok(n.to_string()), + + Expression::Float(f) => Ok(f.to_string()), + + Expression::String(s) => Ok(format!("\"{}\".to_string()", s)), + + Expression::Boolean(b) => Ok(b.to_string()), + + Expression::Identifier(name) => { + // Convert to snake_case + Ok(to_snake_case(name)) + } + + Expression::List(elements) => { + // Generate vec![...] + let mut result = String::from("vec!["); + for (i, elem) in elements.iter().enumerate() { + if i > 0 { + result.push_str(", "); + } + result.push_str(&self.generate_expression_value(elem)?); + } + result.push(']'); + Ok(result) + } + + Expression::Map(entries) => { + // Generate HashMap initialization + let mut result = String::from("{\n"); self.indent_level += 1; + result.push_str(&format!("{}let mut map = std::collections::HashMap::new();\n", self.indent())); + + for (key, value) in entries { + let key_val = self.generate_expression_value(key)?; + let value_val = self.generate_expression_value(value)?; + result.push_str(&format!("{}map.insert({}, {});\n", self.indent(), key_val, value_val)); + } + + result.push_str(&format!("{}map\n", self.indent())); + self.indent_level -= 1; + result.push_str(&format!("{}}}", self.indent())); + Ok(result) + } + + Expression::BinaryOp { left, operator, right } => { + let left_val = self.generate_expression_value(left)?; + let right_val = self.generate_expression_value(right)?; + + match operator { + Operator::Add => Ok(format!("({} + {})", left_val, right_val)), + Operator::Subtract => Ok(format!("({} - {})", left_val, right_val)), + Operator::Multiply => Ok(format!("({} * {})", left_val, right_val)), + Operator::Divide => Ok(format!("({} / {})", left_val, right_val)), + Operator::Power => { + // Use pow for integer exponentiation + Ok(format!("({}.pow({} as u32))", left_val, right_val)) + } + Operator::Equals => Ok(format!("({} == {})", left_val, right_val)), + Operator::NotEquals => Ok(format!("({} != {})", left_val, right_val)), + Operator::LessThan => Ok(format!("({} < {})", left_val, right_val)), + Operator::GreaterThan => Ok(format!("({} > {})", left_val, right_val)), + } + } + + Expression::FunctionCall { function, arguments } => { + match function.as_ref() { + Expression::Identifier(name) => { + // Check for built-in functions + match name.as_str() { + "Print" => { + // Print returns (), so we generate a block + let mut result = String::from("{\n"); + self.indent_level += 1; + + write!(&mut result, "{}println!(", self.indent())?; + if !arguments.is_empty() { + let format_parts: Vec = arguments.iter() + .map(|arg| { + match arg { + Expression::List(_) | Expression::Map(_) => "{:?}".to_string(), + _ => "{}".to_string(), + } + }) + .collect(); + write!(&mut result, "\"{}\"", format_parts.join(" "))?; + + for arg in arguments { + write!(&mut result, ", ")?; + let arg_val = self.generate_expression_value(arg)?; + write!(&mut result, "{}", arg_val)?; + } + } + write!(&mut result, ");\n")?; + + self.indent_level -= 1; + result.push_str(&format!("{}}}", self.indent())); + Ok(result) + } + _ => { + // Generic function call + let func_name = to_snake_case(name); + let mut result = format!("{}(", func_name); + + for (i, arg) in arguments.iter().enumerate() { + if i > 0 { + result.push_str(", "); + } + result.push_str(&self.generate_expression_value(arg)?); + } + + result.push(')'); + Ok(result) + } + } + } + _ => Ok("/* unsupported function call */".to_string()), + } + } + + Expression::Cond { conditions, default_statements } => { + // Generate if-else chain + let mut result = String::new(); + + for (i, (condition, statements)) in conditions.iter().enumerate() { + if i > 0 { + result.push_str(" else "); + } + + let cond_val = self.generate_expression_value(condition)?; + write!(&mut result, "if {} {{\n", cond_val)?; - // Generate condition checks - for (condition, statements) in conditions { - let condition_val = self.generate_expression_as_value(condition)?; - write!( - self.output, - "\n{}if {} {{", - self.indent(), - condition_val - )?; - self.indent_level += 1; - self.generate_expression(statements)?; + let stmt_val = self.generate_expression_value(statements)?; + write!(&mut result, "{}{}\n", self.indent(), stmt_val)?; self.indent_level -= 1; - - write!(self.output, "\n{}break;", self.indent())?; - write!(self.output, "\n{}}", self.indent())?; + + write!(&mut result, "{}}}", self.indent())?; } - // Generate default statements if present + // Generate default case if present if let Some(default_expr) = default_statements { - write!(self.output, "\n{}else {{", self.indent())?; + write!(&mut result, " else {{\n")?; self.indent_level += 1; - self.generate_expression(default_expr)?; + let default_val = self.generate_expression_value(default_expr)?; + write!(&mut result, "{}{}\n", self.indent(), default_val)?; self.indent_level -= 1; - write!(self.output, "\n{}}", self.indent())?; + write!(&mut result, "{}}}", self.indent())?; } - self.indent_level -= 1; - write!(self.output, "\n{}}}", self.indent())?; + Ok(result) } - _ => { - write!(self.output, "{}// Unsupported expression", self.indent())?; + + Expression::LogCall { level, message } => { + let log_macro = match level { + LogLevel::Debug => "debug!", + LogLevel::Info => "info!", + LogLevel::Warn => "warn!", + LogLevel::Error => "error!", + }; + + let message_val = self.generate_expression_value(message)?; + Ok(format!("{}({})", log_macro, message_val)) } - } - Ok(()) - } - // New helper method to convert arguments to printable strings - fn generate_print_argument(&mut self, expr: &Expression) -> Result { - match expr { - Expression::Number(n) => Ok(n.to_string()), - Expression::String(s) => Ok(format!("{}", s)), - Expression::Boolean(b) => Ok(b.to_string()), - _ => Ok("/* unsupported print arg */".to_string()), + Expression::FunctionDefinition { .. } => { + Ok("/* function definitions not supported as values */".to_string()) + } } } +} - fn generate_expression_as_value(&mut self, expr: &Expression) -> Result { - match expr { - Expression::Number(n) => Ok(n.to_string()), - Expression::String(s) => Ok(format!("\"{}\"", s)), - Expression::BinaryOp { left, operator, right } => { - let left_val = self.generate_expression_as_value(left)?; - let right_val = self.generate_expression_as_value(right)?; - - let op_str = match operator { - Operator::Add => "+", - Operator::Subtract => "-", - Operator::Multiply => "*", - Operator::Divide => "/", - Operator::Power => "pow", - _ => "/* unsupported */", - }; - - Ok(format!("({} {} {})", left_val, op_str, right_val)) +/// Convert PascalCase or camelCase to snake_case +fn to_snake_case(s: &str) -> String { + let mut result = String::new(); + let mut prev_is_upper = false; + + for (i, c) in s.chars().enumerate() { + if c.is_uppercase() { + if i > 0 && !prev_is_upper { + result.push('_'); } - _ => Ok("/* complex expression */".to_string()), + result.push(c.to_ascii_lowercase()); + prev_is_upper = true; + } else { + result.push(c); + prev_is_upper = false; } } - fn generate_log_message(&mut self, message: &Expression) -> Result { - match message { - Expression::String(s) => Ok(format!("\"{}\"", s)), - Expression::Number(n) => Ok(n.to_string()), - _ => Ok("\"unknown message\"".to_string()), - } - } + result } diff --git a/compiler/test_parse.w b/compiler/test_parse.w new file mode 100644 index 0000000..84d8aec --- /dev/null +++ b/compiler/test_parse.w @@ -0,0 +1 @@ +Print["Hello", "World"] diff --git a/compiler/tests/arithmetic_tests.rs b/compiler/tests/arithmetic_tests.rs index fa02841..3b2c5cd 100644 --- a/compiler/tests/arithmetic_tests.rs +++ b/compiler/tests/arithmetic_tests.rs @@ -23,25 +23,25 @@ fn evaluate(input: &str) -> i64 { } match op.as_str() { - "Plus" => numbers.iter().sum(), + "Plus" => numbers.iter().map(|&n| n as i64).sum(), "Minus" => { if numbers.len() == 1 { - numbers[0] + numbers[0] as i64 } else { panic!("Minus operation requires exactly one argument") } }, "Power" => { // Add this block if numbers.len() == 2 { - numbers[0].pow(numbers[1] as u32) + (numbers[0].pow(numbers[1] as u32)) as i64 } else { panic!("Power operation requires exactly two arguments") } }, - "Multiply" => numbers.iter().product(), + "Multiply" => numbers.iter().map(|&n| n as i64).product(), "Divide" => { if numbers.len() == 2 { - numbers[0] / numbers[1] + (numbers[0] / numbers[1]) as i64 } else { panic!("Divide operation requires exactly two arguments") } diff --git a/compiler/tests/parser_tests.rs b/compiler/tests/parser_tests.rs index 7faed88..338a5fe 100644 --- a/compiler/tests/parser_tests.rs +++ b/compiler/tests/parser_tests.rs @@ -91,29 +91,41 @@ mod tests { #[test] fn test_cond_single_condition() { let mut parser = Parser::new("Cond[[x > 10 Print[\"Greater than 10\"]]]".to_string()); - let expr = parser.parse().unwrap(); - + let expr = parser.parse_expression().unwrap(); + match expr { Expression::Cond { conditions, default_statements } => { - assert_eq!(conditions.len(), 0); - assert!(default_statements.is_some()); - - match *default_statements.unwrap() { - Expression::FunctionCall { - function: func, - arguments - } => { - match *func { - Expression::Identifier(name) => assert_eq!(name, "Print"), - _ => panic!("Expected Print function"), + assert_eq!(conditions.len(), 1); + assert!(default_statements.is_none()); + + // Check the condition + match &conditions[0] { + (condition, statements) => { + match condition { + Expression::BinaryOp { left, operator: _, right: _ } => { + match **left { + Expression::Identifier(ref name) => assert_eq!(name, "x"), + _ => panic!("Expected x identifier"), + } + } + _ => panic!("Expected binary operation"), } - assert_eq!(arguments.len(), 1); - match arguments[0] { - Expression::String(ref msg) => assert_eq!(msg, "Greater than 10"), - _ => panic!("Expected string argument"), + + match statements { + Expression::FunctionCall { function, arguments } => { + match **function { + Expression::Identifier(ref name) => assert_eq!(name, "Print"), + _ => panic!("Expected Print function"), + } + assert_eq!(arguments.len(), 1); + match arguments[0] { + Expression::String(ref msg) => assert_eq!(msg, "Greater than 10"), + _ => panic!("Expected string argument"), + } + } + _ => panic!("Expected function call"), } } - _ => panic!("Expected function call"), } } _ => panic!("Expected Cond expression"), @@ -123,7 +135,7 @@ mod tests { #[test] fn test_cond_multiple_conditions() { let mut parser = Parser::new("Cond[[x > 10 Print[\"Greater than 10\"]] [x < 5 Print[\"Less than 5\"]] [Print[\"Between 5 and 10\"]]]".to_string()); - let expr = parser.parse().unwrap(); + let expr = parser.parse_expression().unwrap(); match expr { Expression::Cond { conditions, default_statements } => { @@ -133,7 +145,7 @@ mod tests { match &conditions[0] { (condition, statements) => { match condition { - Expression::BinaryOp { left, operator: _, right } => { + Expression::BinaryOp { left, operator: _, right: _ } => { match **left { Expression::Identifier(ref name) => assert_eq!(name, "x"), _ => panic!("Expected x identifier"), @@ -163,7 +175,7 @@ mod tests { match &conditions[1] { (condition, statements) => { match condition { - Expression::BinaryOp { left, operator: _, right } => { + Expression::BinaryOp { left, operator: _, right: _ } => { match **left { Expression::Identifier(ref name) => assert_eq!(name, "x"), _ => panic!("Expected x identifier"), @@ -213,7 +225,7 @@ mod tests { #[test] fn test_cond_with_numeric_conditions() { let mut parser = Parser::new("Cond[[42 Print[\"The answer\"]] [0 Print[\"Zero\"]]]".to_string()); - let expr = parser.parse().unwrap(); + let expr = parser.parse_expression().unwrap(); match expr { Expression::Cond { conditions, default_statements } => {