From 566b1cb6283eb0a233759b06128d9123bf38a6cb Mon Sep 17 00:00:00 2001 From: Shariar Abdullah Nafi <177193733+sa-nafi@users.noreply.github.com> Date: Sat, 18 Jul 2026 22:55:19 +0600 Subject: [PATCH] feat: syntax overhaul for v0.2.0 --- AGENTS.md | 58 +- CHANGELOG.md | 44 +- README.md | 188 ++--- docs/VIPR1.md | 2 +- docs/VIPR2.md | 1454 +++++++++++++++++++++++++--------- src/codegen/mod.rs | 888 ++++++++++----------- src/lexer/mod.rs | 995 +++++++++++++++++------ src/lexer/token.rs | 78 +- src/main.rs | 124 +-- src/parser/ast.rs | 237 ++---- src/parser/mod.rs | 964 +++++++++++++++------- src/semantic/mod.rs | 987 ++++++++++++----------- src/semantic/symbol_table.rs | 117 +-- tests/test.vipr | 14 + tests/test_program | Bin 12616 -> 0 bytes 15 files changed, 3803 insertions(+), 2347 deletions(-) create mode 100644 tests/test.vipr delete mode 100755 tests/test_program diff --git a/AGENTS.md b/AGENTS.md index bc9b610..f28bebe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,14 +6,14 @@ Welcome to the Vipr codebase! This document provides a comprehensive guide for f ## 1. System Overview -Vipr is a statically-typed, block-scoped, ahead-of-time (AOT) compiled programming language. The compiler is written in Rust and uses a multi-pass structure that translates Vipr source code into C code, which is then compiled into native machine code using `gcc`. +Vipr (v0.2.0) is a statically-typed, indentation-scoped, ahead-of-time (AOT) compiled programming language. The compiler is written in Rust and uses a multi-pass structure that translates Vipr source code into C code, which is then compiled into native machine code using `gcc`. ### Compilation Pipeline ```mermaid graph TD A[Vipr Source Code .vipr] --> B[Lexer] - B -->|Token Stream| C[Parser] + B -->|Token Stream w/ Indents| C[Parser] C -->|Abstract Syntax Tree AST| D[Semantic Analyzer] D -->|Validated AST| E[Code Generator] E -->|C Source Code| F[Native Compiler GCC] @@ -27,29 +27,27 @@ graph TD Here is the organization of the codebase: * **[README.md]**: User-facing Vipr Language Guide, describing variables, control flow, functions, types, and loops. -* **[docs/Vipr.md]**: Official Vipr Programming Language Specification v0.1, containing the formal EBNF grammar. +* **[docs/VIPR2.md]**: Official Vipr Programming Language Specification v0.2.0, containing the formal EBNF grammar. * **[Cargo.toml]**: Cargo configuration. Notice that there are no external dependencies; the compiler is implemented using only the Rust standard library. * **`src/`**: The Rust source code of the compiler. * **[src/main.rs]**: Orchestrates the compilation phases: arguments parsing, reading files, lexical analysis, parsing, semantic checking, C code generation, GCC invocation, and cleaning up. * **`src/lexer/`**: Lexical analysis. - * **[src/lexer/token.rs]**: Defines the [`Token`] enum representing Vipr's lexicon. - * **[src/lexer/mod.rs]**: The [`Lexer`] struct that tokenizes raw bytes, handles whitespace/comments, and processes escape sequences. + * **[src/lexer/token.rs]**: Defines the [`Token`] enum representing Vipr's lexicon. Includes structural tokens (`Newline`, `Indent`, `Dedent`). + * **[src/lexer/mod.rs]**: The [`Lexer`] struct that tokenizes raw bytes, manages an indentation stack for Python-like scoping, and handles escape sequences. * **`src/parser/`**: Syntax analysis. * **[src/parser/ast.rs]**: Definition of AST nodes ([`Program`], [`Decl`], [`Stmt`], [`Expr`], and [`Literal`]). - * **[src/parser/mod.rs]**: The recursive-descent [`Parser`] with precedence climbing for operators. + * **[src/parser/mod.rs]**: The recursive-descent [`Parser`] that strictly enforces block structures via `Indent`/`Dedent` matching and lookaheads for statement disambiguation. * **`src/semantic/`**: Static semantics and type checking. * **[src/semantic/symbol_table.rs]**: The block-scoped [`SymbolTable`] tracking variable and function signatures using a stack of hash maps. - * **[src/semantic/mod.rs]**: The [`SemanticAnalyzer`] that performs symbol resolution, type mismatch verification, main function requirement checks, and constant reassignment checks. + * **[src/semantic/mod.rs]**: The [`SemanticAnalyzer`] that performs a two-pass symbol resolution, type mismatch verification, main function requirement checks, and constant reassignment checks. * **`src/codegen/`**: Code generation. - * **[src/codegen/mod.rs]**: The [`CodeGenerator`] which generates C source code from the checked AST, translating primitive types and loops, and injecting compiler macros for I/O functions. - * **`src/utils/`**: Placeholder directory. - * **[src/utils/mod.rs]** and **[src/utils/error.rs]**: Both are currently empty files ready for future shared compiler utility functions or error reporting systems. + * **[src/codegen/mod.rs]**: The [`CodeGenerator`] which generates C source code from the checked AST. It translates primitive types, safely evaluates multiple-assignments into temporary variables to prevent side effects, and maps Vipr structures into valid C11 syntax. --- ## 3. Vipr Language Specification Details -Vipr has a strict and clean feature set: +Vipr v0.2.0 has a strict, clean, and whitespace-sensitive feature set: ### Primitive Types * `int`: 32-bit signed integer (mapped to C `int32_t`). @@ -60,34 +58,34 @@ Vipr has a strict and clean feature set: * `void`: Empty return type. ### Grammar & Structural Quirks -1. **Mandatory Semicolons**: Every statement (except block statements themselves) must end with a semicolon `;`. -2. **Variable Declarations**: No type inference (`int x = 10;`, not `x := 10;` or `var x = 10;`). -3. **No Multiple Declarations**: Only one variable declaration is allowed per line. -4. **Strict Scope**: A block `{}` defines its own scope. Inner declarations can shadow outer declarations, but cannot be accessed outside the block. +1. **Python-style Indentation**: Blocks are defined by colons `:` and strict indentation rules. The Lexer pushes and pops `Indent` and `Dedent` tokens natively based on spaces (tabs are illegal). +2. **Variable Declarations**: `let x: int = 10` for variables, `const y: float = 3.1` for immutable constants. No type inference. +3. **Multiple Declarations & Assignments**: Vipr allows `let a, b: int = 1, 2`. Reassignment `a, b = expr1, expr2` evaluates the right-hand expressions into temporary memory *before* binding them to the left side, guaranteeing safe swaps like `a, b = b, a`. +4. **Strict Scope**: An indented block defines its own scope. Inner declarations can shadow outer declarations, but cannot be accessed outside the block. 5. **No Increment/Decrement Operators**: The `++` and `--` operators do not exist. Increments must be written explicitly as `i = i + 1`. -6. **Conditionals**: Conditions in `if` and `while` must evaluate exactly to the `bool` type. No implicit truthiness (e.g. `if (1)` is a compile error). -7. **The `main` Function**: Execution starts at the `main` function. It must be declared as either returning `void` (implicitly when omitted) or `int`. +6. **Conditionals**: Conditions in `if`, `elif`, and `while` must evaluate exactly to the `bool` type. No implicit truthiness (e.g. `if 1:` is a compile error). +7. **The `main` Function**: Execution starts at the `main` function. It must be declared as returning `void` (`def main() -> void:`). The Code Generator automatically maps this to the required `int main(void)` for GCC. --- ## 4. Compiler Internals -### The I/O Subsystem (`print` and `scan`) -In Vipr, `print(expression)` and `scan(variable)` are built-in functions. They do not exist in the user symbol table but are handled specially: +### The I/O Subsystem (`print` and `input`) +In Vipr, `print(expr1, expr2)` and `input(var1, var2)` are built-in macros. They do not exist in the user symbol table but are handled specially: 1. **Semantic Analysis**: - - `print` expects exactly one argument of any non-void type. - - `scan` expects exactly one variable identifier that is mutable (not a constant). + - `print` accepts variable arguments of any non-void type. + - `input` accepts variable arguments, requiring them to be mutable identifiers (not constants). 2. **Code Generation**: - The generator includes a C boilerplate utilizing C11 `_Generic` macros: - - `vipr_print(x)` resolves the format specifier at compile-time: `%d\n` for `int`, `%f\n` for `float`, `%c\n` for `char`, `%s\n` for `const char*` or `bool` (printing `"true"` or `"false"`). - - `vipr_scan(x)` similarly resolves `%d`, `%f`, `%c`, or `%s` depending on the variable's type. + The generator resolves types dynamically using the AST symbol tables and generates standard C library calls natively: + - `print(x, y)` translates to multiple discrete `printf()` calls sequentially based on the type of the AST expression (`%d` for `int`, `%f` for `float`, `%s` for string, etc.). + - `input(a, b)` calculates a unified format string (e.g. `"%d %f"`) and injects it into a single `scanf("%d %f", &a, &b)` call. ### Compilation Mechanics (`src/main.rs`) When you invoke the compiler on a source file `test.vipr`: 1. It parses arguments, reads the file, and runs the tokenization -> parsing -> type-checking. 2. If successful, it writes C code into `temp_vipr_output.c`. 3. It spawns the command `gcc temp_vipr_output.c -o test` to compile it natively. -4. Finally, it cleans up and deletes `temp_vipr_output.c`. +4. Finally, it cleans up and silently deletes `temp_vipr_output.c`. --- @@ -98,20 +96,20 @@ To compile a Vipr program: ```bash cargo run -- ``` -For example, if you have a file `hello.vipr`, this produces a compiled executable `hello` in the current directory. +For example, if you have a file `test.vipr`, this produces a compiled executable `test` in the current directory. ### Running Tests Unit tests are defined in almost every module's `tests` submodule. Run them with: ```bash cargo test ``` +The codebase enforces `unwrap()` isolation—it is forbidden in core compiler mechanics except for `.expect("ICE: ...")` clauses indicating unrecoverable semantic validation failures. --- ## 6. Gotchas & Tips for Future Work * **GCC Dependency**: The compiler invokes `gcc` on the host system to perform the final native compilation step. Ensure `gcc` is installed and in the user's `PATH`. -* **C11 Requirement**: The generated code uses C11 `_Generic` macros. The host compiler `gcc` must support C11 standards. -* **Lexer Limits**: The lexer reads numbers and converts them directly via `.parse()`. Very large integers or floats that overflow standard bounds are handled as parsing errors. -* **Return Statements**: Non-void functions are validated to ensure all paths return the expected type, though currently the compiler doesn't do control-flow reachability analysis on whether a return is guaranteed at the end of every branch. -* **Compiler Utilities**: The `src/utils` module is currently empty. If you need to implement unified compiler error spans or span-tracking source mappings, this is the place to start. +* **C11 Requirement**: The host compiler `gcc` must support C11 standards. +* **Lexer & Parser Coordination**: The lexer tracks parenthesis depth `paren_depth`. When `paren_depth > 0`, it intentionally swallows newlines and skips indentation mapping so multi-line expressions inside parens parse cleanly. +* **Return Statements**: Non-void functions are strictly validated via a conservative recursive tree check (`guarantees_return` in `src/semantic/mod.rs`) to ensure all `if`/`elif`/`else` control paths yield the expected type. diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1cf8c..1f1d29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,42 @@ # Changelog -All notable changes to this project are documented in this file. +All notable changes to the Vipr programming language and compiler will be documented in this file. + +## [v0.2.0] - The Indentation Update + +VIPR v0.2.0 is a massive syntax overhaul. We have pivoted away from the C-style brackets and semicolons of v0.1 in favor of a cleaner, Python-inspired, whitespace-sensitive syntax, while retaining our strict static typing and AOT C-compilation pipeline. -## [0.1.0] - 2026-07-17 ### Added -- Initial release of Vipr compiler -- Static typing and Python-like syntax support -- Basic CLI for compiling Vipr programs +- **`let` and `const` keywords**: Variables and constants are now explicitly declared using `let` and `const`. +- **Type Annotations**: Types are now declared after a colon, e.g., `let age: int = 25`. +- **Multiple Assignments**: You can now declare and assign multiple variables on a single line: `let a, b: int = 1, 2`. + - **Safe Swaps**: Reassignments safely evaluate the entire right-hand side before mutating the left-hand side, enabling swaps like `a, b = b, a` without temporary variables. +- **Keyword Operators**: Added `and`, `or`, and `not` to replace `&&`, `||`, and `!`. +- **`elif` Keyword**: Replaced `else if` for conditional chaining. +- **Range-based For Loops**: Introduced `for i: int in range(start, end, step):` to replace the verbose C-style `for` loops. +- **Function Returns**: Added the `-> type:` arrow syntax for declaring function return types. +- **Multi-argument I/O**: `print(a, b)` and `input(a, b)` now accept a variable number of comma-separated arguments. + +### Changed +- **Whitespace Scoping**: Removed curly braces `{}`. Code blocks are now strictly defined by a colon `:` and indentation levels. +- **Semicolons Removed**: Statements are now terminated strictly by newlines. No more trailing semicolons. +- **`def` over `func`**: Functions are now declared with the `def` keyword. +- **Parentheses in Conditions**: Parentheses are no longer required around the conditions in `if`, `elif`, and `while` statements. +- **`input` over `scan`**: The built-in console reader is now named `input` instead of `scan`. + +### Under the Hood (Compiler Architecture) +- **Lexer Statefulness**: The lexer now maintains an internal indentation stack and parenthesis depth counter to cleanly emit `Indent`, `Dedent`, and `Newline` tokens for the parser. +- **Lookahead Disambiguation**: The parser now utilizes parenthesis-aware lookaheads to seamlessly differentiate assignments from standard expressions without relying on semicolons. +- **Codegen Optimization**: The C transpiler no longer relies on complex C11 `_Generic` macros for I/O. `CodeGenerator` now strictly type-checks AST nodes on the fly and generates standard `printf` and `scanf` format strings natively (`%d`, `%f`, `%s`). +- **Temporary Variable Evaluation**: The compiler backend automatically calculates `_tempX` placeholder variables to sandbox multiple-assignment right-hand-sides and loop ranges (`start`, `end`, `step`), guaranteeing zero side-effect bleed during execution. -### Requirements -- To run Vipr binary, GCC needs to be installed on the system \ No newline at end of file +--- + +## [v0.1.0] - Initial Release [DEPRECATED] + +### Added +- Initial compiler release written in Rust. +- C-style bracket `{}` block scoping and mandatory semicolons. +- Built-in primitives: `int`, `float`, `bool`, `char`, `string`, `void`. +- Basic control flow (`if`, `else if`, `else`, `while`, C-style `for`). +- Compile-time macro resolution for `print` and `scan` using C11 `_Generic`. \ No newline at end of file diff --git a/README.md b/README.md index 625758d..d9e2d1d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,14 @@ -# The Official Vipr Language Guide (v0.1) +# The Official Vipr Language Guide (v0.2.0) -Welcome to Vipr! Vipr is a statically typed, ahead-of-time compiled programming language designed for simplicity, readability, and learning. Drawing inspiration from C, Go, and Python, Vipr enforces strict rules like mandatory type declarations and block scoping to help you write safe, predictable code. +Welcome to Vipr! Vipr is a statically typed, ahead-of-time compiled programming language designed for simplicity, readability, and learning. Drawing inspiration from Python and Rust, Vipr v0.2.0 features a clean indentation-based syntax, mandatory type annotations, and strict block scoping. ## 1. Program Structure Every executable Vipr program must define a `main` function. This is the entry point where execution begins. ```text -func main() { - print("Hello, Vipr!"); -} - +def main() -> void: + print("Hello, Vipr!") ``` ## 2. Primitive Types @@ -19,68 +17,56 @@ Vipr features mandatory type declarations and provides the following built-in pr | Type | Description | Size | | --- | --- | --- | -| `bool` | Boolean value (`true` or `false`) | 1 byte - - | -| `int` | Signed integer | 32-bit - - | -| `float` | Floating-point number | 32-bit IEEE-754 - - | -| `char` | Single character | 8-bit - - | -| `string` | Text string | Implementation-defined - - | -| `void` | Used exclusively for functions that return no value | N/A - - | +| `bool` | Boolean value (`true` or `false`) | 1 byte | +| `int` | Signed integer | 32-bit | +| `float` | Floating-point number | 32-bit IEEE-754 | +| `char` | Single character | 8-bit | +| `string` | Text string | Implementation-defined | +| `void` | Used exclusively for functions that return no value | N/A | ## 3. Variables & Constants -Variables must be declared with their type before they are used, and every statement must end with a semicolon. Vipr does not support type inference or multiple variables declared on a single line. +Variables must be declared with their type using the `let` keyword. Constants are declared using `const`. Vipr does not support type inference, but it does support multiple assignments on a single line! + +Statements are terminated by newlines; there are no semicolons. **Variables:** ```text -// Declaration without initialization -int age; +// Declaration without initialization (gets default value) +let age: int // Declaration with initialization -string name = "Alice"; +let name: string = "Alice" // Reassignment -age = 25; +age = 25 +// Multiple declaration and assignment +let x, y: float = 1.5, 2.0 +x, y = y, x ``` **Constants:** Use the `const` keyword to declare immutable values. Constants must be initialized immediately and cannot be reassigned later. ```text -const float PI = 3.14159; - +const PI: float = 3.14159 ``` ## 4. Input and Output -Vipr keeps console interaction incredibly simple with two built-in functions. - -* `scan(variable)`: Reads user input directly into an existing variable. - - -* `print(expression)`: Outputs strings, variables, or the results of expressions to the console. - +Vipr keeps console interaction incredibly simple with two built-in statements: +* `input(variable1, variable2)`: Reads user input directly into existing variables. +* `print(expr1, expr2)`: Outputs strings, variables, or the results of expressions sequentially to the console, followed by a newline. ```text -int user_age; -print("Enter your age:"); -scan(user_age); -print(user_age + 5); - +def main() -> void: + let user_age: int + print("Enter your age:") + input(user_age) + print("In 5 years, you will be:", user_age + 5) ``` ## 5. Operators @@ -88,86 +74,69 @@ print(user_age + 5); Vipr supports standard operators with traditional precedence rules: * **Arithmetic:** `+`, `-`, `*`, `/`, `%` - * **Comparison:** `==`, `!=`, `<`, `>`, `<=`, `>=` - -* **Logical:** `&&`, `||`, `!` - +* **Logical:** `and`, `or`, `not` * **Assignment:** `=` - ## 6. Control Flow -Conditional logic uses standard `if`, `else if`, and `else` blocks. Parentheses are required around the condition. +Conditional logic uses standard `if`, `elif`, and `else` blocks. Parentheses are NOT required around the condition. Code blocks are defined by a colon `:` and Python-style indentation. ```text -int score = 85; - -if (score >= 90) { - print("A"); -} -else if (score >= 80) { - print("B"); -} -else { - print("C"); -} - +let score: int = 85 + +if score >= 90: + print("A") +elif score >= 80: + print("B") +else: + print("C") ``` ## 7. Loops -Vipr supports two types of loops: `while` loops and C-style `for` loops. +Vipr supports two types of loops: `while` loops and range-based `for` loops. **While Loop:** ```text -int count = 0; -while (count < 5) { - print(count); - count = count + 1; -} - +let count: int = 0 +while count < 5: + print(count) + count = count + 1 ``` **For Loop:** -*Note: Vipr v0.1 does not support `++` or `--` operators, so you must explicitly add or subtract for the increment step*. +Vipr uses a `range(start, end, step)` construct for strictly typed integer iteration. ```text -for (int i = 0; i < 10; i = i + 1) { - print(i); -} - +for i: int in range(0, 10, 1): + print(i) ``` ## 8. Functions -Functions are declared using the `func` keyword, followed by the parameters, the return type, and the body block. +Functions are declared using the `def` keyword, followed by the parameters, an arrow `->`, the return type, and a colon `:`. **Functions returning a value:** ```text -func multiply(int a, int b) int { - return a * b; -} - +def multiply(a: int, b: int) -> int: + return a * b ``` **Functions returning nothing:** -Functions that do not return data must explicitly declare a `void` return type. The `return` statement can be omitted in void functions. +Functions that do not return data must explicitly declare a `void` return type. The `return` statement can be omitted or used without an expression. ```text -func greet(string user) void { - print("Welcome, "); - print(user); -} - +def greet(user: string) -> void: + print("Welcome, ", user) ``` ## 9. Scope and Comments **Scope:** -Vipr uses strict block scoping. Variables declared inside a `{}` block (like inside an `if` statement or a loop) are completely invisible outside of that block. +Vipr uses strict block scoping. Variables declared inside an indented block (like inside an `if` statement or a loop) are completely invisible outside of that block. **Comments:** @@ -178,7 +147,6 @@ Vipr uses strict block scoping. Variables declared inside a `{}` block (like ins This is a multi-line comment */ - ``` --- @@ -188,32 +156,28 @@ multi-line comment Here is a full Vipr program putting it all together: ```text -func add(int a, int b) int { - return a + b; -} +const MAX_LIMIT: int = 100 -func main() { - int x; - int y; +def add(a: int, b: int) -> int: + return a + b - print("Enter first number:"); - scan(x); +def main() -> void: + let x, y: int + + print("Enter first number:") + input(x) - print("Enter second number:"); - scan(y); + print("Enter second number:") + input(y) - int result = add(x, y); + let result: int = add(x, y) - print("Result:"); - print(result); + print("Result:", result) - if (result > 100) { - print("Large number"); - } - else { - print("Small number"); - } -} + if result > MAX_LIMIT: + print("Large number") + else: + print("Small number") ``` --- @@ -236,14 +200,4 @@ This generates a native executable named after your source file (e.g., compiling If you have Rust and Cargo installed, you can compile and run directly from the repository source: ```bash cargo run -- -``` - ---- - -## Downloading the Compiler Binary - -Pre-compiled compiler binaries for Linux and Windows are automatically built and published. You can obtain them in two ways: - -### GitHub Releases (Recommended) - - Navigate to the [Releases](https://github.com/sa-nafi/vipr/releases) section of the GitHub repository. - - Download the latest pre-compiled binary matching your platform (e.g., `vipr` for Linux or `vipr.exe` for Windows). \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/VIPR1.md b/docs/VIPR1.md index d68e125..c341bdf 100644 --- a/docs/VIPR1.md +++ b/docs/VIPR1.md @@ -1,4 +1,4 @@ -# Vipr Programming Language Specification v0.1 +# Vipr Programming Language Specification v0.1 [DEPRECATED] ## Overview diff --git a/docs/VIPR2.md b/docs/VIPR2.md index 92bbb92..639ab6f 100644 --- a/docs/VIPR2.md +++ b/docs/VIPR2.md @@ -1,46 +1,49 @@ +# VIPR Programming Language Specification v0.2.0 -# VIPR Programming Language Specification v0.2.1 +# 1. Overview -## Overview +VIPR is a statically typed, ahead-of-time compiled programming language with Python-inspired indentation syntax. -VIPR is a statically typed, compiled programming language designed for simplicity, readability, and ease of implementation. +## Design Goals -Its syntax draws inspiration from Python while enforcing explicit type declarations and static typing. - -### Design Goals - -* Statically typed -* Ahead-of-time compiled -* Readable Python-inspired syntax -* Mandatory type annotations -* No type inference -* Indentation-based blocks -* No classes or inheritance -* No generics -* Minimal runtime -* Easy-to-implement compiler architecture +- Statically typed +- Ahead-of-time compiled +- Explicit type annotations +- No type inference +- Indentation-based blocks +- No classes +- No inheritance +- No generics +- Minimal runtime +- Simple compiler implementation --- -# Primitive Types +# 2. Primitive Types | Type | Description | -| -------- | ------------------------------ | +|----------|--------------------------------| | `bool` | Boolean value | | `int` | 32-bit signed integer | | `float` | 32-bit IEEE-754 floating point | -| `char` | Single character | +| `char` | Single Unicode scalar value | | `string` | UTF-8 string | -# Special Return Type +## Special Return Type -| type | Description | -|----------|--------------------------------| -| `void` | No Value | +| Type | Description | +|--------|-------------| +| `void` | No value | + +`void` may only be used as a function return type. + +Variables, constants, parameters, and expressions cannot have type `void`. --- -# Keywords +# 3. Keywords + +The following words are reserved and cannot be used as identifiers: ```text let @@ -78,47 +81,78 @@ char string void +print +input ``` --- -# Program Structure +# 4. Program Structure -VIPR supports Python-like executable programs. +A VIPR program consists of top-level declarations. -A program does not need to define a `main` function. Top-level executable statements are allowed and are executed in source order. -**Note:** To ensure sequential execution works correctly when function declarations are mixed with executable statements, the compiler hoists all function declarations to the top before execution. +The global scope may contain: -Example: +- Function declarations +- Global variable declarations +- Global constant declarations + +The global scope may not contain executable statements such as: ```vipr -let x: int = 10 -print(x) +print("hello") +x = 5 +foo() +return +``` + +## Entry Point +Every VIPR program must define exactly one `main` function with this signature: + +```vipr +def main() -> void: + ... ``` -Functions may still be defined for organization and reuse: +Any other `main` signature is a semantic error. + +Valid: ```vipr +const MAX_USERS: int = 100 + def add(a: int, b: int) -> int: return a + b -let result: int = add(2, 3) -print(result) +def main() -> void: + let result: int = add(2, 3) + print(result) +``` + +Invalid: +```vipr +def main(x: int) -> void: + pass ``` -If a `main` function is present, it is treated as a normal function and is not required for execution. +Invalid: + +```vipr +def main() -> int: + return 0 +``` --- -# Identifiers +# 5. Identifiers Identifiers: -* Must begin with a letter or underscore -* May contain letters, digits, and underscores -* Cannot be a keyword +- Must begin with a letter or underscore +- May contain letters, digits, and underscores +- Cannot be a keyword Valid: @@ -127,7 +161,6 @@ count _total userName value1 - ``` Invalid: @@ -136,147 +169,399 @@ Invalid: 1count for int - ``` --- -# Variable Declarations +# 6. Variables + +## 6.1 Local Variable Declaration + +Local variables are declared with `let`. -## Declaration Without Initialization +Declaration without initialization: ```vipr let age: int let name: string let active: bool - ``` -## Declaration With Initialization +Declaration with initialization: ```vipr let age: int = 25 let name: string = "John" let active: bool = true - ``` -## Multiple Variables +Multiple declarations: ```vipr let x, y, z: int - let a, b, c: int = 1, 2, 3 +``` + +If an initializer list is provided, the number of identifiers and expressions must match exactly. + +Invalid: + +```vipr +let a, b: int = 1 +let a, b: int = 1, 2, 3 +``` + +## 6.2 Global Variable Declaration + +Global variables may be declared with or without an initializer. +If omitted, the variable receives its type's default value (see §20). If provided, the initializer must be a constant expression (§8), and may reference only literals and previously-declared global constants. It may not reference other global variables, and may not call functions. +```vipr +let count: int // valid — count = 0 +let total: int = 10 // valid ``` -## Reassignment +## 6.3 Reassignment -Multiple assignments are supported and must match the expression count on the right-hand side. +Variables may be reassigned after declaration. ```vipr age = 30 name = "Alice" a, b, c = 4, 5, 6 +``` +Multiple assignments must have matching identifier and expression counts. + +Invalid: + +```vipr +a, b = 1 +a, b = 1, 2, 3 ``` +Assignment to constants is illegal. + +## 6.4 Evaluation Order +- Except for short-circuit behavior of and and or, expressions are evaluated left to right. Binary operators evaluate their left operand before their right operand. Function-call arguments and expression lists are evaluated left to right +- In a multiple assignment, all right-hand-side expressions are evaluated before any assignment is performed. The resulting values are then assigned to targets from left to right. +- An identifier may appear at most once in an assignment target list or input argument list. +- Names introduced by a declaration become visible only after the declaration's initializer list has been evaluated. + --- -# Constants +# 7. Constants -Constants are immutable after declaration. Multiple constants can be declared together. +Constants are declared with `const`. + +Constants must always be initialized. ```vipr const PI: float = 3.14159 const MAX_USERS: int = 100 const MIN_AGE, MAX_AGE: int = 18, 65 +``` +Constants cannot be reassigned. + +Invalid: + +```vipr +const PI: float = 3.14159 + +def main() -> void: + PI = 4.0 +``` + +## 7.1 Global Constants + +Global constants must be initialized with constant expressions. + +```vipr +const A: int = 10 +const B: int = A + 5 +``` + +A global constant initializer may refer only to previously declared global constants. + +Valid: + +```vipr +const A: int = 10 +const B: int = A + 1 ``` Invalid: ```vipr -PI = 4.0 +const B: int = A + 1 +const A: int = 10 +``` + +## 7.2 Local Constants + +Local constants may be initialized with normal runtime expressions. + +```vipr +def main() -> void: + let x: int = 10 + const y: int = x + 5 + print(y) +``` +--- + +# 8. Constant Expressions + +A constant expression is an expression that can be fully evaluated at compile time. + +Allowed inside constant expressions: + +- Literals +- Previously declared constants +- Parentheses +- Unary `-` +- `not` +- Arithmetic operators +- Comparison operators +- Equality operators +- Logical operators + +Not allowed inside constant expressions: + +- Function calls +- Variables declared with `let` +- `input` +- `print` + +Examples: + +```vipr +const A: int = 1 + 2 * 3 +const B: bool = true and not false +const C: float = 1.5 + 2.5 +const D: int = -10 +``` + +Invalid: + +```vipr +let x: int = 10 +const y: int = x + 1 ``` +At global scope, `let` and `const` initializers must be constant expressions. + +Inside functions, only `const` declarations require initialization, but their initializer may be a runtime expression. + +If evaluating a constant expression causes an arithmetic error, such as division or modulo by zero, the declaration is a compile-time semantic error. + --- -# Literals +# 9. Literals -## Integer +## 9.1 Integer Literals + +Integer literals are unsigned in source code. ```vipr 0 10 --20 +123 +``` + +Negation is handled by unary `-`. + +```vipr +let x: int = -5 +``` +Integer literals must fit in the range: + +```text +0 to 2147483647 ``` -## Float +Integer arithmetic uses 32-bit signed integer behavior. Division or modulo by zero is a runtime error. + +## 9.2 Float Literals + +Float literals contain digits on both sides of the decimal point. + +Valid: ```vipr 3.14 0.5 --2.7 +10.0 +``` + +Invalid: +```vipr +.5 +1. ``` -## Boolean +## 9.3 Boolean Literals ```vipr true false - ``` -## Character +## 9.4 Character Literals + +A character literal is enclosed in single quotes and contains exactly one character or one valid escape sequence. ```vipr 'A' 'B' '0' +'\n' +``` +Invalid: + +```vipr +'' +'ab' +'\x' ``` -## String +## 9.5 String Literals + +Strings are UTF-8 and enclosed in double quotes. ```vipr "Hello" "VIPR" +"Line\nBreak" +``` + +## 9.6 Escape Sequences + +Both `char` and `string` support: + +| Escape | Meaning | +|--------|----------------| +| `\n` | Newline | +| `\t` | Horizontal tab | +| `\\` | Backslash | +| `\"` | Double quote | +| `\'` | Single quote | +| `\0` | Null character | +Any other escape sequence is a lexer error. + +--- + +# 10. Input and Output + +`print` and `input` are built-in statements, not functions. + +They are keywords and cannot be used as identifiers. + +--- + +## 10.1 `print` + +Syntax: + +```vipr +print(expression1, expression2, ...) +``` + +`print` accepts zero or more arguments. + +Arguments are printed in order with no automatic separator. A newline is appended at the end. + +```vipr +print() +print("Hello") +print("Age: ", age) +``` + +Output formatting: + +| Type | Printed Form | +|----------|---------------------------------| +| `int` | Decimal integer | +| `float` | Decimal floating-point format | +| `bool` | `true` or `false` | +| `char` | The character itself | +| `string` | The string contents | + +`print` produces no value and cannot be used in expression position. + +Invalid: + +```vipr +let x: int = print("hello") ``` --- -# Input and Output +## 10.2 `input` + +Syntax: + +```vipr +input(identifier1, identifier2, ...) +``` -Input is performed using the built-in `input()` function, which takes input of a variable to it's type. +`input` reads from standard input and assigns values to existing mutable variables. ```vipr -let age_str: string -input(age_str) let age: int input(age) - +let name: string +input(name) ``` -Output is performed using the built-in `print()` function. +Rules: + +- Arguments must be identifiers. +- Arguments must resolve to mutable variables. +- Constants cannot be passed to `input`. +- input assigns parsed values to the variables it targets, replacing their current values. +- Runtime parse failure is a runtime error. + +Input behavior: + +| Target Type | Accepted Input | +|-------------|---------------------------------------------| +| `int` | One whitespace-delimited decimal integer | +| `float` | One whitespace-delimited float | +| `bool` | `true` or `false` | +| `char` | One non-whitespace character | +| `string` | One whitespace-delimited token | + +Multiple arguments consume input values left to right. + +Example: ```vipr -print("Hello") -print(age) -print(age + 5) +let age: int +let active: bool + +input(age, active) +``` +Invalid: + +```vipr +input(age + 5) +let x: int = input() +input(PI) ``` --- -# Operators +# 11. Operators -## Arithmetic +## 11.1 Arithmetic Operators ```text + @@ -284,265 +569,801 @@ print(age + 5) * / % - ``` -## Comparison +## 11.2 Comparison Operators ```text -== -!= < > <= >= +``` + +## 11.3 Equality Operators +```text +== +!= ``` -## Logical +## 11.4 Logical Operators ```text and or not - ``` -## Assignment +## 11.5 Assignment Operator ```text = +``` + +--- + +# 12. Operator Typing +VIPR has strict typing and no implicit casts. + +| Operator(s) | Operand Types | Result Type | Notes | +|--------------------|-------------------------------|-------------|-------------------------------| +| `+ - * / %` | `int`, `int` | `int` | `/` is integer division | +| `+ - * / %` | `float`, `float` | `float` | `%` is floating-point modulo | +| `+` | `string`, `string` | `string` | Concatenation | +| `< <= > >=` | `int`, `int` | `bool` | | +| `< <= > >=` | `float`, `float` | `bool` | | +| `< <= > >=` | `char`, `char` | `bool` | Unicode scalar ordering | +| `== !=` | Same non-void type | `bool` | | +| `and or` | `bool`, `bool` | `bool` | Short-circuit evaluation | +| `not` | `bool` | `bool` | | +| unary `-` | `int` | `int` | | +| unary `-` | `float` | `float` | | + +Examples: + +```vipr +let a: int = 1 + 2 +let b: float = 1.0 + 2.0 +let c: string = "hello" + " world" +let d: bool = a < 10 +``` + +Invalid: + +```vipr +let x: float = 1 + 2.0 +let y: int = true + 1 +let z: string = "age: " + 10 ``` --- -# Operator Precedence +# 13. Operator Precedence -Highest to Lowest: +Highest to lowest: ```text () +unary - +* / % ++ - +< <= > >= +== != +not +and +or +``` + +`not` has lower precedence than equality and comparison operators. + +Therefore: + +```vipr +not a == b +``` + +is parsed as: + +```vipr +not (a == b) +``` + +Operators of the same precedence are left-associative, except unary operators, which are right-associative. + +Comparison chaining is not supported. + +This parses but is usually a type error: + +```vipr +a < b < c +``` + +It is interpreted as: + +```vipr +(a < b) < c +``` + +--- + +# 14. Conditional Statements + +## 14.1 `if` + +```vipr +if age >= 18: + print("Adult") +``` + +## 14.2 `if` / `else` + +```vipr +if score >= 50: + print("Pass") +else: + print("Fail") +``` + +## 14.3 `elif` + +```vipr +if score >= 90: + print("A") +elif score >= 80: + print("B") +else: + print("C") +``` + +The condition expression in `if` and `elif` must have type `bool`. + +--- + +# 15. While Loops + +```vipr +let i: int = 0 + +while i < 10: + print(i) + i = i + 1 +``` + +The condition expression must have type `bool`. + +`break` and `continue` are allowed inside loops. + +--- + +# 16. For Loops + +VIPR supports range-based `for` loops. + +Syntax: + +```vipr +for variable: int in range(start, end, step): + block +``` + +Example: + +```vipr +for i: int in range(0, 10, 1): + print(i) +``` + +Rules: + +- The loop variable must have type `int`. +- `range` requires exactly three arguments. +- All `range` arguments must have type `int`. +- The loop variable is mutable. +- The loop variable is scoped to the loop block. +- Accessing the loop variable after the loop is a semantic error. + +Invalid: + +```vipr +for i: float in range(0, 10, 1): + pass +``` + +Invalid: + +```vipr +for i: int in range(0, 10): + pass +``` + +## 16.1 For Loop Evaluation Semantics + +For: + +```vipr +for i: int in range(start, end, step): + body +``` + +The expressions `start`, `end`, and `step` are evaluated exactly once before the loop begins, from left to right. + +If `step == 0`, this is a runtime error. + +If `step > 0`, the loop executes while: + +```vipr +i < end +``` + +If `step < 0`, the loop executes while: + +```vipr +i > end +``` + +After each iteration, the loop variable is updated by: + +```vipr +i = i + step +``` + +Examples: + +```vipr +for i: int in range(0, 5, 1): + print(i) +``` + +Prints: + +```text +0 +1 +2 +3 +4 +``` + +```vipr +for i: int in range(5, 0, -1): + print(i) +``` + +Prints: + +```text +5 +4 +3 +2 +1 +``` + +- The loop variable is the actual iteration counter. If the loop body assigns to the loop variable, that modified value is used by the loop's end-of-iteration update. +- In a for loop, continue performs the normal end-of-iteration update (i = i + step) before checking the loop condition again. break exits the loop without performing that update. +- The start, end, and step expressions are evaluated in the enclosing scope before the loop variable enters scope. + + +--- + +# 17. Functions + +## 17.1 Function Declaration + +Syntax: + +```vipr +def function_name(parameters) -> return_type: + block +``` + +Example: + +```vipr +def add(a: int, b: int) -> int: + return a + b +``` + +## 17.2 Void Functions + +```vipr +def greet() -> void: + print("Hello") +``` + +Void functions may omit `return`. + +They may also use `return` with no expression. + +```vipr +def log() -> void: + print("Done") + return +``` + +## 17.3 Function Calls + +```vipr +let result: int = add(5, 10) +greet() +``` + +Function argument types must match parameter types exactly. + +Function overloading is not supported. + +Recursive functions are allowed. + +Nested function declarations are not allowed. + +## 17.4 Void Function Calls + +A call to a `void` function may only appear as a standalone expression statement. + +Valid: + +```vipr +greet() +``` + +Invalid: + +```vipr +let x: int = greet() +print(greet()) +``` + +--- + +# 18. Return Statements + +A `return` statement may only appear inside a function body. + +Invalid: + +```vipr +return 5 +``` + +## 18.1 Non-Void Functions + +Non-void functions must return a value on all control paths. + +```vipr +def square(n: int) -> int: + return n * n +``` + +Invalid: + +```vipr +def bad(n: int) -> int: + if n > 0: + return n +``` + +The returned expression type must exactly match the declared return type. + +Invalid: + +```vipr +def bad() -> int: + return 1.5 +``` + +## 18.2 Void Functions + +Void functions may use: + +```vipr +return +``` + +They may not return a value. + +Invalid: + +```vipr +def bad() -> void: + return 5 +``` + +## 18.3 Simple Return Analysis Rule + +For this language, the compiler uses the following conservative return-analysis rule: + +A statement guarantees return if: + +- A block guarantees return if, when its statements are considered in order, at least one statement guarantees return. Statements after a guaranteed-return statement do not affect the result. +- An if statement guarantees return only if it has an else branch and the body of every if, elif, and else branch guarantees return. + +Loops do not count as guaranteed returns unless the compiler chooses to implement stronger analysis. + +Example accepted: + +```vipr +def abs_value(x: int) -> int: + if x >= 0: + return x + else: + return -x +``` + +Example rejected: + +```vipr +def bad(x: int) -> int: + while true: + return x +``` + +This may be rejected because the simple analysis does not prove the loop always executes. + +--- + +# 19. Scope Rules + +VIPR uses block scope. + +A block is created by: + +- Function bodies +- `if` bodies +- `elif` bodies +- `else` bodies +- `while` bodies +- `for` bodies + +Variables declared inside a block are visible only from their declaration point to the end of that block. + +Example: + +```vipr +if true: + let x: int = 10 + +print(x) +``` + +Result: semantic error. + +## 19.1 Global Scope + +Global names include: + +- Global variables +- Global constants +- Functions + +All global names share the same namespace. + +This is invalid: + +```vipr +let foo: int = 1 + +def foo() -> void: + pass +``` + +## 19.2 Redeclaration + +Redeclaring a name in the same scope is a semantic error. + +Invalid: + +```vipr +def main() -> void: + let x: int = 1 + let x: int = 2 +``` + +Function parameters are in the function body scope and cannot duplicate each other. + +Invalid: + +```vipr +def add(a: int, a: int) -> int: + return a +``` + +## 19.3 Shadowing + +Inner scopes may shadow outer scopes. + +Valid: + +```vipr +let x: int = 1 + +def main() -> void: + let x: int = 2 + print(x) +``` + +This prints: + +```text +2 +``` + +A `for` loop variable may also shadow an outer variable. + +```vipr +def main() -> void: + let i: int = 100 + + for i: int in range(0, 3, 1): + print(i) + + print(i) +``` + +Output: + +```text +0 +1 +2 +100 +``` -not, - (unary) +--- -* / % +# 20. Default Values -+ - +| Type | Default Value | +|----------|----------------| +| `int` | `0` | +| `float` | `0.0` | +| `bool` | `false` | +| `char` | `'\0'` | +| `string` | `""` | -< <= > >= +> Every variable declared without an explicit initializer is automatically assigned its type's default value at the point of declaration. Reading such a variable is always legal. -== != +--- -and +# 21. Loop Control Statements -or +## 21.1 `break` +`break` exits the innermost enclosing loop. +```vipr +while true: + break ``` ---- +`break` outside a loop is a semantic error. -# Conditional Statements +## 21.2 `continue` -## If +`continue` skips to the next iteration of the innermost enclosing loop. ```vipr -if age >= 18: - print("Adult") - +while true: + continue ``` -## If-Else +`continue` outside a loop is a semantic error. -```vipr -if score >= 50: - print("Pass") -else: - print("Fail") +--- -``` +# 22. `pass` -## Else-If +`pass` is an empty statement. -```vipr -if score >= 90: - print("A") -elif score >= 80: - print("B") -else: - print("C") +It is useful when a block is syntactically required. +```vipr +def main() -> void: + pass ``` ---- +Blocks cannot be empty, so use `pass` for an empty body. -# While Loop +Invalid: ```vipr -let i: int = 0 +def main() -> void: +``` -while i < 10: - print(i) - i = i + 1 +Valid: +```vipr +def main() -> void: + pass ``` --- -# For Loop +# 23. Comments -VIPR provides a range-based loop. The loop variable declared in the `for` statement header is locally scoped to the loop block. - -Syntax: +## 23.1 Single-Line Comments ```vipr -for variable: type in range(start, end, step): - +// This is a comment ``` -Example: +## 23.2 Multi-Line Comments ```vipr -for i: int in range(0, 10, 1): - print(i) - -// Accessing `i` here results in a Compile Error. - +/* +This is +a comment +*/ ``` ---- - -# Functions +Block comments do not nest. -## Function Declaration +The lexer scans from `/*` to the first following `*/`. -Syntax: +Invalid: ```vipr -def function_name(parameters) -> return_type: - +/* outer /* inner */ outer */ ``` -Example: +The second `*/` has no matching opening comment. -```vipr -def add(a: int, b: int) -> int: - return a + b +## 23.3 Comment and Newline Behavior -``` +Comment text is removed by the lexer. ---- +A line that becomes empty after removing comments produces no `NEWLINE` token. -## Void Functions +A line that still contains code after removing a trailing comment still produces a `NEWLINE`. -```vipr -def greet() -> void: - print("Hello") +Newlines inside block comments remain physical line boundaries. Removing a block comment must not concatenate code from two different physical lines. + +Example: +```vipr +let x: int = 5 // comment ``` +This still produces the required `NEWLINE` after the declaration. + --- -## Function Calls +# 24. Indentation and Lexer Tokens -```vipr -let result: int = add(5, 10) +VIPR uses indentation-based blocks. -greet() +The lexer produces the following structural tokens: +```text +NEWLINE +INDENT +DEDENT ``` ---- +Rules: -# Return Statements +- Indentation must use spaces only. +- Tabs are a lexer error. +- A block must begin on a new line with an indented statement. +- Single-line blocks are not supported. +- Indentation is measured by the number of leading spaces on a non-empty, non-comment-only line. A dedent must exactly match a previously established indentation level; otherwise it is a lexer error. Blank lines and comment-only lines do not affect indentation levels. -Non-void functions must return a value. +Invalid: ```vipr -def square(n: int) -> int: - return n * n +if true: print("yes") +``` +```vipr +if true: + print("a") + print("b") ``` -Void functions may omit `return`. +Valid: ```vipr -def log() -> void: - print("Done") - +if true: + print("yes") ``` ---- +## 24.1 End of File -# Scope Rules +At end of input, the lexer emits: -VIPR uses block scope. Variables declared inside a block (such as an `if` statement, `while` loop, or `for` loop body) are only visible within that block. Loop variables declared in `for` loop headers belong to the loop block. +1. A final `NEWLINE` if the last non-empty line did not already end with one. +2. One `DEDENT` token for each currently open indentation level. -```vipr -if true: - let x: int = 10 +## 24.2 Parentheses and Newlines -print(x) +The lexer tracks parenthesis depth. -``` +Inside parentheses, physical newlines do not produce `NEWLINE` tokens. + +Valid: -Result: +```vipr +let x: int = ( + 1 + 2 +) +``` -```text -Compile Error +Valid: +```vipr +print( + "hello", + "world" +) ``` ---- +Indentation changes inside parentheses do not produce `INDENT` or `DEDENT`. -# Comments +--- -## Single-Line +# 25. Name Resolution -```vipr -// This is a comment +Semantic analysis of function bodies occurs after all top-level declarations have been entered into the global symbol table. -``` +Therefore, functions may call functions declared later in the file. -## Multi-Line +Valid: ```vipr -/* -This is -a comment -*/ +def main() -> void: + greet() +def greet() -> void: + print("Hello") ``` +However, global constant initializers may only refer to previously declared global constants. + --- -# Example Program +# 26. Semantic Rules Summary -```vipr -def add(a: int, b: int) -> int: - return a + b +## Function Rules -let x: int -let y: int +- A valid `main` function must exist. +- `main` must have signature `def main() -> void`. +- Function overloading is not allowed. +- Recursive functions are allowed. +- Nested function declarations are not allowed. +- Non-void functions must return a value on all control paths. +- Void functions may omit `return`. +- Void functions may use `return` with no expression. -input(x) -input(y) +## Type Rules -let result: int = add(x, y) +- Variables are statically typed. +- No implicit casts are allowed. +- Assignment types must match exactly. +- Function argument types must match parameter types exactly. +- `void` may only appear as a function return type. +- Conditions in `if`, `elif`, and `while` must have type `bool`. -print(result) +## Variable and Constant Rules -if result > 100: - print("Large number") -else: - print("Small number") +- Variables may be declared without an initializer. They receive their type's default value. +- If a global variable initializer is provided, it must be a constant expression. +- A global variable initializer may reference only literals and previously declared global constants. +- Constants must always be initialized. +- Global constants must be initialized with constant expressions. +- Constants cannot be reassigned. +- Multiple declarations and assignments must have matching identifier/expression counts. -``` +## Loop Rules + +- `break` and `continue` may only appear inside loops. +- `break` and `continue` target the innermost enclosing loop. +- `range` requires exactly three `int` arguments. +- `for` loop variables must have type `int`. --- -# VIPR EBNF Grammar v0.2.1 +# 27. VIPR EBNF Grammar v0.2.0 ```ebnf program @@ -550,37 +1371,10 @@ program topLevel = functionDecl - | topLevelStatement ; - -topLevelStatement - = simpleTopLevelStatement NEWLINE - | compoundStatement ; - -simpleTopLevelStatement - = variableDecl - | constDecl - | assignment - | passStmt - | expressionStmt ; - -statement - = simpleStatement NEWLINE - | compoundStatement ; - -simpleStatement - = variableDecl - | constDecl - | assignment - | breakStmt - | continueStmt - | passStmt - | returnStmt - | expressionStmt ; + | globalDeclaration ; -compoundStatement - = ifStmt - | whileStmt - | forStmt ; +globalDeclaration + = ( variableDecl | constDecl ) NEWLINE ; functionDecl = "def" @@ -617,6 +1411,27 @@ constDecl "=" expressionList ; +statement + = simpleStatement NEWLINE + | compoundStatement ; + +simpleStatement + = variableDecl + | constDecl + | assignment + | breakStmt + | continueStmt + | passStmt + | returnStmt + | inputStmt + | printStmt + | expressionStmt ; + +compoundStatement + = ifStmt + | whileStmt + | forStmt ; + assignment = identifierList "=" @@ -678,8 +1493,20 @@ continueStmt passStmt = "pass" ; +inputStmt + = "input" + "(" + identifierList + ")" ; + +printStmt + = "print" + "(" + [ expressionList ] + ")" ; + expressionStmt - = expression ; + = functionCall ; block = NEWLINE @@ -704,8 +1531,12 @@ logicalOr { "or" logicalAnd } ; logicalAnd - = equality - { "and" equality } ; + = logicalNot + { "and" logicalNot } ; + +logicalNot + = "not" logicalNot + | equality ; equality = comparison @@ -751,10 +1582,7 @@ factor } ; unary - = ( - "not" - | "-" - ) + = "-" unary | primary ; @@ -810,153 +1638,61 @@ stringLiteral charLiteral = CHAR ; - -``` - -# Lexer-Supplied Tokens - -VIPR uses indentation-based blocks. The lexer must generate: - -```text -NEWLINE -INDENT -DEDENT - -``` - -These tokens are not written by the programmer. They are produced automatically from changes in indentation level. - -**Note on Blank Lines and Comments:** The lexer is strictly responsible for swallowing comments and empty lines, ensuring they do not produce extraneous `NEWLINE` tokens that could interfere with block parsing. - -# Reserved Keywords - -```text -let -const - -def -return - -if -elif -else - -while -for -in -range - -break -continue -pass - -and -or -not - -true -false - -int -float -bool -char -string -void - ``` -# Semantic Rules - -The following constraints are enforced during semantic analysis: - -### Function Rules - -* Functions may be declared before or after use. -* Recursive functions are allowed. -* Function overloading is not allowed. -* Nested function declarations are not allowed. -* Non-void functions must return a value on all control paths. -* Void functions may use `return` with no expression. - -### Return Rules - -* `return` statements may only appear inside function bodies. -* Top-level `return` statements are illegal. -* `return expression` must match the declared return type. - -### Variable Rules - -* Variables are statically typed. -* Variables are not required to be initialized at declaration. -* Reading an uninitialized variable is a compile-time error. -* Multi-variable declarations and assignments must have matching identifier and expression counts. - -Valid: +--- -```vipr -let x, y, z: int = 1, 2, 3 +# 28. Parser Disambiguation Note -``` +The grammar has an intentional ambiguity between assignment statements and expression statements because both can begin with an identifier. -Invalid: +Example: ```vipr -let x, y, z: int = 1, 2 - +x = 5 +foo() +x + 1 ``` -### Constant Rules - -* Constants must be initialized. -* Constants cannot be reassigned. - -### Loop Rules +A practical parser should resolve this as follows: -* Loop variables are mutable. -* Loop variables declared in a `for` loop header exist entirely within the block scope of that loop. -* `break` may only appear inside loops. -* `continue` may only appear inside loops. -* `range()` always requires exactly three arguments. +When parsing a statement beginning with an identifier, scan ahead until the statement-ending `NEWLINE` at parenthesis depth zero. -Valid: - -```vipr -for i: int in range(0, 100, 1): - print(i) +- If a top-level `=` is found, parse the statement as an assignment. +- Otherwise, parse it as an expression statement. -``` +This is acceptable for a simple hand-written recursive descent parser. -Invalid: +Semantic analysis may reject useless expression statements if desired, but function calls as statements must be allowed: ```vipr -for i: int in range(100): - print(i) - +greet() ``` -### Type Rules - -* `void` may only be used as a function return type. -* Variables, constants, parameters, and expressions cannot have type `void`. -* Assignment types must match exactly. -* Function argument types must match parameter types exactly. -* Implicit type casting is not allowed. - -### Built-In Functions - -Built-in functionality is provided through predefined functions rather than language keywords. +--- -Examples: +# 29. Example Program ```vipr -print("Hello") +const LIMIT: int = 5 -let str_val: string -input(str_val) +def factorial(n: int) -> int: + if n <= 1: + return 1 + else: + return n * factorial(n - 1) +def main() -> void: + for i: int in range(1, LIMIT + 1, 1): + print("factorial(", i, ") = ", factorial(i)) ``` -These functions (`print`, `input`) are inserted into the global symbol table by the compiler before semantic analysis. +Output: ---- \ No newline at end of file +```text +factorial(1) = 1 +factorial(2) = 2 +factorial(3) = 6 +factorial(4) = 24 +factorial(5) = 120 +``` \ No newline at end of file diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 04fbf44..9a74d6b 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -1,593 +1,507 @@ use crate::parser::ast::*; +use std::collections::HashMap; + +#[derive(Clone)] +enum SymbolInfo { + Variable(ViprType), + Function(ViprType), +} -/// Generates C source code from a validated Vipr AST. pub struct CodeGenerator { - /// Accumulated C output. - output: String, - /// Current indentation depth (in 4-space units). - indent_level: usize, + buf: String, + temp_counter: usize, + scopes: Vec>, } impl CodeGenerator { pub fn new() -> Self { Self { - output: String::new(), - indent_level: 0, + buf: String::new(), + temp_counter: 0, + scopes: vec![HashMap::new()], } } - // ── Public API ─────────────────────────────────────────── - - /// Generate complete C source from a Vipr program. - pub fn generate(&mut self, program: &Program) -> String { - self.emit_boilerplate(); - - // Forward-declare every function so call order doesn't matter. - for decl in &program.declarations { - if let Decl::Function { - name, - parameters, - return_type, - .. - } = decl - { - self.emit(Self::type_to_c(return_type)); - self.emit(" "); - self.emit(name); - self.emit("("); - for (i, (pt, pn)) in parameters.iter().enumerate() { - if i > 0 { - self.emit(", "); - } - self.emit(Self::type_to_c(pt)); - self.emit(" "); - self.emit(pn); - } - self.emit(");\n"); - } - } - self.emit("\n"); - - // Full definitions & global variables. - for decl in &program.declarations { - self.gen_decl(decl); - self.emit("\n"); - } - - self.output.clone() + fn enter_scope(&mut self) { + self.scopes.push(HashMap::new()); } - // ── Emit helpers ───────────────────────────────────────── - - fn emit(&mut self, s: &str) { - self.output.push_str(s); + fn exit_scope(&mut self) { + self.scopes.pop(); } - fn emit_indent(&mut self) { - for _ in 0..self.indent_level { - self.output.push_str(" "); - } + fn insert_variable(&mut self, name: String, ty: ViprType) { + self.scopes.last_mut().expect("ICE: Codegen scopes stack is empty").insert(name, SymbolInfo::Variable(ty)); } - fn emit_line(&mut self, s: &str) { - self.emit_indent(); - self.emit(s); - self.emit("\n"); + fn insert_function(&mut self, name: String, ty: ViprType) { + self.scopes.last_mut().expect("ICE: Codegen scopes stack is empty").insert(name, SymbolInfo::Function(ty)); } - fn indent(&mut self) { - self.indent_level += 1; + fn lookup(&self, name: &str) -> Option<&SymbolInfo> { + for scope in self.scopes.iter().rev() { + if let Some(info) = scope.get(name) { + return Some(info); + } + } + None } - fn dedent(&mut self) { - self.indent_level -= 1; + fn type_of(&self, expr: &Expr) -> ViprType { + match expr { + Expr::Literal(lit) => match lit { + Literal::Int(_) => ViprType::Int, + Literal::Float(_) => ViprType::Float, + Literal::Bool(_) => ViprType::Bool, + Literal::Char(_) => ViprType::Char, + Literal::String(_) => ViprType::String, + }, + Expr::Identifier(name) => match self.lookup(name).expect("ICE: Variable not found in codegen (semantic bug)") { + SymbolInfo::Variable(ty) => ty.clone(), + _ => panic!("Expected variable"), + }, + Expr::BinaryOp { left, op, .. } => match op { + BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Lte | BinaryOp::Gte + | BinaryOp::Eq | BinaryOp::Neq | BinaryOp::And | BinaryOp::Or => ViprType::Bool, + _ => self.type_of(left), + }, + Expr::UnaryOp { op, expr } => match op { + UnaryOp::Not => ViprType::Bool, + UnaryOp::Neg => self.type_of(expr), + }, + Expr::FunctionCall { name, .. } => match self.lookup(name).expect("ICE: Function not found in codegen (semantic bug)") { + SymbolInfo::Function(ty) => ty.clone(), + _ => panic!("Expected function"), + }, + } } - // ── Mapping helpers ────────────────────────────────────── - - fn type_to_c(t: &ViprType) -> &'static str { - match t { - ViprType::Int => "int32_t", - ViprType::Float => "float", - ViprType::Bool => "bool", - ViprType::Char => "char", + fn vipr_type_to_c(&self, ty: &ViprType) -> &'static str { + match ty { + ViprType::Int => "int32_t", + ViprType::Float => "float", + ViprType::Bool => "bool", + ViprType::Char => "char", ViprType::String => "const char*", - ViprType::Void => "void", + ViprType::Void => "void", } } - fn binop_to_c(op: &BinaryOp) -> &'static str { - match op { - BinaryOp::Add => "+", - BinaryOp::Sub => "-", - BinaryOp::Mul => "*", - BinaryOp::Div => "/", - BinaryOp::Mod => "%", - BinaryOp::Eq => "==", - BinaryOp::Neq => "!=", - BinaryOp::Lt => "<", - BinaryOp::Gt => ">", - BinaryOp::Lte => "<=", - BinaryOp::Gte => ">=", - BinaryOp::And => "&&", - BinaryOp::Or => "||", - } + fn next_temp(&mut self) -> String { + self.temp_counter += 1; + format!("_temp{}", self.temp_counter) } - fn unaryop_to_c(op: &UnaryOp) -> &'static str { - match op { - UnaryOp::Not => "!", - UnaryOp::Neg => "-", + pub fn generate(&mut self, program: &Program) -> String { + self.buf.push_str("#include \n"); + self.buf.push_str("#include \n"); + self.buf.push_str("#include \n\n"); + + // Pass 1: Register all globals to support out-of-order calls + for decl in &program.declarations { + match decl { + TopLevel::Function { name, return_type, .. } => { + self.insert_function(name.clone(), return_type.clone()); + } + TopLevel::GlobalLet { identifiers, vipr_type, .. } => { + for id in identifiers { + self.insert_variable(id.clone(), vipr_type.clone()); + } + } + TopLevel::GlobalConst { identifiers, vipr_type, .. } => { + for id in identifiers { + self.insert_variable(id.clone(), vipr_type.clone()); + } + } + } } - } - /// Re-escape a char value back to its C literal representation. - fn escape_char(c: char) -> String { - match c { - '\n' => "\\n".to_string(), - '\t' => "\\t".to_string(), - '\\' => "\\\\".to_string(), - '\'' => "\\'".to_string(), - '"' => "\\\"".to_string(), - c => c.to_string(), + // Pass 2: Global variables and constants + for decl in &program.declarations { + match decl { + TopLevel::GlobalLet { identifiers, vipr_type, initializers } => { + let c_ty = self.vipr_type_to_c(vipr_type); + if let Some(inits) = initializers { + for (id, init) in identifiers.iter().zip(inits.iter()) { + let init_c = self.gen_expr(init); + self.buf.push_str(&format!("{} {} = {};\n", c_ty, id, init_c)); + } + } else { + for id in identifiers { + self.buf.push_str(&format!("{} {};\n", c_ty, id)); + } + } + } + TopLevel::GlobalConst { identifiers, vipr_type, initializers } => { + let c_ty = self.vipr_type_to_c(vipr_type); + for (id, init) in identifiers.iter().zip(initializers.iter()) { + let init_c = self.gen_expr(init); + self.buf.push_str(&format!("const {} {} = {};\n", c_ty, id, init_c)); + } + } + _ => {} + } } - } - /// Re-escape a full string for use inside `"…"` in C. - fn escape_string(s: &str) -> String { - s.chars().map(Self::escape_char).collect() - } + // Pass 3: Function Prototypes + for decl in &program.declarations { + if let TopLevel::Function { name, parameters, return_type, .. } = decl { + if name == "main" { + self.buf.push_str("int main(void);\n"); + } else { + let c_ret_ty = self.vipr_type_to_c(return_type); + let mut param_strs = Vec::new(); + for (p_name, p_ty) in parameters { + param_strs.push(format!("{} {}", self.vipr_type_to_c(p_ty), p_name)); + } + let params_c = if param_strs.is_empty() { + "void".to_string() + } else { + param_strs.join(", ") + }; + self.buf.push_str(&format!("{} {}({});\n", c_ret_ty, name, params_c)); + } + } + } - // ── Boilerplate ────────────────────────────────────────── - - fn emit_boilerplate(&mut self) { - self.emit("#include \n"); - self.emit("#include \n"); - self.emit("#include \n"); - self.emit("\n"); - - // vipr_print — type-dispatched printf via C11 _Generic - self.emit("/* Built-in: vipr_print / vipr_scan */\n"); - self.emit("#define vipr_print(x) _Generic((x), \\\n"); - self.emit(" int: printf(\"%d\\n\", (x)), \\\n"); - self.emit(" float: printf(\"%f\\n\", (x)), \\\n"); - self.emit(" char: printf(\"%c\\n\", (x)), \\\n"); - self.emit(" _Bool: printf(\"%s\\n\", (x) ? \"true\" : \"false\"), \\\n"); - self.emit(" default: printf(\"%s\\n\", (x)) \\\n"); - self.emit(")\n"); - self.emit("\n"); - - // vipr_scan — type-dispatched scanf via C11 _Generic - self.emit("#define vipr_scan(x) _Generic((x), \\\n"); - self.emit(" int: scanf(\"%d\", &(x)), \\\n"); - self.emit(" float: scanf(\"%f\", &(x)), \\\n"); - self.emit(" char: scanf(\" %c\", &(x)), \\\n"); - self.emit(" default: scanf(\"%s\", (x)) \\\n"); - self.emit(")\n"); - self.emit("\n"); - } + self.buf.push_str("\n"); - // ── Declarations ───────────────────────────────────────── - - fn gen_decl(&mut self, decl: &Decl) { - match decl { - Decl::Variable { - is_const, - vipr_type, - name, - initializer, - } => { - self.emit_indent(); - if *is_const { - self.emit("const "); - } - self.emit(Self::type_to_c(vipr_type)); - self.emit(" "); - self.emit(name); - if let Some(expr) = initializer { - self.emit(" = "); - self.gen_expr(expr); + // Pass 4: Function Bodies + for decl in &program.declarations { + if let TopLevel::Function { name, parameters, return_type, body } = decl { + self.enter_scope(); + for (p_name, p_ty) in parameters { + self.insert_variable(p_name.clone(), p_ty.clone()); } - self.emit(";\n"); - } - Decl::Function { - name, - parameters, - return_type, - body, - } => { - self.emit_indent(); - self.emit(Self::type_to_c(return_type)); - self.emit(" "); - self.emit(name); - self.emit("("); - for (i, (pt, pn)) in parameters.iter().enumerate() { - if i > 0 { - self.emit(", "); + if name == "main" { + self.buf.push_str("int main(void) {\n"); + if let Stmt::Block(stmts) = body { + for s in stmts { + self.gen_stmt(s); + } + } else { + self.gen_stmt(body); } - self.emit(Self::type_to_c(pt)); - self.emit(" "); - self.emit(pn); + self.buf.push_str("return 0;\n}\n"); + } else { + let c_ret_ty = self.vipr_type_to_c(return_type); + let mut param_strs = Vec::new(); + for (p_name, p_ty) in parameters { + param_strs.push(format!("{} {}", self.vipr_type_to_c(p_ty), p_name)); + } + let params_c = if param_strs.is_empty() { + "void".to_string() + } else { + param_strs.join(", ") + }; + self.buf.push_str(&format!("{} {}({}) ", c_ret_ty, name, params_c)); + self.gen_stmt(body); + self.buf.push_str("\n"); } - self.emit(") "); - self.gen_stmt(body); + self.exit_scope(); } } - } - // ── Statements ─────────────────────────────────────────── + self.buf.clone() + } fn gen_stmt(&mut self, stmt: &Stmt) { match stmt { - Stmt::Block(stmts) => { - self.emit("{\n"); - self.indent(); - for s in stmts { - self.gen_stmt(s); + Stmt::Let { identifiers, vipr_type, initializers } => { + let c_ty = self.vipr_type_to_c(vipr_type); + for id in identifiers { + self.insert_variable(id.clone(), vipr_type.clone()); + } + if let Some(inits) = initializers { + let mut temps = Vec::new(); + for init in inits { + let temp = self.next_temp(); + let expr_c = self.gen_expr(init); + self.buf.push_str(&format!("{} {} = {};\n", c_ty, temp, expr_c)); + temps.push(temp); + } + for (id, temp) in identifiers.iter().zip(temps.iter()) { + self.buf.push_str(&format!("{} {} = {};\n", c_ty, id, temp)); + } + } else { + for id in identifiers { + self.buf.push_str(&format!("{} {};\n", c_ty, id)); + } } - self.dedent(); - self.emit_indent(); - self.emit("}\n"); - } - - Stmt::ExpressionStmt(expr) => { - self.emit_indent(); - self.gen_expr(expr); - self.emit(";\n"); } - - Stmt::VariableDecl { - is_const, - vipr_type, - name, - initializer, - } => { - self.emit_indent(); - if *is_const { - self.emit("const "); + Stmt::Const { identifiers, vipr_type, initializers } => { + let c_ty = self.vipr_type_to_c(vipr_type); + for id in identifiers { + self.insert_variable(id.clone(), vipr_type.clone()); } - self.emit(Self::type_to_c(vipr_type)); - self.emit(" "); - self.emit(name); - if let Some(expr) = initializer { - self.emit(" = "); - self.gen_expr(expr); + let mut temps = Vec::new(); + for init in initializers { + let temp = self.next_temp(); + let expr_c = self.gen_expr(init); + self.buf.push_str(&format!("{} {} = {};\n", c_ty, temp, expr_c)); + temps.push(temp); + } + for (id, temp) in identifiers.iter().zip(temps.iter()) { + self.buf.push_str(&format!("const {} {} = {};\n", c_ty, id, temp)); } - self.emit(";\n"); } - - Stmt::Assignment { identifier, value } => { - self.emit_indent(); - self.emit(identifier); - self.emit(" = "); - self.gen_expr(value); - self.emit(";\n"); + Stmt::Assignment { targets, values } => { + let mut temps = Vec::new(); + for val in values { + let temp = self.next_temp(); + let expr_c = self.gen_expr(val); + let ty = self.type_of(val); + let c_ty = self.vipr_type_to_c(&ty); + self.buf.push_str(&format!("{} {} = {};\n", c_ty, temp, expr_c)); + temps.push(temp); + } + for (target, temp) in targets.iter().zip(temps.iter()) { + self.buf.push_str(&format!("{} = {};\n", target, temp)); + } } - - Stmt::If { - condition, - then_branch, - else_if_branches, - else_branch, - } => { - self.emit_indent(); - self.emit("if ("); - self.gen_expr(condition); - self.emit(") "); - self.gen_stmt(then_branch); - - for (elif_cond, elif_body) in else_if_branches { - self.emit_indent(); - self.emit("else if ("); - self.gen_expr(elif_cond); - self.emit(") "); + Stmt::If { condition, body, elif_branches, else_branch } => { + let cond_c = self.gen_expr(condition); + self.buf.push_str(&format!("if ({}) ", cond_c)); + self.gen_stmt(body); + for (elif_cond, elif_body) in elif_branches { + let elif_cond_c = self.gen_expr(elif_cond); + self.buf.push_str(&format!("else if ({}) ", elif_cond_c)); self.gen_stmt(elif_body); } - - if let Some(else_body) = else_branch { - self.emit_indent(); - self.emit("else "); - self.gen_stmt(else_body); + if let Some(else_b) = else_branch { + self.buf.push_str("else "); + self.gen_stmt(else_b); } } - Stmt::While { condition, body } => { - self.emit_indent(); - self.emit("while ("); - self.gen_expr(condition); - self.emit(") "); + let cond_c = self.gen_expr(condition); + self.buf.push_str(&format!("while ({}) ", cond_c)); self.gen_stmt(body); } + Stmt::For { variable, vipr_type, start, end, step, body } => { + self.enter_scope(); + self.insert_variable(variable.clone(), vipr_type.clone()); - Stmt::For { - init, - condition, - increment, - body, - } => { - self.emit_indent(); - self.emit("for ("); - - // Init — inline variable declaration (no semicolon / newline) - if let Stmt::VariableDecl { - is_const, - vipr_type, - name, - initializer, - } = init.as_ref() - { - if *is_const { - self.emit("const "); - } - self.emit(Self::type_to_c(vipr_type)); - self.emit(" "); - self.emit(name); - if let Some(expr) = initializer { - self.emit(" = "); - self.gen_expr(expr); - } - } + let start_temp = self.next_temp(); + let end_temp = self.next_temp(); + let step_temp = self.next_temp(); + + let c_ty = self.vipr_type_to_c(vipr_type); + + let start_expr_c = self.gen_expr(start); + let end_expr_c = self.gen_expr(end); + let step_expr_c = self.gen_expr(step); + + self.buf.push_str(&format!("{} {} = {};\n", c_ty, start_temp, start_expr_c)); + self.buf.push_str(&format!("{} {} = {};\n", c_ty, end_temp, end_expr_c)); + self.buf.push_str(&format!("{} {} = {};\n", c_ty, step_temp, step_expr_c)); - self.emit("; "); - self.gen_expr(condition); - self.emit("; "); + self.buf.push_str(&format!( + "for ({} {} = {}; {} > 0 ? {} < {} : {} > {}; {} += {}) {{\n", + c_ty, variable, start_temp, step_temp, variable, end_temp, variable, end_temp, variable, step_temp + )); - // Increment — inline assignment (no semicolon / newline) - if let Stmt::Assignment { identifier, value } = increment.as_ref() { - self.emit(identifier); - self.emit(" = "); - self.gen_expr(value); + if let Stmt::Block(stmts) = &**body { + for s in stmts { + self.gen_stmt(s); + } + } else { + self.gen_stmt(body); } + self.buf.push_str("}\n"); - self.emit(") "); - self.gen_stmt(body); + self.exit_scope(); } - - Stmt::Return(expr) => { - self.emit_indent(); - match expr { - Some(e) => { - self.emit("return "); - self.gen_expr(e); - self.emit(";\n"); + Stmt::Return(expr_opt) => { + if let Some(expr) = expr_opt { + let expr_c = self.gen_expr(expr); + self.buf.push_str(&format!("return {};\n", expr_c)); + } else { + self.buf.push_str("return;\n"); + } + } + Stmt::Break => self.buf.push_str("break;\n"), + Stmt::Continue => self.buf.push_str("continue;\n"), + Stmt::Pass => self.buf.push_str("{}\n"), + Stmt::Print(exprs) => { + for expr in exprs { + let ty = self.type_of(expr); + let expr_c = self.gen_expr(expr); + match ty { + ViprType::Int => self.buf.push_str(&format!("printf(\"%d\", {});\n", expr_c)), + ViprType::Float => self.buf.push_str(&format!("printf(\"%f\", {});\n", expr_c)), + ViprType::Bool => self.buf.push_str(&format!("printf(\"%s\", ({}) ? \"true\" : \"false\");\n", expr_c)), + ViprType::Char => self.buf.push_str(&format!("printf(\"%c\", {});\n", expr_c)), + ViprType::String => self.buf.push_str(&format!("printf(\"%s\", {});\n", expr_c)), + ViprType::Void => {} } - None => { - self.emit_line("return;"); + } + self.buf.push_str("printf(\"\\n\");\n"); + } + Stmt::Input(ids) => { + let mut fmts = String::new(); + let mut args = String::new(); + for id in ids { + let ty = match self.lookup(id).expect("ICE: Input variable not found in codegen (semantic bug)") { + SymbolInfo::Variable(t) => t, + _ => panic!("Expected variable for input"), + }; + match ty { + ViprType::Int => fmts.push_str("%d "), + ViprType::Float => fmts.push_str("%f "), + ViprType::Bool => fmts.push_str("%d "), + ViprType::Char => fmts.push_str(" %c "), + ViprType::String => fmts.push_str("%s "), + ViprType::Void => {} } + args.push_str(&format!(", &{}", id)); } + let fmts = fmts.trim(); + self.buf.push_str(&format!("scanf(\"{}\"{});\n", fmts, args)); + } + Stmt::ExprStmt(expr) => { + let expr_c = self.gen_expr(expr); + self.buf.push_str(&format!("{};\n", expr_c)); + } + Stmt::Block(stmts) => { + self.enter_scope(); + self.buf.push_str("{\n"); + for s in stmts { + self.gen_stmt(s); + } + self.buf.push_str("}\n"); + self.exit_scope(); } } } - // ── Expressions ────────────────────────────────────────── - - fn gen_expr(&mut self, expr: &Expr) { + fn gen_expr(&mut self, expr: &Expr) -> String { match expr { Expr::Literal(lit) => match lit { - Literal::Int(n) => self.emit(&n.to_string()), - Literal::Float(f) => { - let s = format!("{}", f); - if s.contains('.') { - self.emit(&format!("{}f", s)); - } else { - self.emit(&format!("{}.0f", s)); + Literal::Int(v) => format!("{}", v), + Literal::Float(v) => { + let mut s = format!("{}", v); + if !s.contains('.') { + s.push_str(".0"); } + s } - Literal::Bool(b) => self.emit(if *b { "true" } else { "false" }), - Literal::Char(c) => { - self.emit("'"); - self.emit(&Self::escape_char(*c)); - self.emit("'"); - } - Literal::String(s) => { - self.emit("\""); - self.emit(&Self::escape_string(s)); - self.emit("\""); + Literal::Bool(v) => (if *v { "true" } else { "false" }).to_string(), + Literal::Char(v) => match *v { + '\n' => "'\\n'".to_string(), + '\t' => "'\\t'".to_string(), + '\\' => "'\\\\'".to_string(), + '\'' => "'\\''".to_string(), + '\0' => "'\\0'".to_string(), + _ => format!("'{}'", v), + }, + Literal::String(v) => { + let escaped = v + .replace('\\', "\\\\") + .replace('"', "\\\"") + .replace('\n', "\\n") + .replace('\t', "\\t"); + format!("\"{}\"", escaped) } }, - - Expr::Identifier(name) => self.emit(name), - + Expr::Identifier(name) => name.clone(), Expr::BinaryOp { left, op, right } => { - self.emit("("); - self.gen_expr(left); - self.emit(" "); - self.emit(Self::binop_to_c(op)); - self.emit(" "); - self.gen_expr(right); - self.emit(")"); + let l = self.gen_expr(left); + let r = self.gen_expr(right); + let op_str = match op { + BinaryOp::Add => "+", + BinaryOp::Sub => "-", + BinaryOp::Mul => "*", + BinaryOp::Div => "/", + BinaryOp::Mod => "%", + BinaryOp::Lt => "<", + BinaryOp::Gt => ">", + BinaryOp::Lte => "<=", + BinaryOp::Gte => ">=", + BinaryOp::Eq => "==", + BinaryOp::Neq => "!=", + BinaryOp::And => "&&", + BinaryOp::Or => "||", + }; + format!("({} {} {})", l, op_str, r) } - Expr::UnaryOp { op, expr } => { - self.emit("("); - self.emit(Self::unaryop_to_c(op)); - self.gen_expr(expr); - self.emit(")"); + let e = self.gen_expr(expr); + match op { + UnaryOp::Neg => format!("-({})", e), + UnaryOp::Not => format!("!({})", e), + } } - Expr::FunctionCall { name, args } => { - // Map Vipr builtins to their C macro equivalents. - let c_name = match name.as_str() { - "print" => "vipr_print", - "scan" => "vipr_scan", - _ => name.as_str(), - }; - - self.emit(c_name); - self.emit("("); - for (i, arg) in args.iter().enumerate() { - if i > 0 { - self.emit(", "); - } - self.gen_expr(arg); + let mut arg_strs = Vec::new(); + for arg in args { + arg_strs.push(self.gen_expr(arg)); } - self.emit(")"); + format!("{}({})", name, arg_strs.join(", ")) } } } } -// ── Tests ───────────────────────────────────────────────────── - #[cfg(test)] mod tests { use super::*; #[test] - fn test_generate_main_with_variable() { - // func main() int { int x = 0; return 0; } - let program = Program { - declarations: vec![Decl::Function { - name: "main".to_string(), - parameters: vec![], - return_type: ViprType::Int, - body: Stmt::Block(vec![ - Stmt::VariableDecl { - is_const: false, - vipr_type: ViprType::Int, - name: "x".to_string(), - initializer: Some(Expr::Literal(Literal::Int(0))), - }, - Stmt::Return(Some(Expr::Literal(Literal::Int(0)))), - ]), - }], - }; - - let mut generator = CodeGenerator::new(); - let output = generator.generate(&program); - - assert!( - output.contains("#include "), - "Missing stdio.h include" - ); - assert!( - output.contains("#include "), - "Missing stdbool.h include" - ); - assert!( - output.contains("#include "), - "Missing stdint.h include" - ); - assert!( - output.contains("int32_t main()"), - "Missing main function signature" - ); - assert!( - output.contains("int32_t x = 0;"), - "Missing variable declaration" - ); - assert!( - output.contains("return 0;"), - "Missing return statement" - ); - } - - #[test] - fn test_generate_function_call_and_print() { - // func add(int a, int b) int { return a + b; } - // func main() void { print(add(1, 2)); } - let program = Program { + fn test_codegen_basic_program() { + let ast = Program { declarations: vec![ - Decl::Function { - name: "add".to_string(), - parameters: vec![ - (ViprType::Int, "a".to_string()), - (ViprType::Int, "b".to_string()), - ], - return_type: ViprType::Int, - body: Stmt::Block(vec![Stmt::Return(Some(Expr::BinaryOp { - left: Box::new(Expr::Identifier("a".to_string())), - op: BinaryOp::Add, - right: Box::new(Expr::Identifier("b".to_string())), - }))]), + TopLevel::GlobalConst { + identifiers: vec!["PI".to_string()], + vipr_type: ViprType::Float, + initializers: vec![Expr::Literal(Literal::Float(3.14))], }, - Decl::Function { + TopLevel::Function { name: "main".to_string(), parameters: vec![], return_type: ViprType::Void, - body: Stmt::Block(vec![Stmt::ExpressionStmt(Expr::FunctionCall { - name: "print".to_string(), - args: vec![Expr::FunctionCall { - name: "add".to_string(), - args: vec![ + body: Stmt::Block(vec![ + Stmt::Let { + identifiers: vec!["a".to_string(), "b".to_string()], + vipr_type: ViprType::Int, + initializers: Some(vec![ Expr::Literal(Literal::Int(1)), Expr::Literal(Literal::Int(2)), + ]), + }, + Stmt::Assignment { + targets: vec!["a".to_string(), "b".to_string()], + values: vec![ + Expr::Literal(Literal::Int(3)), + Expr::Literal(Literal::Int(4)), ], - }], - })]), - }, + } + ]), + } ], }; - let mut generator = CodeGenerator::new(); - let output = generator.generate(&program); - - assert!( - output.contains("int32_t add(int32_t a, int32_t b)"), - "Missing add() signature" - ); - assert!( - output.contains("return (a + b);"), - "Missing return expression" - ); - assert!( - output.contains("vipr_print(add(1, 2))"), - "print() should map to vipr_print()" - ); - } - - #[test] - fn test_generate_for_loop() { - // for (int i = 0; i < 10; i = i + 1) { print(i); } - let for_stmt = Stmt::For { - init: Box::new(Stmt::VariableDecl { - is_const: false, - vipr_type: ViprType::Int, - name: "i".to_string(), - initializer: Some(Expr::Literal(Literal::Int(0))), - }), - condition: Expr::BinaryOp { - left: Box::new(Expr::Identifier("i".to_string())), - op: BinaryOp::Lt, - right: Box::new(Expr::Literal(Literal::Int(10))), - }, - increment: Box::new(Stmt::Assignment { - identifier: "i".to_string(), - value: Expr::BinaryOp { - left: Box::new(Expr::Identifier("i".to_string())), - op: BinaryOp::Add, - right: Box::new(Expr::Literal(Literal::Int(1))), - }, - }), - body: Box::new(Stmt::Block(vec![Stmt::ExpressionStmt( - Expr::FunctionCall { - name: "print".to_string(), - args: vec![Expr::Identifier("i".to_string())], - }, - )])), - }; - - let program = Program { - declarations: vec![Decl::Function { - name: "main".to_string(), - parameters: vec![], - return_type: ViprType::Void, - body: Stmt::Block(vec![for_stmt]), - }], - }; - - let mut generator = CodeGenerator::new(); - let output = generator.generate(&program); - - assert!( - output.contains("for (int32_t i = 0; (i < 10); i = (i + 1))"), - "For loop header incorrect. Got:\n{}", - output - ); - assert!( - output.contains("vipr_print(i)"), - "print(i) should map to vipr_print(i)" - ); + let mut codegen = CodeGenerator::new(); + let c_code = codegen.generate(&ast); + + let expected_includes = "#include \n#include \n#include \n"; + assert!(c_code.contains(expected_includes)); + assert!(c_code.contains("const float PI = 3.14;")); + assert!(c_code.contains("int main(void) {\n")); + + // Check temp variables for Let + assert!(c_code.contains("int32_t _temp1 = 1;")); + assert!(c_code.contains("int32_t _temp2 = 2;")); + assert!(c_code.contains("int32_t a = _temp1;")); + assert!(c_code.contains("int32_t b = _temp2;")); + + // Check temp variables for Assignment + assert!(c_code.contains("int32_t _temp3 = 3;")); + assert!(c_code.contains("int32_t _temp4 = 4;")); + assert!(c_code.contains("a = _temp3;")); + assert!(c_code.contains("b = _temp4;")); + + assert!(c_code.contains("return 0;\n}")); } } diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index cfefaec..5f11ed4 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -2,12 +2,28 @@ pub mod token; use token::Token; -/// The Vipr lexer. Converts raw source text into a stream of [`Token`]s. +/// The Vipr v0.2.0 lexer. +/// +/// Converts raw source text into a stream of [`Token`]s, including the +/// structural `Newline`, `Indent`, and `Dedent` tokens required for +/// Python-style indentation-based blocks. pub struct Lexer<'a> { - /// The full source text being lexed. + /// The full source text as a byte slice. source: &'a [u8], - /// Current byte position within `source`. + /// Current byte offset within `source`. pos: usize, + /// Current line number (1-based, for diagnostics). + line: usize, + /// Current column number (1-based, for diagnostics). + col: usize, + /// Stack of indentation levels. Initialised with a single `0`. + indent_stack: Vec, + /// Current parenthesis nesting depth. + paren_depth: usize, + /// Whether any non-structural token has been emitted on the current + /// logical line. Used to decide whether an end-of-line `Newline` should + /// be emitted. + emitted_token_on_line: bool, } impl<'a> Lexer<'a> { @@ -16,73 +32,312 @@ impl<'a> Lexer<'a> { Self { source: source.as_bytes(), pos: 0, + line: 1, + col: 1, + indent_stack: vec![0], + paren_depth: 0, + emitted_token_on_line: false, } } // ── Public API ──────────────────────────────────────────── - /// Tokenize the entire source and return the token list (terminated by `EOF`). + /// Tokenize the entire source and return the token list, terminated by + /// optional trailing `Newline` + `Dedent`(s) + `EOF`. pub fn tokenize(&mut self) -> Result, String> { let mut tokens = Vec::new(); - loop { - self.skip_whitespace_and_comments(); + while !self.is_at_end() { + // ── Start-of-line indentation handling ──────────── + // We process indentation at the beginning of every *physical* + // line. `self.col == 1` indicates we are at the first column. + if self.col == 1 { + self.handle_line_start(&mut tokens)?; + if self.is_at_end() { + break; + } + } + + // ── Skip in-line whitespace (spaces only — tabs cause error) ── + if !self.is_at_end() && self.current() == b' ' { + self.advance(); + continue; + } + if !self.is_at_end() && self.current() == b'\t' { + return Err(format!( + "Tabs are not allowed (line {}, col {})", + self.line, self.col + )); + } + + // ── Newlines ────────────────────────────────────── + if !self.is_at_end() && self.current() == b'\n' { + if self.paren_depth == 0 && self.emitted_token_on_line { + tokens.push(Token::Newline); + } + self.advance_newline(); + continue; + } + + // ── Carriage return (handle \r\n and bare \r) ───── + if !self.is_at_end() && self.current() == b'\r' { + if self.paren_depth == 0 && self.emitted_token_on_line { + tokens.push(Token::Newline); + } + self.advance(); // skip \r + if !self.is_at_end() && self.current() == b'\n' { + self.advance(); // skip \n after \r + } + self.line += 1; + self.col = 1; + self.emitted_token_on_line = false; + continue; + } + // ── Comments ────────────────────────────────────── + if !self.is_at_end() && self.current() == b'/' { + if let Some(next) = self.peek_next() { + if next == b'/' { + self.skip_line_comment(); + continue; + } + if next == b'*' { + self.skip_block_comment()?; + continue; + } + } + } + + // ── EOF check after whitespace / comment skip ───── if self.is_at_end() { - tokens.push(Token::EOF); break; } - let ch = self.current(); - - let token = match ch { - // ── Identifiers & keywords ──────────────── - b'a'..=b'z' | b'A'..=b'Z' | b'_' => self.read_identifier_or_keyword(), - - // ── Numeric literals ────────────────────── - b'0'..=b'9' => self.read_number()?, - - // ── String literals ─────────────────────── - b'"' => self.read_string()?, - - // ── Char literals ───────────────────────── - b'\'' => self.read_char()?, - - // ── Two-character operators ──────────────── - b'=' => self.read_if_next(b'=', Token::EqEq, Token::Assign), - b'!' => self.read_if_next(b'=', Token::NotEq, Token::Not), - b'<' => self.read_if_next(b'=', Token::LessEq, Token::Less), - b'>' => self.read_if_next(b'=', Token::GreaterEq, Token::Greater), - b'&' => self.expect_double(b'&', Token::And, "&&")?, - b'|' => self.expect_double(b'|', Token::Or, "||")?, - - // ── Single-character operators ──────────── - b'+' => { self.advance(); Token::Plus } - b'-' => { self.advance(); Token::Minus } - b'*' => { self.advance(); Token::Star } - b'/' => { self.advance(); Token::Slash } - b'%' => { self.advance(); Token::Percent } - - // ── Punctuation ─────────────────────────── - b'(' => { self.advance(); Token::LParen } - b')' => { self.advance(); Token::RParen } - b'{' => { self.advance(); Token::LBrace } - b'}' => { self.advance(); Token::RBrace } - b';' => { self.advance(); Token::Semicolon } - b',' => { self.advance(); Token::Comma } - - _ => { + // ── Scan a real token ───────────────────────────── + let tok = self.scan_token()?; + tokens.push(tok); + self.emitted_token_on_line = true; + } + + // ── End-of-stream finalisation ──────────────────────── + if self.emitted_token_on_line { + tokens.push(Token::Newline); + self.emitted_token_on_line = false; + } + + // Pop remaining indentation levels back to 0. + while *self.indent_stack.last().expect("ICE: Indent stack empty") > 0 { + self.indent_stack.pop(); + tokens.push(Token::Dedent); + } + + tokens.push(Token::EOF); + Ok(tokens) + } + + // ── Indentation handling ────────────────────────────────── + + /// Process the leading whitespace at the beginning of a physical line. + /// + /// This may emit `Indent`, `Dedent`, and/or `Newline` tokens. Blank + /// lines and comment-only lines are silently skipped without affecting + /// the indentation stack. + fn handle_line_start(&mut self, tokens: &mut Vec) -> Result<(), String> { + // Count leading spaces. + let mut spaces = 0usize; + while !self.is_at_end() && self.current() == b' ' { + spaces += 1; + self.advance(); + } + + // Tabs in indentation prefix → error. + if !self.is_at_end() && self.current() == b'\t' { + return Err(format!( + "Tabs are not allowed in indentation (line {})", + self.line + )); + } + + // ── Blank line or comment-only line → skip entirely ── + if self.is_at_end() + || self.current() == b'\n' + || self.current() == b'\r' + || self.is_comment_start() + { + // If it's a comment-only line, check if there is anything + // after the comment on the same physical line. + if self.is_comment_start() { + if self.current() == b'/' { + if let Some(b'/') = self.peek_next() { + // Single-line comment — definitely comment-only. + // Leave the comment for the main loop to skip. + return Ok(()); + } + if let Some(b'*') = self.peek_next() { + // Block comment — skip it and re-check. + let save_line = self.line; + self.skip_block_comment()?; + // If the block comment ended on a different line, + // the col has been reset; the main loop will re-enter + // handle_line_start on that new line. Otherwise, + // fall through and re-check what follows. + if self.line != save_line { + return Ok(()); + } + // After the block comment, skip any inline spaces + // and re-check if remaining line is blank/comment. + while !self.is_at_end() && self.current() == b' ' { + self.advance(); + } + if self.is_at_end() + || self.current() == b'\n' + || self.current() == b'\r' + { + return Ok(()); + } + if self.is_comment_start() { + return Ok(()); + } + // There is real code after the block comment on the + // same line — fall through to indentation logic. + } + } + } else { + // Blank line or bare newline — skip. + return Ok(()); + } + } + + // ── Inside parentheses → no indentation processing ─── + if self.paren_depth > 0 { + return Ok(()); + } + + // ── Compare against indentation stack ──────────────── + let current_indent = *self.indent_stack.last().expect("ICE: Indent stack empty"); + + if spaces > current_indent { + self.indent_stack.push(spaces); + tokens.push(Token::Indent); + } else if spaces < current_indent { + while *self.indent_stack.last().expect("ICE: Indent stack empty") > spaces { + self.indent_stack.pop(); + tokens.push(Token::Dedent); + } + if *self.indent_stack.last().expect("ICE: Indent stack empty") != spaces { + return Err(format!( + "Invalid dedent on line {}: indentation level {} does not match any outer block", + self.line, spaces + )); + } + } + // spaces == current_indent → no token needed. + + Ok(()) + } + + // ── Token scanning ──────────────────────────────────────── + + /// Scan and return a single token starting at the current position. + fn scan_token(&mut self) -> Result { + let ch = self.current(); + + match ch { + // ── Identifiers & keywords ──────────────────── + b'a'..=b'z' | b'A'..=b'Z' | b'_' => Ok(self.read_identifier_or_keyword()), + + // ── Numeric literals ────────────────────────── + b'0'..=b'9' => self.read_number(), + + // ── String literals ─────────────────────────── + b'"' => self.read_string(), + + // ── Char literals ───────────────────────────── + b'\'' => self.read_char(), + + // ── Two-character operators & arrow ─────────── + b'=' => { + self.advance(); + if !self.is_at_end() && self.current() == b'=' { + self.advance(); + Ok(Token::EqEq) + } else { + Ok(Token::Assign) + } + } + b'!' => { + self.advance(); + if !self.is_at_end() && self.current() == b'=' { + self.advance(); + Ok(Token::NotEq) + } else { + Err(format!( + "Unexpected character '!' at line {}, col {} (did you mean '!='?)", + self.line, + self.col - 1 + )) + } + } + b'<' => { + self.advance(); + if !self.is_at_end() && self.current() == b'=' { + self.advance(); + Ok(Token::LessEq) + } else { + Ok(Token::Less) + } + } + b'>' => { + self.advance(); + if !self.is_at_end() && self.current() == b'=' { + self.advance(); + Ok(Token::GreaterEq) + } else { + Ok(Token::Greater) + } + } + b'-' => { + self.advance(); + if !self.is_at_end() && self.current() == b'>' { + self.advance(); + Ok(Token::Arrow) + } else { + Ok(Token::Minus) + } + } + + // ── Single-character operators ──────────────── + b'+' => { self.advance(); Ok(Token::Plus) } + b'*' => { self.advance(); Ok(Token::Star) } + b'/' => { self.advance(); Ok(Token::Slash) } + b'%' => { self.advance(); Ok(Token::Percent) } + + // ── Punctuation ────────────────────────────── + b'(' => { + self.advance(); + self.paren_depth += 1; + Ok(Token::LParen) + } + b')' => { + self.advance(); + if self.paren_depth == 0 { return Err(format!( - "Unexpected character '{}' at position {}", - ch as char, self.pos + "Unmatched ')' at line {}, col {}", + self.line, + self.col - 1 )); } - }; + self.paren_depth -= 1; + Ok(Token::RParen) + } + b':' => { self.advance(); Ok(Token::Colon) } + b',' => { self.advance(); Ok(Token::Comma) } - tokens.push(token); + _ => Err(format!( + "Unexpected character '{}' at line {}, col {}", + ch as char, self.line, self.col + )), } - - Ok(tokens) } // ── Helpers: navigation ─────────────────────────────────── @@ -102,66 +357,82 @@ impl<'a> Lexer<'a> { self.source.get(self.pos + 1).copied() } - /// Advances the position by one byte. + /// Advances the position by one byte (non-newline). fn advance(&mut self) { self.pos += 1; + self.col += 1; } - // ── Helpers: skipping ───────────────────────────────────── - - /// Skips whitespace and all comment forms. - fn skip_whitespace_and_comments(&mut self) { - while !self.is_at_end() { - let ch = self.current(); - - // Plain whitespace - if ch.is_ascii_whitespace() { - self.advance(); - continue; - } - - // Comments start with '/' - if ch == b'/' { - if let Some(next) = self.peek_next() { - if next == b'/' { - self.skip_line_comment(); - continue; - } - if next == b'*' { - self.skip_block_comment(); - continue; - } - } - } + /// Advances past a newline (`\n`), updating line/col tracking. + fn advance_newline(&mut self) { + self.pos += 1; + self.line += 1; + self.col = 1; + self.emitted_token_on_line = false; + } - break; + /// Returns `true` if the current position starts a comment (`//` or `/*`). + fn is_comment_start(&self) -> bool { + if self.is_at_end() || self.current() != b'/' { + return false; + } + match self.peek_next() { + Some(b'/') | Some(b'*') => true, + _ => false, } } - /// Skips a `// …` comment until end-of-line. + // ── Helpers: skipping ───────────────────────────────────── + + /// Skips a `// …` comment until end-of-line (or end-of-file). fn skip_line_comment(&mut self) { - // Skip the two '/' characters. - self.advance(); - self.advance(); - while !self.is_at_end() && self.current() != b'\n' { + self.advance(); // skip first '/' + self.advance(); // skip second '/' + while !self.is_at_end() && self.current() != b'\n' && self.current() != b'\r' { self.advance(); } } /// Skips a `/* … */` block comment. - fn skip_block_comment(&mut self) { - // Skip '/*'. - self.advance(); - self.advance(); - while !self.is_at_end() { + /// + /// Block comments do not nest. The lexer scans from `/*` to the first + /// following `*/`. + fn skip_block_comment(&mut self) -> Result<(), String> { + let start_line = self.line; + let start_col = self.col; + + self.advance(); // skip '/' + self.advance(); // skip '*' + + loop { + if self.is_at_end() { + return Err(format!( + "Unterminated block comment starting at line {}, col {}", + start_line, start_col + )); + } + if self.current() == b'*' { if let Some(b'/') = self.peek_next() { self.advance(); // skip '*' self.advance(); // skip '/' - return; + return Ok(()); } } - self.advance(); + + if self.current() == b'\n' { + self.advance_newline(); + } else if self.current() == b'\r' { + self.advance(); + if !self.is_at_end() && self.current() == b'\n' { + self.pos += 1; // skip \n (don't double-advance col) + } + self.line += 1; + self.col = 1; + self.emitted_token_on_line = false; + } else { + self.advance(); + } } } @@ -173,7 +444,7 @@ impl<'a> Lexer<'a> { while !self.is_at_end() && self.is_ident_char(self.current()) { self.advance(); } - let text = std::str::from_utf8(&self.source[start..self.pos]).unwrap(); + let text = std::str::from_utf8(&self.source[start..self.pos]).expect("ICE: Invalid UTF-8 in source"); Self::keyword_or_ident(text) } @@ -186,25 +457,36 @@ impl<'a> Lexer<'a> { fn keyword_or_ident(word: &str) -> Token { match word { // Keywords - "func" => Token::Func, - "return" => Token::Return, - "if" => Token::If, - "else" => Token::Else, - "for" => Token::For, - "while" => Token::While, - "const" => Token::Const, + "let" => Token::Let, + "const" => Token::Const, + "def" => Token::Def, + "return" => Token::Return, + "if" => Token::If, + "else" => Token::Else, + "elif" => Token::Elif, + "for" => Token::For, + "while" => Token::While, + "break" => Token::Break, + "continue" => Token::Continue, + "pass" => Token::Pass, + "and" => Token::And, + "or" => Token::Or, + "not" => Token::Not, + "in" => Token::In, + "range" => Token::Range, + "true" => Token::True, + "false" => Token::False, + "print" => Token::Print, + "input" => Token::Input, // Primitive types - "int" => Token::IntType, - "float" => Token::FloatType, - "bool" => Token::BoolType, - "char" => Token::CharType, - "string" => Token::StringType, - "void" => Token::VoidType, - // Boolean literals - "true" => Token::True, - "false" => Token::False, - // Identifier - _ => Token::Identifier(word.to_string()), + "int" => Token::IntType, + "float" => Token::FloatType, + "bool" => Token::BoolType, + "char" => Token::CharType, + "string" => Token::StringType, + "void" => Token::VoidType, + // Identifiers + _ => Token::Identifier(word.to_string()), } } @@ -219,26 +501,31 @@ impl<'a> Lexer<'a> { // Check for a decimal part. if !self.is_at_end() && self.current() == b'.' { - self.advance(); // skip '.' + // Peek ahead: if the next char after '.' is a digit, it's a float. + if let Some(next) = self.peek_next() { + if next.is_ascii_digit() { + self.advance(); // skip '.' - if self.is_at_end() || !self.current().is_ascii_digit() { - return Err(format!( - "Expected digit after decimal point at position {}", - self.pos - )); - } + while !self.is_at_end() && self.current().is_ascii_digit() { + self.advance(); + } - while !self.is_at_end() && self.current().is_ascii_digit() { - self.advance(); + let text = std::str::from_utf8(&self.source[start..self.pos]).expect("ICE: Invalid UTF-8 in source"); + let value: f32 = text + .parse() + .map_err(|e| format!("Invalid float '{}': {} (line {})", text, e, self.line))?; + return Ok(Token::FloatLiteral(value)); + } } - - let text = std::str::from_utf8(&self.source[start..self.pos]).unwrap(); - let value: f32 = text.parse().map_err(|e| format!("Invalid float: {}", e))?; - return Ok(Token::FloatLiteral(value)); + // A trailing dot with no digit after it (e.g. `1.`) is invalid per + // spec, but we treat the integer part as a valid integer and leave + // the dot for the next scan pass (which will error on it). } - let text = std::str::from_utf8(&self.source[start..self.pos]).unwrap(); - let value: i32 = text.parse().map_err(|e| format!("Invalid integer: {}", e))?; + let text = std::str::from_utf8(&self.source[start..self.pos]).expect("ICE: Invalid UTF-8 in source"); + let value: i32 = text + .parse() + .map_err(|e| format!("Invalid integer '{}': {} (line {})", text, e, self.line))?; Ok(Token::IntLiteral(value)) } @@ -249,6 +536,12 @@ impl<'a> Lexer<'a> { let mut value = String::new(); while !self.is_at_end() && self.current() != b'"' { + if self.current() == b'\n' || self.current() == b'\r' { + return Err(format!( + "Unterminated string literal at line {}", + self.line + )); + } if self.current() == b'\\' { self.advance(); value.push(self.read_escape()?); @@ -259,7 +552,10 @@ impl<'a> Lexer<'a> { } if self.is_at_end() { - return Err("Unterminated string literal".to_string()); + return Err(format!( + "Unterminated string literal at line {}", + self.line + )); } self.advance(); // skip closing '"' @@ -271,7 +567,10 @@ impl<'a> Lexer<'a> { self.advance(); // skip opening '\'' if self.is_at_end() { - return Err("Unterminated character literal".to_string()); + return Err(format!( + "Unterminated character literal at line {}", + self.line + )); } let ch = if self.current() == b'\\' { @@ -284,7 +583,10 @@ impl<'a> Lexer<'a> { }; if self.is_at_end() || self.current() != b'\'' { - return Err("Unterminated character literal — expected closing '".to_string()); + return Err(format!( + "Unterminated character literal — expected closing ' at line {}", + self.line + )); } self.advance(); // skip closing '\'' @@ -292,9 +594,14 @@ impl<'a> Lexer<'a> { } /// Interprets the byte after a backslash as an escape character. + /// + /// Supports: `\n`, `\t`, `\\`, `\"`, `\'`, `\0`. fn read_escape(&mut self) -> Result { if self.is_at_end() { - return Err("Unexpected end of input in escape sequence".to_string()); + return Err(format!( + "Unexpected end of input in escape sequence at line {}", + self.line + )); } let ch = match self.current() { @@ -303,10 +610,11 @@ impl<'a> Lexer<'a> { b'\\' => '\\', b'\'' => '\'', b'"' => '"', + b'0' => '\0', other => { return Err(format!( - "Unknown escape sequence '\\{}' at position {}", - other as char, self.pos + "Unknown escape sequence '\\{}' at line {}, col {}", + other as char, self.line, self.col )); } }; @@ -314,46 +622,6 @@ impl<'a> Lexer<'a> { self.advance(); Ok(ch) } - - // ── Helpers: operator lookahead ─────────────────────────── - - /// If the next byte matches `expected`, consume both and return `matched`; - /// otherwise consume only the current byte and return `fallback`. - fn read_if_next(&mut self, expected: u8, matched: Token, fallback: Token) -> Token { - self.advance(); // consume current - if !self.is_at_end() && self.current() == expected { - self.advance(); - matched - } else { - fallback - } - } - - /// Expects a doubled character (e.g. `&&`, `||`). Returns an error if the - /// second character is missing. - fn expect_double( - &mut self, - expected: u8, - token: Token, - display: &str, - ) -> Result { - self.advance(); // consume first - if !self.is_at_end() && self.current() == expected { - self.advance(); - Ok(token) - } else { - Err(format!( - "Expected '{}' at position {} but found '{}'", - display, - self.pos, - if self.is_at_end() { - "EOF".to_string() - } else { - (self.current() as char).to_string() - } - )) - } - } } // ── Tests ───────────────────────────────────────────────────── @@ -368,85 +636,281 @@ mod tests { Lexer::new(source).tokenize().unwrap() } + // ── Basic indentation blocks ────────────────────────────── + #[test] - fn test_keywords_and_identifiers() { - let tokens = lex("func main return if else for while const"); + fn test_simple_indent_dedent() { + // A minimal function body. + let source = "\ +def main() -> void: + pass +"; + let tokens = lex(source); assert_eq!( tokens, vec![ - Token::Func, + Token::Def, Token::Identifier("main".into()), - Token::Return, + Token::LParen, + Token::RParen, + Token::Arrow, + Token::VoidType, + Token::Colon, + Token::Newline, + Token::Indent, + Token::Pass, + Token::Newline, + Token::Dedent, + Token::EOF, + ] + ); + } + + #[test] + fn test_nested_indent() { + let source = "\ +if true: + if false: + pass +"; + let tokens = lex(source); + assert_eq!( + tokens, + vec![ Token::If, - Token::Else, - Token::For, - Token::While, - Token::Const, + Token::True, + Token::Colon, + Token::Newline, + Token::Indent, + Token::If, + Token::False, + Token::Colon, + Token::Newline, + Token::Indent, + Token::Pass, + Token::Newline, + Token::Dedent, + Token::Dedent, Token::EOF, ] ); + } - // Type keywords - let tokens = lex("int float bool char string void"); + #[test] + fn test_indent_with_code_after() { + let source = "\ +if true: + let x: int = 1 +let y: int = 2 +"; + let tokens = lex(source); assert_eq!( tokens, vec![ + Token::If, + Token::True, + Token::Colon, + Token::Newline, + Token::Indent, + Token::Let, + Token::Identifier("x".into()), + Token::Colon, Token::IntType, - Token::FloatType, - Token::BoolType, - Token::CharType, - Token::StringType, - Token::VoidType, + Token::Assign, + Token::IntLiteral(1), + Token::Newline, + Token::Dedent, + Token::Let, + Token::Identifier("y".into()), + Token::Colon, + Token::IntType, + Token::Assign, + Token::IntLiteral(2), + Token::Newline, + Token::EOF, + ] + ); + } + + // ── Empty / comment-only lines ──────────────────────────── + + #[test] + fn test_blank_lines_ignored() { + let source = "\ +let x: int = 1 + +let y: int = 2 +"; + let tokens = lex(source); + assert_eq!( + tokens, + vec![ + Token::Let, + Token::Identifier("x".into()), + Token::Colon, + Token::IntType, + Token::Assign, + Token::IntLiteral(1), + Token::Newline, + Token::Let, + Token::Identifier("y".into()), + Token::Colon, + Token::IntType, + Token::Assign, + Token::IntLiteral(2), + Token::Newline, Token::EOF, ] ); + } - // Boolean literals and identifiers - let tokens = lex("true false myVar _hidden counter1"); + #[test] + fn test_comment_only_lines_ignored() { + let source = "\ +if true: + // a comment + pass +"; + let tokens = lex(source); assert_eq!( tokens, vec![ + Token::If, Token::True, - Token::False, - Token::Identifier("myVar".into()), - Token::Identifier("_hidden".into()), - Token::Identifier("counter1".into()), + Token::Colon, + Token::Newline, + Token::Indent, + Token::Pass, + Token::Newline, + Token::Dedent, Token::EOF, ] ); } #[test] - fn test_numbers_and_strings() { - let tokens = lex("42 3.14 0 \"hello\" 'A' '\\n'"); + fn test_trailing_comment_preserves_newline() { + let source = "let x: int = 5 // comment\n"; + let tokens = lex(source); assert_eq!( tokens, vec![ - Token::IntLiteral(42), - Token::FloatLiteral(3.14), - Token::IntLiteral(0), - Token::StringLiteral("hello".into()), - Token::CharLiteral('A'), - Token::CharLiteral('\n'), + Token::Let, + Token::Identifier("x".into()), + Token::Colon, + Token::IntType, + Token::Assign, + Token::IntLiteral(5), + Token::Newline, Token::EOF, ] ); + } + + // ── Parenthesised multi-line expressions ────────────────── - // String with escape sequences - let tokens = lex(r#""line1\nline2\t!""#); + #[test] + fn test_paren_suppresses_newline_and_indent() { + let source = "\ +let x: int = ( + 1 + 2 +) +"; + let tokens = lex(source); assert_eq!( tokens, vec![ - Token::StringLiteral("line1\nline2\t!".into()), + Token::Let, + Token::Identifier("x".into()), + Token::Colon, + Token::IntType, + Token::Assign, + Token::LParen, + Token::IntLiteral(1), + Token::Plus, + Token::IntLiteral(2), + Token::RParen, + Token::Newline, Token::EOF, ] ); } + // ── Invalid dedent ──────────────────────────────────────── + + #[test] + fn test_invalid_dedent_error() { + // 4-space indent, then 2-space indent which doesn't match. + let source = "\ +if true: + pass + pass +"; + let result = Lexer::new(source).tokenize(); + assert!(result.is_err(), "Expected an error for invalid dedent"); + let err = result.unwrap_err(); + assert!( + err.contains("dedent") || err.contains("indentation"), + "Error should mention indentation, got: {}", + err + ); + } + + // ── Tabs are rejected ───────────────────────────────────── + #[test] - fn test_operators_and_comments() { - // All operators and punctuation - let tokens = lex("+ - * / % == != < > <= >= && || ! = ( ) { } ; ,"); + fn test_tabs_rejected() { + let source = "if true:\n\tpass\n"; + let result = Lexer::new(source).tokenize(); + assert!(result.is_err()); + assert!( + result.unwrap_err().contains("Tab"), + "Error should mention tabs" + ); + } + + // ── Keywords ────────────────────────────────────────────── + + #[test] + fn test_all_keywords() { + let source = "let const def return if else elif for while break continue pass and or not in range true false print input"; + let tokens = lex(source); + assert_eq!( + tokens, + vec![ + Token::Let, + Token::Const, + Token::Def, + Token::Return, + Token::If, + Token::Else, + Token::Elif, + Token::For, + Token::While, + Token::Break, + Token::Continue, + Token::Pass, + Token::And, + Token::Or, + Token::Not, + Token::In, + Token::Range, + Token::True, + Token::False, + Token::Print, + Token::Input, + Token::Newline, + Token::EOF, + ] + ); + } + + // ── Operators and punctuation ───────────────────────────── + + #[test] + fn test_operators_and_punctuation() { + let source = "+ - * / % == != < > <= >= = -> ( ) : ,"; + let tokens = lex(source); assert_eq!( tokens, vec![ @@ -461,42 +925,125 @@ mod tests { Token::Greater, Token::LessEq, Token::GreaterEq, - Token::And, - Token::Or, - Token::Not, Token::Assign, + Token::Arrow, Token::LParen, Token::RParen, - Token::LBrace, - Token::RBrace, - Token::Semicolon, + Token::Colon, Token::Comma, + Token::Newline, Token::EOF, ] ); + } - // Single-line comment is fully skipped - let tokens = lex("int x; // this is a comment\nreturn;"); + // ── Literals ────────────────────────────────────────────── + + #[test] + fn test_numeric_literals() { + let source = "42 3.14 0"; + let tokens = lex(source); assert_eq!( tokens, vec![ - Token::IntType, - Token::Identifier("x".into()), - Token::Semicolon, - Token::Return, - Token::Semicolon, + Token::IntLiteral(42), + Token::FloatLiteral(3.14), + Token::IntLiteral(0), + Token::Newline, Token::EOF, ] ); + } - // Multi-line comment is fully skipped - let tokens = lex("int /* ignore this */ y;"); + #[test] + fn test_string_and_char_literals() { + let source = r#""hello" 'A' '\n' '\0'"#; + let tokens = lex(source); assert_eq!( tokens, vec![ + Token::StringLiteral("hello".into()), + Token::CharLiteral('A'), + Token::CharLiteral('\n'), + Token::CharLiteral('\0'), + Token::Newline, + Token::EOF, + ] + ); + } + + #[test] + fn test_string_escape_sequences() { + let source = r#""line1\nline2\t!""#; + let tokens = lex(source); + assert_eq!( + tokens, + vec![ + Token::StringLiteral("line1\nline2\t!".into()), + Token::Newline, + Token::EOF, + ] + ); + } + + // ── End-of-file handling ────────────────────────────────── + + #[test] + fn test_eof_emits_dedent_and_newline() { + // Source without trailing newline. + let source = "if true:\n pass"; + let tokens = lex(source); + assert_eq!( + tokens, + vec![ + Token::If, + Token::True, + Token::Colon, + Token::Newline, + Token::Indent, + Token::Pass, + Token::Newline, // auto-inserted at EOF + Token::Dedent, // close the 4-space indent + Token::EOF, + ] + ); + } + + // ── Full example program ────────────────────────────────── + + #[test] + fn test_full_program() { + let source = "\ +def main() -> void: + let x: int = 10 + print(x) +"; + let tokens = lex(source); + assert_eq!( + tokens, + vec![ + Token::Def, + Token::Identifier("main".into()), + Token::LParen, + Token::RParen, + Token::Arrow, + Token::VoidType, + Token::Colon, + Token::Newline, + Token::Indent, + Token::Let, + Token::Identifier("x".into()), + Token::Colon, Token::IntType, - Token::Identifier("y".into()), - Token::Semicolon, + Token::Assign, + Token::IntLiteral(10), + Token::Newline, + Token::Print, + Token::LParen, + Token::Identifier("x".into()), + Token::RParen, + Token::Newline, + Token::Dedent, Token::EOF, ] ); diff --git a/src/lexer/token.rs b/src/lexer/token.rs index f89190b..08ac67c 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -1,21 +1,59 @@ -/// All lexical tokens recognized by the Vipr language. +/// All lexical tokens recognized by the VIPR v0.2.0 language. #[derive(Debug, Clone, PartialEq)] pub enum Token { + // ── Structural ──────────────────────────────────────────── + /// Marks the end of a logical line. + Newline, + /// Marks the start of an indented block. + Indent, + /// Marks the end of an indented block. + Dedent, + /// Signals the end of the token stream. + EOF, + // ── Keywords ────────────────────────────────────────────── - /// `func` - Func, + /// `let` + Let, + /// `const` + Const, + /// `def` + Def, /// `return` Return, /// `if` If, /// `else` Else, + /// `elif` + Elif, /// `for` For, /// `while` While, - /// `const` - Const, + /// `break` + Break, + /// `continue` + Continue, + /// `pass` + Pass, + /// `and` + And, + /// `or` + Or, + /// `not` + Not, + /// `in` + In, + /// `range` + Range, + /// `true` + True, + /// `false` + False, + /// `print` + Print, + /// `input` + Input, // ── Primitive Types ─────────────────────────────────────── /// `int` @@ -31,12 +69,6 @@ pub enum Token { /// `void` VoidType, - // ── Boolean Literals ────────────────────────────────────── - /// `true` - True, - /// `false` - False, - // ── Data Literals ───────────────────────────────────────── /// An integer literal, e.g. `42` IntLiteral(i32), @@ -77,33 +109,21 @@ pub enum Token { /// `>=` GreaterEq, - // ── Logical Operators ───────────────────────────────────── - /// `&&` - And, - /// `||` - Or, - /// `!` - Not, - // ── Assignment Operator ─────────────────────────────────── /// `=` Assign, + // ── Arrow ───────────────────────────────────────────────── + /// `->` + Arrow, + // ── Punctuation ─────────────────────────────────────────── /// `(` LParen, /// `)` RParen, - /// `{` - LBrace, - /// `}` - RBrace, - /// `;` - Semicolon, + /// `:` + Colon, /// `,` Comma, - - // ── Special ─────────────────────────────────────────────── - /// Signals the end of the token stream. - EOF, } diff --git a/src/main.rs b/src/main.rs index 93e8074..b34ec72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,11 @@ mod lexer; mod parser; mod semantic; mod codegen; -mod utils; use std::env; use std::fs; -use std::process; use std::path::Path; +use std::process::{Command, exit}; use lexer::Lexer; use parser::Parser; @@ -15,94 +14,101 @@ use semantic::SemanticAnalyzer; use codegen::CodeGenerator; fn main() { - // 1. CLI Arguments parsing + // 1. CLI Arguments let args: Vec = env::args().collect(); - if args.len() < 2 { - eprintln!("Usage: {} ", args[0]); - process::exit(1); + if args.len() != 2 { + eprintln!("Usage: {} ", args.get(0).unwrap_or(&"vipr".to_string())); + exit(1); } - let file_path = &args[1]; + let input_path_str = &args[1]; + let input_path = Path::new(input_path_str); - // Determine the output binary name - let path = Path::new(file_path); - let output_bin = path - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("a.out"); - - // 2. Read the source code - let source_code = match fs::read_to_string(file_path) { - Ok(content) => content, + // Ensure the file exists and can be read + let source_code = match fs::read_to_string(input_path) { + Ok(code) => code, Err(e) => { - eprintln!("Error reading file '{}': {}", file_path, e); - process::exit(1); + eprintln!("Error: Could not read file '{}': {}", input_path_str, e); + exit(1); } }; - // 3. Lexical Analysis - let mut lexer = Lexer::new(&source_code); - let tokens = match lexer.tokenize() { - Ok(t) => t, + // 2. The Pipeline + + // Lexical Analysis + let tokens = match Lexer::new(&source_code).tokenize() { + Ok(tokens) => tokens, Err(e) => { - eprintln!("Lexer Error: {}", e); - process::exit(1); + eprintln!("Lexer Error:\n{}", e); + exit(1); } }; - // 4. Parsing + // Syntax Analysis let mut parser = Parser::new(tokens); let ast = match parser.parse() { - Ok(tree) => tree, + Ok(ast) => ast, Err(e) => { - eprintln!("Parser Error: {}", e); - process::exit(1); + eprintln!("Parser Error:\n{}", e); + exit(1); } }; - // 5. Semantic Analysis + // Semantic Analysis let mut analyzer = SemanticAnalyzer::new(); - if let Err(e) = analyzer.check_program(&ast) { - eprintln!("Semantic Error: {}", e); - process::exit(1); + if let Err(e) = analyzer.analyze(&ast) { + eprintln!("Semantic Error:\n{}", e); + exit(1); } - // 6. Code Generation - let mut generator = CodeGenerator::new(); - let c_code = generator.generate(&ast); + // Code Generation + let mut codegen = CodeGenerator::new(); + let c_code = codegen.generate(&ast); - // 7. Outputting C Code to a temporary file + // 3. Native Compilation via GCC let temp_c_file = "temp_vipr_output.c"; + if let Err(e) = fs::write(temp_c_file, &c_code) { - eprintln!("Error writing to temporary file '{}': {}", temp_c_file, e); - process::exit(1); + eprintln!("Error: Failed to write temporary C file: {}", e); + exit(1); } - // 8. Native Compilation - let status = process::Command::new("gcc") - .args([temp_c_file, "-o", output_bin]) + // Extract the binary name from the input file stem (e.g. `program.vipr` -> `program`) + let binary_name = input_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("output"); + + // Invoke GCC + let gcc_status = Command::new("gcc") + .arg(temp_c_file) + .arg("-o") + .arg(binary_name) .status(); - match status { - Ok(exit_status) if exit_status.success() => { - // 9. Cleanup - if let Err(e) = fs::remove_file(temp_c_file) { - eprintln!("Warning: Failed to remove temporary file '{}': {}", temp_c_file, e); - } + match gcc_status { + Ok(status) if status.success() => { + // Compilation succeeded } - Ok(exit_status) => { - eprintln!("gcc compilation failed: {}", exit_status); - if let Err(e) = fs::remove_file(temp_c_file) { - eprintln!("Warning: Failed to remove temporary file '{}': {}", temp_c_file, e); - } - process::exit(1); + Ok(status) => { + eprintln!("Error: GCC compilation failed with status {}", status); + // Optionally, we could clean up the temp file here too, but leaving it + // can be helpful for debugging compiler bugs. We'll follow strict cleanup. + let _ = fs::remove_file(temp_c_file); + exit(1); } Err(e) => { - eprintln!("Failed to invoke gcc. Is it installed and in your PATH? Error: {}", e); - if let Err(cleanup_err) = fs::remove_file(temp_c_file) { - eprintln!("Warning: Failed to remove temporary file '{}': {}", temp_c_file, cleanup_err); - } - process::exit(1); + eprintln!("Error: Failed to invoke GCC: {}", e); + let _ = fs::remove_file(temp_c_file); + exit(1); } } + + // 4. Cleanup + if let Err(e) = fs::remove_file(temp_c_file) { + eprintln!("Warning: Failed to delete temporary file '{}': {}", temp_c_file, e); + } + + // 5. Success Message + println!("Successfully compiled '{}' to executable '{}'", input_path_str, binary_name); } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 2cdfb8f..10e6c8d 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -23,12 +23,13 @@ pub enum BinaryOp { Div, Mod, // Comparison - Eq, - Neq, Lt, Gt, Lte, Gte, + // Equality + Eq, + Neq, // Logical And, Or, @@ -37,10 +38,22 @@ pub enum BinaryOp { /// Unary operators used in expressions. #[derive(Debug, Clone, PartialEq)] pub enum UnaryOp { - /// Logical negation (`!`) - Not, /// Arithmetic negation (`-`) Neg, + /// Logical negation (`not`) + Not, +} + +// ── Literals ────────────────────────────────────────────────── + +/// Concrete literal values that can appear in source code. +#[derive(Debug, Clone, PartialEq)] +pub enum Literal { + Int(i32), + Float(f32), + Bool(bool), + Char(char), + String(String), } // ── Expressions ─────────────────────────────────────────────── @@ -74,196 +87,112 @@ pub enum Expr { }, } -/// Concrete literal values that can appear in source code. -#[derive(Debug, Clone, PartialEq)] -pub enum Literal { - Int(i32), - Float(f32), - Bool(bool), - Char(char), - String(String), -} - // ── Statements ──────────────────────────────────────────────── /// A statement within a function body or block. #[derive(Debug, Clone, PartialEq)] pub enum Stmt { - /// A `{ … }` block containing zero or more statements. - Block(Vec), + /// `let ident1, ident2: type [= expr1, expr2]` + Let { + identifiers: Vec, + vipr_type: ViprType, + initializers: Option>, + }, - /// A bare expression followed by `;`. - ExpressionStmt(Expr), + /// `const ident1, ident2: type = expr1, expr2` + Const { + identifiers: Vec, + vipr_type: ViprType, + initializers: Vec, + }, - /// `identifier = expr;` + /// `ident1, ident2 = expr1, expr2` Assignment { - identifier: String, - value: Expr, + targets: Vec, + values: Vec, }, - /// `if (cond) { … } else if (cond) { … } else { … }` + /// `if cond: block { elif cond: block } [ else: block ]` If { condition: Expr, - then_branch: Box, - else_if_branches: Vec<(Expr, Stmt)>, + body: Box, + elif_branches: Vec<(Expr, Stmt)>, else_branch: Option>, }, - /// `while (cond) { … }` + /// `while cond: block` While { condition: Expr, body: Box, }, - /// `for (init; cond; increment) { … }` + /// `for variable: type in range(start, end, step): block` For { - init: Box, - condition: Expr, - increment: Box, + variable: String, + vipr_type: ViprType, + start: Expr, + end: Expr, + step: Expr, body: Box, }, - /// `return [expr];` + /// `return [expr]` Return(Option), - /// A variable declaration appearing as a statement inside a block. - VariableDecl { - is_const: bool, - vipr_type: ViprType, - name: String, - initializer: Option, - }, + /// `break` + Break, + + /// `continue` + Continue, + + /// `pass` + Pass, + + /// `print(expr1, expr2, …)` + Print(Vec), + + /// `input(ident1, ident2, …)` + Input(Vec), + + /// A bare expression used as a statement (e.g. a function call). + ExprStmt(Expr), + + /// A `{ … }` block containing one or more statements. + Block(Vec), } -// ── Declarations ────────────────────────────────────────────── +// ── Top-Level Declarations ──────────────────────────────────── -/// A top-level declaration in a Vipr program. +/// A top-level declaration in a VIPR program. #[derive(Debug, Clone, PartialEq)] -pub enum Decl { - /// A variable or constant declaration at the top level. - Variable { - is_const: bool, - vipr_type: ViprType, - name: String, - initializer: Option, - }, - +pub enum TopLevel { /// A function declaration. Function { name: String, - parameters: Vec<(ViprType, String)>, + parameters: Vec<(String, ViprType)>, return_type: ViprType, body: Stmt, }, + + /// A global variable declaration (`let`). + GlobalLet { + identifiers: Vec, + vipr_type: ViprType, + initializers: Option>, + }, + + /// A global constant declaration (`const`). + GlobalConst { + identifiers: Vec, + vipr_type: ViprType, + initializers: Vec, + }, } // ── Program ─────────────────────────────────────────────────── -/// The root node of every Vipr program — a list of declarations. +/// The root node of every VIPR program — a list of top-level declarations. #[derive(Debug, Clone, PartialEq)] pub struct Program { - pub declarations: Vec, -} - -// ── Tests ───────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_variable_declaration_ast() { - // Represents: `int age = 25;` - let decl = Decl::Variable { - is_const: false, - vipr_type: ViprType::Int, - name: "age".to_string(), - initializer: Some(Expr::Literal(Literal::Int(25))), - }; - - assert_eq!( - decl, - Decl::Variable { - is_const: false, - vipr_type: ViprType::Int, - name: "age".to_string(), - initializer: Some(Expr::Literal(Literal::Int(25))), - } - ); - - // Represents: `const float PI = 3.14;` - let const_decl = Decl::Variable { - is_const: true, - vipr_type: ViprType::Float, - name: "PI".to_string(), - initializer: Some(Expr::Literal(Literal::Float(3.14))), - }; - - assert!(matches!( - const_decl, - Decl::Variable { is_const: true, .. } - )); - } - - #[test] - fn test_binary_expression_ast() { - // Represents: `a + b * 2` - // With correct precedence this is `a + (b * 2)`. - let expr = Expr::BinaryOp { - left: Box::new(Expr::Identifier("a".to_string())), - op: BinaryOp::Add, - right: Box::new(Expr::BinaryOp { - left: Box::new(Expr::Identifier("b".to_string())), - op: BinaryOp::Mul, - right: Box::new(Expr::Literal(Literal::Int(2))), - }), - }; - - // Verify the top-level node is an Add. - assert!(matches!( - expr, - Expr::BinaryOp { op: BinaryOp::Add, .. } - )); - - // Verify the right branch is a Mul. - if let Expr::BinaryOp { right, .. } = &expr { - assert!(matches!( - right.as_ref(), - Expr::BinaryOp { op: BinaryOp::Mul, .. } - )); - } else { - panic!("Expected BinaryOp at top level"); - } - } - - #[test] - fn test_function_declaration_ast() { - // Represents: - // func add(int a, int b) int { - // return a + b; - // } - let func = Decl::Function { - name: "add".to_string(), - parameters: vec![ - (ViprType::Int, "a".to_string()), - (ViprType::Int, "b".to_string()), - ], - return_type: ViprType::Int, - body: Stmt::Block(vec![ - Stmt::Return(Some(Expr::BinaryOp { - left: Box::new(Expr::Identifier("a".to_string())), - op: BinaryOp::Add, - right: Box::new(Expr::Identifier("b".to_string())), - })), - ]), - }; - - if let Decl::Function { name, parameters, return_type, .. } = &func { - assert_eq!(name, "add"); - assert_eq!(parameters.len(), 2); - assert_eq!(*return_type, ViprType::Int); - } else { - panic!("Expected Function declaration"); - } - } + pub declarations: Vec, } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6eedb25..31263c8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3,7 +3,7 @@ pub mod ast; use crate::lexer::token::Token; use ast::*; -/// The Vipr recursive descent parser. +/// The VIPR v0.2.0 recursive-descent parser. pub struct Parser { tokens: Vec, pos: usize, @@ -21,13 +21,6 @@ impl Parser { &self.tokens[self.pos] } - /// Peeks at the token after the current one (used for lookahead). - fn peek_next(&self) -> &Token { - self.tokens - .get(self.pos + 1) - .unwrap_or(&self.tokens[self.tokens.len() - 1]) - } - /// Consumes and returns a clone of the current token. fn advance(&mut self) -> Token { let token = self.tokens[self.pos].clone(); @@ -57,21 +50,20 @@ impl Parser { } } - /// Returns `true` if the current token is a type keyword. - fn is_type_token(&self) -> bool { - matches!( - self.peek(), - Token::IntType - | Token::FloatType - | Token::BoolType - | Token::CharType - | Token::StringType - | Token::VoidType - ) + /// Consume an `Identifier` token and return its name string. + fn consume_identifier(&mut self) -> Result { + let tok = self.peek().clone(); + match tok { + Token::Identifier(name) => { + self.advance(); + Ok(name) + } + _ => Err(format!("Expected identifier, found {:?}", tok)), + } } - /// Parse a `ViprType` from the current token. - fn parse_type(&mut self) -> Result { + /// Parse a value type (no `void`). + fn parse_value_type(&mut self) -> Result { let tok = self.peek().clone(); match tok { Token::IntType => { self.advance(); Ok(ViprType::Int) } @@ -79,76 +71,141 @@ impl Parser { Token::BoolType => { self.advance(); Ok(ViprType::Bool) } Token::CharType => { self.advance(); Ok(ViprType::Char) } Token::StringType => { self.advance(); Ok(ViprType::String) } - Token::VoidType => { self.advance(); Ok(ViprType::Void) } - _ => Err(format!("Expected type, found {:?}", tok)), + _ => Err(format!("Expected value type, found {:?}", tok)), } } - /// Consume an `Identifier` token and return its name string. - fn consume_identifier(&mut self) -> Result { - let tok = self.peek().clone(); - match tok { - Token::Identifier(name) => { self.advance(); Ok(name) } - _ => Err(format!("Expected identifier, found {:?}", tok)), + /// Parse a return type (`void` or any value type). + fn parse_return_type(&mut self) -> Result { + if *self.peek() == Token::VoidType { + self.advance(); + Ok(ViprType::Void) + } else { + self.parse_value_type() + } + } + + /// Parse a comma-separated list of identifiers. + fn parse_identifier_list(&mut self) -> Result, String> { + let mut ids = Vec::new(); + ids.push(self.consume_identifier()?); + while self.match_token(&Token::Comma) { + ids.push(self.consume_identifier()?); } + Ok(ids) + } + + /// Parse a comma-separated list of expressions. + fn parse_expression_list(&mut self) -> Result, String> { + let mut exprs = Vec::new(); + exprs.push(self.parse_expression()?); + while self.match_token(&Token::Comma) { + exprs.push(self.parse_expression()?); + } + Ok(exprs) + } + + /// Lookahead: is the current identifier-starting statement an assignment? + /// + /// Scans forward from the current position until `Newline` or `EOF` at + /// parenthesis depth zero. Returns `true` if a top-level `=` (`Assign`) + /// is found before the line ends. + fn is_assignment_statement(&self) -> bool { + let mut depth = 0usize; + let mut i = self.pos; + while i < self.tokens.len() { + match &self.tokens[i] { + Token::Newline | Token::EOF => return false, + Token::LParen => depth += 1, + Token::RParen => depth = depth.saturating_sub(1), + Token::Assign if depth == 0 => return true, + _ => {} + } + i += 1; + } + false } // ── Program ────────────────────────────────────────────── - /// Parse the entire program: a sequence of declarations until `EOF`. + /// Parse the entire program: a sequence of top-level declarations. pub fn parse(&mut self) -> Result { let mut declarations = Vec::new(); while *self.peek() != Token::EOF { - declarations.push(self.parse_declaration()?); + declarations.push(self.parse_top_level()?); } Ok(Program { declarations }) } - // ── Declarations ───────────────────────────────────────── - - /// Dispatch to the correct declaration parser based on the leading token. - fn parse_declaration(&mut self) -> Result { - let tok = self.peek().clone(); - match tok { - Token::Func => self.parse_function_decl(), - Token::Const => self.parse_const_decl(), - _ if self.is_type_token() => self.parse_variable_decl(), - _ => Err(format!("Expected declaration, found {:?}", tok)), + // ── Top-level declarations ─────────────────────────────── + + /// Dispatch to the correct top-level declaration parser. + fn parse_top_level(&mut self) -> Result { + match self.peek().clone() { + Token::Def => self.parse_function(), + Token::Let => { + let (ids, ty, inits) = self.parse_let_decl()?; + self.consume( + &Token::Newline, + "Expected newline after global variable declaration", + )?; + Ok(TopLevel::GlobalLet { + identifiers: ids, + vipr_type: ty, + initializers: inits, + }) + } + Token::Const => { + let (ids, ty, inits) = self.parse_const_decl()?; + self.consume( + &Token::Newline, + "Expected newline after global constant declaration", + )?; + Ok(TopLevel::GlobalConst { + identifiers: ids, + vipr_type: ty, + initializers: inits, + }) + } + other => Err(format!( + "Expected top-level declaration (def, let, or const), found {:?}", + other + )), } } - /// Parse: `func name(params) [returnType] block` - fn parse_function_decl(&mut self) -> Result { - self.consume(&Token::Func, "Expected 'func'")?; + /// Parse: `def name(params) -> returnType: block` + fn parse_function(&mut self) -> Result { + self.consume(&Token::Def, "Expected 'def'")?; let name = self.consume_identifier()?; // Parameter list self.consume(&Token::LParen, "Expected '(' after function name")?; let mut parameters = Vec::new(); - if !self.match_token(&Token::RParen) { + if *self.peek() != Token::RParen { loop { - let param_type = self.parse_type()?; let param_name = self.consume_identifier()?; - parameters.push((param_type, param_name)); + self.consume(&Token::Colon, "Expected ':' after parameter name")?; + let param_type = self.parse_value_type()?; + parameters.push((param_name, param_type)); if !self.match_token(&Token::Comma) { break; } } - self.consume(&Token::RParen, "Expected ')' after parameters")?; } + self.consume(&Token::RParen, "Expected ')' after parameters")?; - // Return type — optional, defaults to Void when omitted - let return_type = if self.is_type_token() { - self.parse_type()? - } else { - ViprType::Void - }; + // Return type + self.consume(&Token::Arrow, "Expected '->' after parameter list")?; + let return_type = self.parse_return_type()?; + self.consume(&Token::Colon, "Expected ':' after return type")?; + // Body let body = self.parse_block()?; - Ok(Decl::Function { + Ok(TopLevel::Function { name, parameters, return_type, @@ -156,245 +213,257 @@ impl Parser { }) } - /// Parse: `const type name = expr ;` - fn parse_const_decl(&mut self) -> Result { - self.consume(&Token::Const, "Expected 'const'")?; - let vipr_type = self.parse_type()?; - let name = self.consume_identifier()?; - self.consume(&Token::Assign, "Expected '=' in const declaration")?; - let initializer = self.parse_expression()?; - self.consume(&Token::Semicolon, "Expected ';' after const declaration")?; - - Ok(Decl::Variable { - is_const: true, - vipr_type, - name, - initializer: Some(initializer), - }) - } - - /// Parse: `type name [= expr] ;` - fn parse_variable_decl(&mut self) -> Result { - let vipr_type = self.parse_type()?; - let name = self.consume_identifier()?; - - let initializer = if self.match_token(&Token::Assign) { - Some(self.parse_expression()?) + // ── Shared declaration helpers ─────────────────────────── + + /// Parse: `let identifierList : valueType [ = expressionList ]` + /// + /// Consumes `let` through the expressions. Does NOT consume the trailing + /// `Newline` — the caller is responsible for that. + fn parse_let_decl( + &mut self, + ) -> Result<(Vec, ViprType, Option>), String> { + self.consume(&Token::Let, "Expected 'let'")?; + let identifiers = self.parse_identifier_list()?; + self.consume(&Token::Colon, "Expected ':' after identifier list")?; + let vipr_type = self.parse_value_type()?; + + let initializers = if self.match_token(&Token::Assign) { + Some(self.parse_expression_list()?) } else { None }; - self.consume(&Token::Semicolon, "Expected ';' after variable declaration")?; - - Ok(Decl::Variable { - is_const: false, - vipr_type, - name, - initializer, - }) + Ok((identifiers, vipr_type, initializers)) } - // ── Statements ─────────────────────────────────────────── + /// Parse: `const identifierList : valueType = expressionList` + /// + /// Consumes `const` through the expressions. Does NOT consume the + /// trailing `Newline`. + fn parse_const_decl( + &mut self, + ) -> Result<(Vec, ViprType, Vec), String> { + self.consume(&Token::Const, "Expected 'const'")?; + let identifiers = self.parse_identifier_list()?; + self.consume(&Token::Colon, "Expected ':' after identifier list")?; + let vipr_type = self.parse_value_type()?; + self.consume(&Token::Assign, "Expected '=' in const declaration")?; + let initializers = self.parse_expression_list()?; - /// Dispatch to the correct statement parser. - fn parse_statement(&mut self) -> Result { - let tok = self.peek().clone(); - match tok { - Token::If => self.parse_if_stmt(), - Token::While => self.parse_while_stmt(), - Token::For => self.parse_for_stmt(), - Token::Return => self.parse_return_stmt(), - Token::LBrace => self.parse_block(), - Token::Const => self.parse_var_decl_stmt(true), - _ if self.is_type_token() => self.parse_var_decl_stmt(false), - Token::Identifier(_) if *self.peek_next() == Token::Assign => { - self.parse_assignment_stmt() - } - _ => self.parse_expression_stmt(), - } + Ok((identifiers, vipr_type, initializers)) } - /// Parse: `{ statement* }` + // ── Block ──────────────────────────────────────────────── + + /// Parse: `NEWLINE INDENT statement { statement } DEDENT` fn parse_block(&mut self) -> Result { - self.consume(&Token::LBrace, "Expected '{'")?; + self.consume(&Token::Newline, "Expected newline before indented block")?; + self.consume(&Token::Indent, "Expected indented block")?; let mut stmts = Vec::new(); - while *self.peek() != Token::RBrace && *self.peek() != Token::EOF { + while *self.peek() != Token::Dedent && *self.peek() != Token::EOF { stmts.push(self.parse_statement()?); } - self.consume(&Token::RBrace, "Expected '}'")?; + if stmts.is_empty() { + return Err("Block must contain at least one statement".to_string()); + } + + self.consume(&Token::Dedent, "Expected end of indented block")?; Ok(Stmt::Block(stmts)) } - /// Parse a variable declaration as a statement inside a block. - fn parse_var_decl_stmt(&mut self, is_const: bool) -> Result { - if is_const { - self.consume(&Token::Const, "Expected 'const'")?; - } - let vipr_type = self.parse_type()?; - let name = self.consume_identifier()?; - - let initializer = if self.match_token(&Token::Assign) { - Some(self.parse_expression()?) - } else { - None - }; + // ── Statements ─────────────────────────────────────────── - self.consume(&Token::Semicolon, "Expected ';' after variable declaration")?; + /// Parse a single statement. + /// + /// Compound statements (`if`, `while`, `for`) include their own block + /// handling and do NOT consume a trailing `Newline`. Simple statements + /// are always followed by a `Newline` that this method consumes. + fn parse_statement(&mut self) -> Result { + // Compound statements — no trailing Newline + match self.peek() { + Token::If => return self.parse_if_stmt(), + Token::While => return self.parse_while_stmt(), + Token::For => return self.parse_for_stmt(), + _ => {} + } - Ok(Stmt::VariableDecl { - is_const, - vipr_type, - name, - initializer, - }) + // Simple statements — followed by Newline + let stmt = self.parse_simple_statement()?; + self.consume(&Token::Newline, "Expected newline after statement")?; + Ok(stmt) } - /// Parse: `identifier = expression ;` - fn parse_assignment_stmt(&mut self) -> Result { - let name = self.consume_identifier()?; - self.consume(&Token::Assign, "Expected '=' in assignment")?; - let value = self.parse_expression()?; - self.consume(&Token::Semicolon, "Expected ';' after assignment")?; - - Ok(Stmt::Assignment { - identifier: name, - value, - }) + /// Parse a simple (non-compound) statement. + fn parse_simple_statement(&mut self) -> Result { + match self.peek().clone() { + Token::Let => { + let (ids, ty, inits) = self.parse_let_decl()?; + Ok(Stmt::Let { + identifiers: ids, + vipr_type: ty, + initializers: inits, + }) + } + Token::Const => { + let (ids, ty, inits) = self.parse_const_decl()?; + Ok(Stmt::Const { + identifiers: ids, + vipr_type: ty, + initializers: inits, + }) + } + Token::Return => self.parse_return_stmt(), + Token::Break => { self.advance(); Ok(Stmt::Break) } + Token::Continue => { self.advance(); Ok(Stmt::Continue) } + Token::Pass => { self.advance(); Ok(Stmt::Pass) } + Token::Print => self.parse_print_stmt(), + Token::Input => self.parse_input_stmt(), + Token::Identifier(_) => { + if self.is_assignment_statement() { + self.parse_assignment_stmt() + } else { + self.parse_expression_stmt() + } + } + _ => self.parse_expression_stmt(), + } } - /// Parse: `if (expr) block { else if (expr) block } [ else block ]` + /// Parse: `if expr : block { elif expr : block } [ else : block ]` fn parse_if_stmt(&mut self) -> Result { self.consume(&Token::If, "Expected 'if'")?; - self.consume(&Token::LParen, "Expected '(' after 'if'")?; let condition = self.parse_expression()?; - self.consume(&Token::RParen, "Expected ')' after if condition")?; - let then_branch = Box::new(self.parse_block()?); - - let mut else_if_branches = Vec::new(); - let mut else_branch = None; - - while self.match_token(&Token::Else) { - if self.match_token(&Token::If) { - // else if - self.consume(&Token::LParen, "Expected '(' after 'else if'")?; - let elif_cond = self.parse_expression()?; - self.consume(&Token::RParen, "Expected ')' after else-if condition")?; - let elif_body = self.parse_block()?; - else_if_branches.push((elif_cond, elif_body)); - } else { - // final else - else_branch = Some(Box::new(self.parse_block()?)); - break; - } + self.consume(&Token::Colon, "Expected ':' after if condition")?; + let body = Box::new(self.parse_block()?); + + let mut elif_branches = Vec::new(); + while self.match_token(&Token::Elif) { + let elif_cond = self.parse_expression()?; + self.consume(&Token::Colon, "Expected ':' after elif condition")?; + let elif_body = self.parse_block()?; + elif_branches.push((elif_cond, elif_body)); } + let else_branch = if self.match_token(&Token::Else) { + self.consume(&Token::Colon, "Expected ':' after else")?; + Some(Box::new(self.parse_block()?)) + } else { + None + }; + Ok(Stmt::If { condition, - then_branch, - else_if_branches, + body, + elif_branches, else_branch, }) } - /// Parse: `while (expr) block` + /// Parse: `while expr : block` fn parse_while_stmt(&mut self) -> Result { self.consume(&Token::While, "Expected 'while'")?; - self.consume(&Token::LParen, "Expected '(' after 'while'")?; let condition = self.parse_expression()?; - self.consume(&Token::RParen, "Expected ')' after while condition")?; + self.consume(&Token::Colon, "Expected ':' after while condition")?; let body = Box::new(self.parse_block()?); Ok(Stmt::While { condition, body }) } - /// Parse: `for (variableDecl expression ; identifier = expression) block` + /// Parse: `for ident : valueType in range(start, end, step) : block` fn parse_for_stmt(&mut self) -> Result { self.consume(&Token::For, "Expected 'for'")?; - self.consume(&Token::LParen, "Expected '(' after 'for'")?; - - // Init — a variable declaration (consumes its own trailing ';') - let init = if self.is_type_token() { - let vipr_type = self.parse_type()?; - let name = self.consume_identifier()?; - let initializer = if self.match_token(&Token::Assign) { - Some(self.parse_expression()?) - } else { - None - }; - self.consume(&Token::Semicolon, "Expected ';' after for-loop init")?; - Box::new(Stmt::VariableDecl { - is_const: false, - vipr_type, - name, - initializer, - }) - } else { - return Err(format!( - "Expected variable declaration in for-loop init, found {:?}", - self.peek() - )); - }; - - // Condition - let condition = self.parse_expression()?; - self.consume(&Token::Semicolon, "Expected ';' after for-loop condition")?; - - // Increment — `identifier = expression` (no trailing semicolon) - let inc_name = self.consume_identifier()?; - self.consume(&Token::Assign, "Expected '=' in for-loop increment")?; - let inc_value = self.parse_expression()?; - let increment = Box::new(Stmt::Assignment { - identifier: inc_name, - value: inc_value, - }); - - self.consume(&Token::RParen, "Expected ')' after for-loop clauses")?; + let variable = self.consume_identifier()?; + self.consume(&Token::Colon, "Expected ':' after for-loop variable")?; + let vipr_type = self.parse_value_type()?; + self.consume(&Token::In, "Expected 'in' after for-loop type")?; + self.consume(&Token::Range, "Expected 'range'")?; + self.consume(&Token::LParen, "Expected '(' after 'range'")?; + let start = self.parse_expression()?; + self.consume(&Token::Comma, "Expected ',' after range start")?; + let end = self.parse_expression()?; + self.consume(&Token::Comma, "Expected ',' after range end")?; + let step = self.parse_expression()?; + self.consume(&Token::RParen, "Expected ')' after range arguments")?; + self.consume(&Token::Colon, "Expected ':' after for-loop header")?; let body = Box::new(self.parse_block()?); Ok(Stmt::For { - init, - condition, - increment, + variable, + vipr_type, + start, + end, + step, body, }) } - /// Parse: `return [expression] ;` + /// Parse: `return [ expression ]` fn parse_return_stmt(&mut self) -> Result { self.consume(&Token::Return, "Expected 'return'")?; - let value = if *self.peek() != Token::Semicolon { + let expr = if *self.peek() != Token::Newline { Some(self.parse_expression()?) } else { None }; - self.consume(&Token::Semicolon, "Expected ';' after return statement")?; - Ok(Stmt::Return(value)) + Ok(Stmt::Return(expr)) + } + + /// Parse: `print ( [ expressionList ] )` + fn parse_print_stmt(&mut self) -> Result { + self.consume(&Token::Print, "Expected 'print'")?; + self.consume(&Token::LParen, "Expected '(' after 'print'")?; + + let args = if *self.peek() != Token::RParen { + self.parse_expression_list()? + } else { + Vec::new() + }; + + self.consume(&Token::RParen, "Expected ')' after print arguments")?; + Ok(Stmt::Print(args)) + } + + /// Parse: `input ( identifierList )` + fn parse_input_stmt(&mut self) -> Result { + self.consume(&Token::Input, "Expected 'input'")?; + self.consume(&Token::LParen, "Expected '(' after 'input'")?; + let identifiers = self.parse_identifier_list()?; + self.consume(&Token::RParen, "Expected ')' after input arguments")?; + Ok(Stmt::Input(identifiers)) + } + + /// Parse: `identifierList = expressionList` + fn parse_assignment_stmt(&mut self) -> Result { + let targets = self.parse_identifier_list()?; + self.consume(&Token::Assign, "Expected '=' in assignment")?; + let values = self.parse_expression_list()?; + Ok(Stmt::Assignment { targets, values }) } - /// Parse: `expression ;` + /// Parse a bare expression as a statement. fn parse_expression_stmt(&mut self) -> Result { let expr = self.parse_expression()?; - self.consume(&Token::Semicolon, "Expected ';' after expression")?; - Ok(Stmt::ExpressionStmt(expr)) + Ok(Stmt::ExprStmt(expr)) } // ── Expressions (precedence climbing) ──────────────────── // // Lowest → Highest: - // || → && → == != → < <= > >= → + - → * / % → ! - → primary + // or → and → not → == != → < <= > >= → + - → * / % → unary - → primary fn parse_expression(&mut self) -> Result { self.parse_or() } - /// `||` + /// `or` fn parse_or(&mut self) -> Result { let mut left = self.parse_and()?; - while self.match_token(&Token::Or) { + while *self.peek() == Token::Or { + self.advance(); let right = self.parse_and()?; left = Expr::BinaryOp { left: Box::new(left), @@ -405,11 +474,12 @@ impl Parser { Ok(left) } - /// `&&` + /// `and` fn parse_and(&mut self) -> Result { - let mut left = self.parse_equality()?; - while self.match_token(&Token::And) { - let right = self.parse_equality()?; + let mut left = self.parse_not()?; + while *self.peek() == Token::And { + self.advance(); + let right = self.parse_not()?; left = Expr::BinaryOp { left: Box::new(left), op: BinaryOp::And, @@ -419,6 +489,20 @@ impl Parser { Ok(left) } + /// `not expr` | equality + fn parse_not(&mut self) -> Result { + if *self.peek() == Token::Not { + self.advance(); + let expr = self.parse_not()?; + Ok(Expr::UnaryOp { + op: UnaryOp::Not, + expr: Box::new(expr), + }) + } else { + self.parse_equality() + } + } + /// `==` `!=` fn parse_equality(&mut self) -> Result { let mut left = self.parse_comparison()?; @@ -441,7 +525,7 @@ impl Parser { /// `<` `<=` `>` `>=` fn parse_comparison(&mut self) -> Result { - let mut left = self.parse_addition()?; + let mut left = self.parse_term()?; loop { let op = match self.peek() { Token::Less => BinaryOp::Lt, @@ -451,7 +535,7 @@ impl Parser { _ => break, }; self.advance(); - let right = self.parse_addition()?; + let right = self.parse_term()?; left = Expr::BinaryOp { left: Box::new(left), op, @@ -462,8 +546,8 @@ impl Parser { } /// `+` `-` - fn parse_addition(&mut self) -> Result { - let mut left = self.parse_multiplication()?; + fn parse_term(&mut self) -> Result { + let mut left = self.parse_factor()?; loop { let op = match self.peek() { Token::Plus => BinaryOp::Add, @@ -471,7 +555,7 @@ impl Parser { _ => break, }; self.advance(); - let right = self.parse_multiplication()?; + let right = self.parse_factor()?; left = Expr::BinaryOp { left: Box::new(left), op, @@ -482,7 +566,7 @@ impl Parser { } /// `*` `/` `%` - fn parse_multiplication(&mut self) -> Result { + fn parse_factor(&mut self) -> Result { let mut left = self.parse_unary()?; loop { let op = match self.peek() { @@ -502,27 +586,17 @@ impl Parser { Ok(left) } - /// `!expr` `-expr` + /// `-expr` | primary fn parse_unary(&mut self) -> Result { - let tok = self.peek().clone(); - match tok { - Token::Not => { - self.advance(); - let expr = self.parse_unary()?; - Ok(Expr::UnaryOp { - op: UnaryOp::Not, - expr: Box::new(expr), - }) - } - Token::Minus => { - self.advance(); - let expr = self.parse_unary()?; - Ok(Expr::UnaryOp { - op: UnaryOp::Neg, - expr: Box::new(expr), - }) - } - _ => self.parse_primary(), + if *self.peek() == Token::Minus { + self.advance(); + let expr = self.parse_unary()?; + Ok(Expr::UnaryOp { + op: UnaryOp::Neg, + expr: Box::new(expr), + }) + } else { + self.parse_primary() } } @@ -560,7 +634,7 @@ impl Parser { // ── Identifier or function call ─────────────── Token::Identifier(name) => { self.advance(); - // If followed by '(', it's a function call + // If followed by '(', it's a function call. if *self.peek() == Token::LParen { self.advance(); // consume '(' let mut args = Vec::new(); @@ -572,7 +646,10 @@ impl Parser { } } } - self.consume(&Token::RParen, "Expected ')' after function arguments")?; + self.consume( + &Token::RParen, + "Expected ')' after function arguments", + )?; Ok(Expr::FunctionCall { name, args }) } else { Ok(Expr::Identifier(name)) @@ -583,7 +660,10 @@ impl Parser { Token::LParen => { self.advance(); // consume '(' let expr = self.parse_expression()?; - self.consume(&Token::RParen, "Expected ')' after grouped expression")?; + self.consume( + &Token::RParen, + "Expected ')' after grouped expression", + )?; Ok(expr) } @@ -605,54 +685,334 @@ mod tests { Parser::new(tokens).parse() } + // ── Multi-identifier let declaration ────────────────────── + #[test] - fn test_parse_variable_declaration() { - let program = parse("int x = 42;").unwrap(); + fn test_parse_multi_identifier_let() { + let source = "\ +def main() -> void: + let a, b, c: int = 1, 2, 3 +"; + let program = parse(source).unwrap(); assert_eq!(program.declarations.len(), 1); - assert_eq!( - program.declarations[0], - Decl::Variable { - is_const: false, - vipr_type: ViprType::Int, - name: "x".to_string(), - initializer: Some(Expr::Literal(Literal::Int(42))), + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!(stmts.len(), 1); + assert_eq!( + stmts[0], + Stmt::Let { + identifiers: vec![ + "a".to_string(), + "b".to_string(), + "c".to_string(), + ], + vipr_type: ViprType::Int, + initializers: Some(vec![ + Expr::Literal(Literal::Int(1)), + Expr::Literal(Literal::Int(2)), + Expr::Literal(Literal::Int(3)), + ]), + } + ); + return; } - ); + } + panic!("Unexpected AST structure"); + } - // Constant declaration - let program = parse("const float PI = 3.14;").unwrap(); - assert_eq!( - program.declarations[0], - Decl::Variable { - is_const: true, - vipr_type: ViprType::Float, - name: "PI".to_string(), - initializer: Some(Expr::Literal(Literal::Float(3.14))), + #[test] + fn test_parse_let_without_initializer() { + let source = "\ +def main() -> void: + let x, y: float +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!( + stmts[0], + Stmt::Let { + identifiers: vec!["x".to_string(), "y".to_string()], + vipr_type: ViprType::Float, + initializers: None, + } + ); + return; } - ); + } + panic!("Unexpected AST structure"); + } + + // ── Assignment vs. expression statement disambiguation ─── + + #[test] + fn test_assignment_vs_expression_stmt() { + let source = "\ +def main() -> void: + x = 5 + foo() +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!(stmts.len(), 2); + + // First statement: assignment + assert_eq!( + stmts[0], + Stmt::Assignment { + targets: vec!["x".to_string()], + values: vec![Expr::Literal(Literal::Int(5))], + } + ); + + // Second statement: expression statement (function call) + assert_eq!( + stmts[1], + Stmt::ExprStmt(Expr::FunctionCall { + name: "foo".to_string(), + args: vec![], + }) + ); + return; + } + } + panic!("Unexpected AST structure"); + } + + #[test] + fn test_multi_target_assignment() { + let source = "\ +def main() -> void: + a, b = 1, 2 +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!( + stmts[0], + Stmt::Assignment { + targets: vec!["a".to_string(), "b".to_string()], + values: vec![ + Expr::Literal(Literal::Int(1)), + Expr::Literal(Literal::Int(2)), + ], + } + ); + return; + } + } + panic!("Unexpected AST structure"); + } + + #[test] + fn test_assignment_with_call_on_rhs() { + // Ensures the lookahead correctly finds `=` even when parens appear + // after it on the right-hand side. + let source = "\ +def main() -> void: + x = add(1, 2) +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!( + stmts[0], + Stmt::Assignment { + targets: vec!["x".to_string()], + values: vec![Expr::FunctionCall { + name: "add".to_string(), + args: vec![ + Expr::Literal(Literal::Int(1)), + Expr::Literal(Literal::Int(2)), + ], + }], + } + ); + return; + } + } + panic!("Unexpected AST structure"); + } + + // ── For loop with range ────────────────────────────────── + + #[test] + fn test_parse_for_loop_with_range() { + let source = "\ +def main() -> void: + for i: int in range(0, 10, 1): + pass +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!(stmts.len(), 1); + if let Stmt::For { + variable, + vipr_type, + start, + end, + step, + body: for_body, + } = &stmts[0] + { + assert_eq!(variable, "i"); + assert_eq!(*vipr_type, ViprType::Int); + assert_eq!(*start, Expr::Literal(Literal::Int(0))); + assert_eq!(*end, Expr::Literal(Literal::Int(10))); + assert_eq!(*step, Expr::Literal(Literal::Int(1))); + assert_eq!( + **for_body, + Stmt::Block(vec![Stmt::Pass]) + ); + return; + } + } + } + panic!("Unexpected AST structure"); + } + + // ── Additional tests ───────────────────────────────────── + + #[test] + fn test_parse_if_elif_else() { + let source = "\ +def main() -> void: + if x > 0: + pass + elif x < 0: + pass + else: + pass +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!(stmts.len(), 1); + if let Stmt::If { + elif_branches, + else_branch, + .. + } = &stmts[0] + { + assert_eq!(elif_branches.len(), 1); + assert!(else_branch.is_some()); + return; + } + } + } + panic!("Unexpected AST structure"); + } + + #[test] + fn test_parse_while_loop() { + let source = "\ +def main() -> void: + while true: + break +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!(stmts.len(), 1); + if let Stmt::While { condition, body: while_body } = &stmts[0] { + assert_eq!(*condition, Expr::Literal(Literal::Bool(true))); + assert_eq!( + **while_body, + Stmt::Block(vec![Stmt::Break]) + ); + return; + } + } + } + panic!("Unexpected AST structure"); + } + + #[test] + fn test_parse_global_const_and_function() { + let source = "\ +const LIMIT: int = 5 + +def main() -> void: + pass +"; + let program = parse(source).unwrap(); + assert_eq!(program.declarations.len(), 2); - // Declaration without initializer - let program = parse("string name;").unwrap(); assert_eq!( program.declarations[0], - Decl::Variable { - is_const: false, - vipr_type: ViprType::String, - name: "name".to_string(), - initializer: None, + TopLevel::GlobalConst { + identifiers: vec!["LIMIT".to_string()], + vipr_type: ViprType::Int, + initializers: vec![Expr::Literal(Literal::Int(5))], } ); + + if let TopLevel::Function { name, parameters, return_type, .. } = + &program.declarations[1] + { + assert_eq!(name, "main"); + assert!(parameters.is_empty()); + assert_eq!(*return_type, ViprType::Void); + } else { + panic!("Expected Function declaration"); + } } #[test] - fn test_parse_binary_expression_precedence() { - // `1 + 2 * 3` should parse as `1 + (2 * 3)` — Mul binds tighter. - let program = parse("func test() int { return 1 + 2 * 3; }").unwrap(); + fn test_parse_print_and_input() { + let source = "\ +def main() -> void: + print(\"hello\", x) + input(age, name) +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { + if let Stmt::Block(stmts) = body { + assert_eq!(stmts.len(), 2); + + assert_eq!( + stmts[0], + Stmt::Print(vec![ + Expr::Literal(Literal::String("hello".to_string())), + Expr::Identifier("x".to_string()), + ]) + ); + assert_eq!( + stmts[1], + Stmt::Input(vec![ + "age".to_string(), + "name".to_string(), + ]) + ); + return; + } + } + panic!("Unexpected AST structure"); + } - if let Decl::Function { body, .. } = &program.declarations[0] { + #[test] + fn test_parse_expression_precedence() { + // `1 + 2 * 3` should parse as `1 + (2 * 3)`. + let source = "\ +def test() -> int: + return 1 + 2 * 3 +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { body, .. } = &program.declarations[0] { if let Stmt::Block(stmts) = body { if let Stmt::Return(Some(expr)) = &stmts[0] { - // Top-level node must be Add match expr { Expr::BinaryOp { op: BinaryOp::Add, @@ -660,7 +1020,6 @@ mod tests { right, } => { assert_eq!(**left, Expr::Literal(Literal::Int(1))); - // Right child must be Mul(2, 3) match right.as_ref() { Expr::BinaryOp { op: BinaryOp::Mul, @@ -682,15 +1041,44 @@ mod tests { panic!("Unexpected AST structure"); } + #[test] + fn test_parse_function_with_params() { + let source = "\ +def add(a: int, b: int) -> int: + return a + b +"; + let program = parse(source).unwrap(); + + if let TopLevel::Function { + name, + parameters, + return_type, + .. + } = &program.declarations[0] + { + assert_eq!(name, "add"); + assert_eq!( + *parameters, + vec![ + ("a".to_string(), ViprType::Int), + ("b".to_string(), ViprType::Int), + ] + ); + assert_eq!(*return_type, ViprType::Int); + } else { + panic!("Expected Function declaration"); + } + } + #[test] fn test_parse_syntax_error() { - // Missing semicolon after variable declaration - let result = parse("int x = 42"); + // Missing colon after if condition. + let result = parse("def main() -> void:\n if true\n pass\n"); assert!(result.is_err()); let err = result.unwrap_err(); assert!( - err.contains("Expected ';'"), - "Error should mention missing semicolon, got: {}", + err.contains("Expected ':'"), + "Error should mention missing colon, got: {}", err ); } diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index b6f9c11..0f16653 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -3,142 +3,284 @@ pub mod symbol_table; use crate::parser::ast::*; use symbol_table::{Symbol, SymbolTable}; -/// The Vipr semantic analyzer — traverses a validated AST and enforces type -/// rules, scope rules, and language constraints. pub struct SemanticAnalyzer { symbol_table: SymbolTable, - /// The return type of the function currently being checked. - current_return_type: Option, + loop_depth: usize, + current_function_return_type: Option, } impl SemanticAnalyzer { pub fn new() -> Self { Self { symbol_table: SymbolTable::new(), - current_return_type: None, + loop_depth: 0, + current_function_return_type: None, } } - // ── Program ────────────────────────────────────────────── - - /// Entry point: check the entire program. - pub fn check_program(&mut self, program: &Program) -> Result<(), String> { - // First pass — register every function signature so that calls can - // appear before the callee's definition. + /// Perform a full semantic analysis of the program. + pub fn analyze(&mut self, program: &Program) -> Result<(), String> { + // Pass 1: Global declarations for decl in &program.declarations { - if let Decl::Function { - name, - parameters, - return_type, - .. - } = decl - { - let param_types: Vec = - parameters.iter().map(|(t, _)| t.clone()).collect(); + match decl { + TopLevel::Function { name, parameters, return_type, .. } => { + let param_types = parameters.iter().map(|(_, t)| t.clone()).collect(); + self.symbol_table.insert( + name.clone(), + Symbol::Function { + param_types, + return_type: return_type.clone(), + }, + )?; + } + TopLevel::GlobalLet { identifiers, vipr_type, .. } => { + if *vipr_type == ViprType::Void { + return Err("Variables cannot have type void".to_string()); + } + for id in identifiers { + self.symbol_table.insert( + id.clone(), + Symbol::Variable { + vipr_type: vipr_type.clone(), + is_const: false, + }, + )?; + } + } + TopLevel::GlobalConst { identifiers, vipr_type, .. } => { + if *vipr_type == ViprType::Void { + return Err("Constants cannot have type void".to_string()); + } + for id in identifiers { + self.symbol_table.insert( + id.clone(), + Symbol::Variable { + vipr_type: vipr_type.clone(), + is_const: true, + }, + )?; + } + } + } + } - self.symbol_table.insert( - name.clone(), - Symbol::Function { - param_types, - return_type: return_type.clone(), - }, - )?; + // Check main function rule + let main_sym = self + .symbol_table + .lookup("main") + .ok_or_else(|| "Function 'main' is not defined".to_string())?; + match main_sym { + Symbol::Function { param_types, return_type } => { + if !param_types.is_empty() { + return Err("Function 'main' must take 0 parameters".to_string()); + } + if *return_type != ViprType::Void { + return Err("Function 'main' must return void".to_string()); + } } + _ => return Err("'main' must be a function".to_string()), } - // Second pass — fully check every declaration. + // Pass 2: Check bodies and initializers for decl in &program.declarations { - self.check_decl(decl)?; - } + match decl { + TopLevel::GlobalLet { identifiers, vipr_type, initializers } => { + if let Some(inits) = initializers { + if identifiers.len() != inits.len() { + return Err("Multiple declarations and assignments must have matching identifier/expression counts".to_string()); + } + for init in inits { + let ty = self.check_const_expr(init)?; + if ty != *vipr_type { + return Err(format!("Type mismatch in global variable initializer: expected {:?}, got {:?}", vipr_type, ty)); + } + } + } + } + TopLevel::GlobalConst { identifiers, vipr_type, initializers } => { + if identifiers.len() != initializers.len() { + return Err("Multiple declarations and assignments must have matching identifier/expression counts".to_string()); + } + for init in initializers { + let ty = self.check_const_expr(init)?; + if ty != *vipr_type { + return Err(format!("Type mismatch in global constant initializer: expected {:?}, got {:?}", vipr_type, ty)); + } + } + } + TopLevel::Function { parameters, return_type, body, .. } => { + self.current_function_return_type = Some(return_type.clone()); + self.symbol_table.enter_scope(); - // Every program must contain a `main` function. - match self.symbol_table.lookup("main") { - Some(Symbol::Function { .. }) => Ok(()), - _ => Err("Program must contain a 'main' function".to_string()), + for (param_name, param_type) in parameters { + if *param_type == ViprType::Void { + return Err("Parameters cannot have type void".to_string()); + } + self.symbol_table.insert( + param_name.clone(), + Symbol::Variable { + vipr_type: param_type.clone(), + is_const: false, + }, + )?; + } + + self.check_stmt(body)?; + + if *return_type != ViprType::Void && !self.guarantees_return(body) { + return Err("Non-void function must return a value on all control paths".to_string()); + } + + self.symbol_table.exit_scope(); + self.current_function_return_type = None; + } + } } + + Ok(()) } - // ── Declarations ───────────────────────────────────────── + // ── Expression Checking ────────────────────────────────────── - fn check_decl(&mut self, decl: &Decl) -> Result<(), String> { - match decl { - Decl::Variable { - is_const, - vipr_type, - name, - initializer, - } => { - if *vipr_type == ViprType::Void { - return Err(format!( - "Cannot declare variable '{}' with type 'void'", - name - )); + /// Validates that an expression can be evaluated at compile-time (constant expression). + fn check_const_expr(&mut self, expr: &Expr) -> Result { + match expr { + Expr::Literal(lit) => Ok(self.get_literal_type(lit)), + Expr::Identifier(name) => { + if let Some(Symbol::Variable { vipr_type, is_const }) = self.symbol_table.lookup_global(name) { + if *is_const { + return Ok(vipr_type.clone()); + } } + Err(format!("Identifier '{}' in constant expression must be a previously declared global constant", name)) + } + Expr::BinaryOp { left, op, right } => { + let l_ty = self.check_const_expr(left)?; + let r_ty = self.check_const_expr(right)?; + self.check_binary_op(&l_ty, op, &r_ty) + } + Expr::UnaryOp { op, expr } => { + let e_ty = self.check_const_expr(expr)?; + self.check_unary_op(op, &e_ty) + } + Expr::FunctionCall { .. } => Err("Function calls are not allowed in constant expressions".to_string()), + } + } - if *is_const && initializer.is_none() { - return Err(format!("Constant '{}' must be initialized", name)); + /// Validates a normal runtime expression. + fn check_expr(&mut self, expr: &Expr) -> Result { + match expr { + Expr::Literal(lit) => Ok(self.get_literal_type(lit)), + Expr::Identifier(name) => { + match self.symbol_table.lookup(name) { + Some(Symbol::Variable { vipr_type, .. }) => Ok(vipr_type.clone()), + Some(Symbol::Function { .. }) => Err(format!("'{}' is a function, not a variable", name)), + None => Err(format!("Undefined variable '{}'", name)), } - - if let Some(init_expr) = initializer { - let init_type = self.check_expr(init_expr)?; - if init_type != *vipr_type { - return Err(format!( - "Type mismatch in declaration of '{}': expected {:?}, found {:?}", - name, vipr_type, init_type - )); + } + Expr::BinaryOp { left, op, right } => { + let l_ty = self.check_expr(left)?; + let r_ty = self.check_expr(right)?; + self.check_binary_op(&l_ty, op, &r_ty) + } + Expr::UnaryOp { op, expr } => { + let e_ty = self.check_expr(expr)?; + self.check_unary_op(op, &e_ty) + } + Expr::FunctionCall { name, args } => { + let symbol = self.symbol_table.lookup(name).cloned(); + match symbol { + Some(Symbol::Function { param_types, return_type }) => { + if args.len() != param_types.len() { + return Err(format!("Function '{}' expects {} arguments, got {}", name, param_types.len(), args.len())); + } + for (arg, expected_ty) in args.iter().zip(param_types.iter()) { + let arg_ty = self.check_expr(arg)?; + if arg_ty != *expected_ty { + return Err(format!("Argument type mismatch in function '{}': expected {:?}, got {:?}", name, expected_ty, arg_ty)); + } + } + // Note: return_type could be Void. The caller handles if void is allowed. + Ok(return_type) } + Some(Symbol::Variable { .. }) => Err(format!("'{}' is a variable, not a function", name)), + None => Err(format!("Undefined function '{}'", name)), } - - self.symbol_table.insert( - name.clone(), - Symbol::Variable { - vipr_type: vipr_type.clone(), - is_const: *is_const, - }, - )?; - - Ok(()) } + } + } - Decl::Function { - name: _, - parameters, - return_type, - body, - } => { - // Signature was already registered in the first pass. - self.current_return_type = Some(return_type.clone()); - self.symbol_table.enter_scope(); + fn get_literal_type(&self, lit: &Literal) -> ViprType { + match lit { + Literal::Int(_) => ViprType::Int, + Literal::Float(_) => ViprType::Float, + Literal::Bool(_) => ViprType::Bool, + Literal::Char(_) => ViprType::Char, + Literal::String(_) => ViprType::String, + } + } - // Register parameters as local variables. - for (param_type, param_name) in parameters { - self.symbol_table.insert( - param_name.clone(), - Symbol::Variable { - vipr_type: param_type.clone(), - is_const: false, - }, - )?; - } + fn check_binary_op(&self, l: &ViprType, op: &BinaryOp, r: &ViprType) -> Result { + if l == &ViprType::Void || r == &ViprType::Void { + return Err("Cannot use void in expressions".to_string()); + } - // Process body statements directly so that parameters and - // top-level locals share the same scope. - if let Stmt::Block(stmts) = body { - for stmt in stmts { - self.check_stmt(stmt)?; - } + match op { + BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => { + match (l, r) { + (ViprType::Int, ViprType::Int) => Ok(ViprType::Int), + (ViprType::Float, ViprType::Float) => Ok(ViprType::Float), + (ViprType::String, ViprType::String) if *op == BinaryOp::Add => Ok(ViprType::String), + _ => Err(format!("Invalid types for arithmetic operator {:?}: {:?} and {:?}", op, l, r)), + } + } + BinaryOp::Lt | BinaryOp::Lte | BinaryOp::Gt | BinaryOp::Gte => { + match (l, r) { + (ViprType::Int, ViprType::Int) => Ok(ViprType::Bool), + (ViprType::Float, ViprType::Float) => Ok(ViprType::Bool), + (ViprType::Char, ViprType::Char) => Ok(ViprType::Bool), + _ => Err(format!("Invalid types for comparison operator {:?}: {:?} and {:?}", op, l, r)), + } + } + BinaryOp::Eq | BinaryOp::Neq => { + if l == r { + Ok(ViprType::Bool) } else { - self.check_stmt(body)?; + Err(format!("Type mismatch for equality operator: {:?} and {:?}", l, r)) } + } + BinaryOp::And | BinaryOp::Or => { + match (l, r) { + (ViprType::Bool, ViprType::Bool) => Ok(ViprType::Bool), + _ => Err(format!("Invalid types for logical operator {:?}: {:?} and {:?}", op, l, r)), + } + } + } + } - self.symbol_table.exit_scope(); - self.current_return_type = None; - Ok(()) + fn check_unary_op(&self, op: &UnaryOp, t: &ViprType) -> Result { + if t == &ViprType::Void { + return Err("Cannot use void in expressions".to_string()); + } + + match op { + UnaryOp::Neg => { + match t { + ViprType::Int => Ok(ViprType::Int), + ViprType::Float => Ok(ViprType::Float), + _ => Err(format!("Invalid type for unary minus: {:?}", t)), + } + } + UnaryOp::Not => { + match t { + ViprType::Bool => Ok(ViprType::Bool), + _ => Err(format!("Invalid type for logical not: {:?}", t)), + } } } } - // ── Statements ─────────────────────────────────────────── + // ── Statement Checking ─────────────────────────────────────── fn check_stmt(&mut self, stmt: &Stmt) -> Result<(), String> { match stmt { @@ -148,380 +290,227 @@ impl SemanticAnalyzer { self.check_stmt(s)?; } self.symbol_table.exit_scope(); - Ok(()) } - - Stmt::ExpressionStmt(expr) => { - self.check_expr(expr)?; - Ok(()) - } - - Stmt::Assignment { identifier, value } => { - // Clone the symbol to release the borrow on the symbol table. - let sym = self - .symbol_table - .lookup(identifier) - .ok_or(format!("Undefined variable '{}'", identifier))? - .clone(); - - match &sym { - Symbol::Variable { - vipr_type, - is_const, - } => { - if *is_const { - return Err(format!( - "Cannot reassign constant '{}'", - identifier - )); + Stmt::Let { identifiers, vipr_type, initializers } => { + if *vipr_type == ViprType::Void { + return Err("Variables cannot have type void".to_string()); + } + if let Some(inits) = initializers { + if identifiers.len() != inits.len() { + return Err("Multiple declarations and assignments must have matching identifier/expression counts".to_string()); + } + for init in inits { + let ty = self.check_expr(init)?; + if ty == ViprType::Void { + return Err("Cannot assign void to a variable".to_string()); } - let val_type = self.check_expr(value)?; - if val_type != *vipr_type { - return Err(format!( - "Type mismatch in assignment to '{}': expected {:?}, found {:?}", - identifier, vipr_type, val_type - )); + if ty != *vipr_type { + return Err(format!("Type mismatch in let declaration: expected {:?}, got {:?}", vipr_type, ty)); } } - Symbol::Function { .. } => { - return Err(format!( - "Cannot assign to function '{}'", - identifier - )); - } } - Ok(()) + for id in identifiers { + self.symbol_table.insert( + id.clone(), + Symbol::Variable { + vipr_type: vipr_type.clone(), + is_const: false, + }, + )?; + } } - - Stmt::If { - condition, - then_branch, - else_if_branches, - else_branch, - } => { - let cond_type = self.check_expr(condition)?; - if cond_type != ViprType::Bool { - return Err(format!( - "If condition must be bool, found {:?}", - cond_type - )); + Stmt::Const { identifiers, vipr_type, initializers } => { + if *vipr_type == ViprType::Void { + return Err("Constants cannot have type void".to_string()); } - - self.check_stmt(then_branch)?; - - for (elif_cond, elif_body) in else_if_branches { - let elif_type = self.check_expr(elif_cond)?; - if elif_type != ViprType::Bool { - return Err(format!( - "Else-if condition must be bool, found {:?}", - elif_type - )); + if identifiers.len() != initializers.len() { + return Err("Multiple declarations and assignments must have matching identifier/expression counts".to_string()); + } + for init in initializers { + let ty = self.check_expr(init)?; + if ty == ViprType::Void { + return Err("Cannot assign void to a constant".to_string()); + } + if ty != *vipr_type { + return Err(format!("Type mismatch in const declaration: expected {:?}, got {:?}", vipr_type, ty)); } - self.check_stmt(elif_body)?; } - - if let Some(else_body) = else_branch { - self.check_stmt(else_body)?; + for id in identifiers { + self.symbol_table.insert( + id.clone(), + Symbol::Variable { + vipr_type: vipr_type.clone(), + is_const: true, + }, + )?; } - Ok(()) } - - Stmt::While { condition, body } => { - let cond_type = self.check_expr(condition)?; - if cond_type != ViprType::Bool { - return Err(format!( - "While condition must be bool, found {:?}", - cond_type - )); + Stmt::Assignment { targets, values } => { + if targets.len() != values.len() { + return Err("Multiple assignments must have matching identifier/expression counts".to_string()); + } + for (target, value) in targets.iter().zip(values.iter()) { + let val_ty = self.check_expr(value)?; + if val_ty == ViprType::Void { + return Err("Cannot assign void to a variable".to_string()); + } + let sym = self.symbol_table.lookup(target).cloned(); + match sym { + Some(Symbol::Variable { vipr_type, is_const }) => { + if is_const { + return Err(format!("Cannot reassign constant '{}'", target)); + } + if vipr_type != val_ty { + return Err(format!("Type mismatch in assignment to '{}': expected {:?}, got {:?}", target, vipr_type, val_ty)); + } + } + Some(Symbol::Function { .. }) => return Err(format!("Cannot assign to function '{}'", target)), + None => return Err(format!("Undefined variable '{}'", target)), + } } - self.check_stmt(body)?; - Ok(()) } - - Stmt::For { - init, - condition, - increment, - body, - } => { - // The for-loop gets its own scope so the init variable is - // visible in the condition, increment, and body. - self.symbol_table.enter_scope(); - - self.check_stmt(init)?; - - let cond_type = self.check_expr(condition)?; - if cond_type != ViprType::Bool { - return Err(format!( - "For condition must be bool, found {:?}", - cond_type - )); + Stmt::If { condition, body, elif_branches, else_branch } => { + let cond_ty = self.check_expr(condition)?; + if cond_ty != ViprType::Bool { + return Err("Condition in 'if' must be bool".to_string()); } - - self.check_stmt(increment)?; self.check_stmt(body)?; - - self.symbol_table.exit_scope(); - Ok(()) - } - - Stmt::Return(expr) => { - let expected_type = self - .current_return_type - .clone() - .ok_or("Return statement outside of function".to_string())?; - - match expr { - Some(e) => { - let ret_type = self.check_expr(e)?; - if ret_type != expected_type { - return Err(format!( - "Return type mismatch: expected {:?}, found {:?}", - expected_type, ret_type - )); - } - } - None => { - if expected_type != ViprType::Void { - return Err(format!( - "Missing return value in non-void function (expected {:?})", - expected_type - )); - } + for (elif_cond, elif_body) in elif_branches { + let elif_ty = self.check_expr(elif_cond)?; + if elif_ty != ViprType::Bool { + return Err("Condition in 'elif' must be bool".to_string()); } + self.check_stmt(elif_body)?; + } + if let Some(else_b) = else_branch { + self.check_stmt(else_b)?; } - Ok(()) } - - Stmt::VariableDecl { - is_const, - vipr_type, - name, - initializer, - } => { - if *vipr_type == ViprType::Void { - return Err(format!( - "Cannot declare variable '{}' with type 'void'", - name - )); + Stmt::While { condition, body } => { + let cond_ty = self.check_expr(condition)?; + if cond_ty != ViprType::Bool { + return Err("Condition in 'while' must be bool".to_string()); } - - if *is_const && initializer.is_none() { - return Err(format!("Constant '{}' must be initialized", name)); + self.loop_depth += 1; + self.check_stmt(body)?; + self.loop_depth -= 1; + } + Stmt::For { variable, vipr_type, start, end, step, body } => { + if *vipr_type != ViprType::Int { + return Err("For loop variable must be of type int".to_string()); } - - if let Some(init_expr) = initializer { - let init_type = self.check_expr(init_expr)?; - if init_type != *vipr_type { - return Err(format!( - "Type mismatch in declaration of '{}': expected {:?}, found {:?}", - name, vipr_type, init_type - )); - } + let start_ty = self.check_expr(start)?; + let end_ty = self.check_expr(end)?; + let step_ty = self.check_expr(step)?; + if start_ty != ViprType::Int || end_ty != ViprType::Int || step_ty != ViprType::Int { + return Err("Range arguments must be of type int".to_string()); } - + + self.symbol_table.enter_scope(); self.symbol_table.insert( - name.clone(), + variable.clone(), Symbol::Variable { - vipr_type: vipr_type.clone(), - is_const: *is_const, + vipr_type: ViprType::Int, + is_const: false, }, )?; - Ok(()) + + self.loop_depth += 1; + self.check_stmt(body)?; + self.loop_depth -= 1; + + self.symbol_table.exit_scope(); } - } - } - - // ── Expressions ────────────────────────────────────────── - - /// Type-check an expression and return its computed type. - fn check_expr(&mut self, expr: &Expr) -> Result { - match expr { - // ── Literals ────────────────────────────────── - Expr::Literal(lit) => Ok(match lit { - Literal::Int(_) => ViprType::Int, - Literal::Float(_) => ViprType::Float, - Literal::Bool(_) => ViprType::Bool, - Literal::Char(_) => ViprType::Char, - Literal::String(_) => ViprType::String, - }), - - // ── Identifiers ────────────────────────────── - Expr::Identifier(name) => match self.symbol_table.lookup(name) { - Some(Symbol::Variable { vipr_type, .. }) => Ok(vipr_type.clone()), - Some(Symbol::Function { .. }) => { - Err(format!("'{}' is a function, not a variable", name)) - } - None => Err(format!("Undefined variable '{}'", name)), - }, - - // ── Binary operations ───────────────────────── - Expr::BinaryOp { left, op, right } => { - let left_type = self.check_expr(left)?; - let right_type = self.check_expr(right)?; - - if left_type != right_type { - return Err(format!( - "Type mismatch in binary operation: {:?} vs {:?}", - left_type, right_type - )); - } - - match op { - // Arithmetic → numeric in, same type out - BinaryOp::Add - | BinaryOp::Sub - | BinaryOp::Mul - | BinaryOp::Div - | BinaryOp::Mod => { - if left_type != ViprType::Int && left_type != ViprType::Float { - return Err(format!( - "Arithmetic operator requires numeric types, found {:?}", - left_type - )); + Stmt::Return(expr_opt) => { + let expected_ty = self.current_function_return_type.clone().expect("ICE: Not currently parsing a function body"); + match expr_opt { + Some(expr) => { + let actual_ty = self.check_expr(expr)?; + if actual_ty == ViprType::Void { + return Err("Cannot return void expression".to_string()); } - Ok(left_type) - } - - // Comparison → numeric in, bool out - BinaryOp::Lt - | BinaryOp::Lte - | BinaryOp::Gt - | BinaryOp::Gte => { - if left_type != ViprType::Int && left_type != ViprType::Float { - return Err(format!( - "Comparison operator requires numeric types, found {:?}", - left_type - )); + if expected_ty == ViprType::Void { + return Err("Void function cannot return a value".to_string()); } - Ok(ViprType::Bool) - } - - // Equality → any non-void type in, bool out - BinaryOp::Eq | BinaryOp::Neq => { - if left_type == ViprType::Void { - return Err("Cannot compare void types".to_string()); + if actual_ty != expected_ty { + return Err(format!("Return type mismatch: expected {:?}, got {:?}", expected_ty, actual_ty)); } - Ok(ViprType::Bool) } - - // Logical → bool in, bool out - BinaryOp::And | BinaryOp::Or => { - if left_type != ViprType::Bool { - return Err(format!( - "Logical operator requires bool operands, found {:?}", - left_type - )); + None => { + if expected_ty != ViprType::Void { + return Err("Non-void function must return a value".to_string()); } - Ok(ViprType::Bool) } } } - - // ── Unary operations ────────────────────────── - Expr::UnaryOp { op, expr } => { - let expr_type = self.check_expr(expr)?; - match op { - UnaryOp::Not => { - if expr_type != ViprType::Bool { - return Err(format!( - "Logical NOT requires bool operand, found {:?}", - expr_type - )); - } - Ok(ViprType::Bool) - } - UnaryOp::Neg => { - if expr_type != ViprType::Int && expr_type != ViprType::Float { - return Err(format!( - "Negation requires numeric type, found {:?}", - expr_type - )); - } - Ok(expr_type) - } + Stmt::Break => { + if self.loop_depth == 0 { + return Err("break statement outside of a loop".to_string()); } } - - // ── Function calls ──────────────────────────── - Expr::FunctionCall { name, args } => { - // Built-in: print(any_expr) - if name == "print" { - if args.len() != 1 { - return Err("print() expects exactly one argument".to_string()); - } - self.check_expr(&args[0])?; - return Ok(ViprType::Void); + Stmt::Continue => { + if self.loop_depth == 0 { + return Err("continue statement outside of a loop".to_string()); } - - // Built-in: scan(variable) - if name == "scan" { - if args.len() != 1 { - return Err("scan() expects exactly one argument".to_string()); + } + Stmt::Pass => {} + Stmt::Print(exprs) => { + for expr in exprs { + let ty = self.check_expr(expr)?; + if ty == ViprType::Void { + return Err("Cannot print void".to_string()); } - match &args[0] { - Expr::Identifier(var_name) => { - let sym = self - .symbol_table - .lookup(var_name) - .ok_or(format!("Undefined variable '{}'", var_name))?; - if let Symbol::Variable { is_const: true, .. } = sym { - return Err(format!( - "Cannot scan into constant '{}'", - var_name - )); + } + } + Stmt::Input(identifiers) => { + for id in identifiers { + let sym = self.symbol_table.lookup(id).cloned(); + match sym { + Some(Symbol::Variable { is_const, .. }) => { + if is_const { + return Err(format!("Cannot use input on constant '{}'", id)); } } - _ => { - return Err( - "scan() argument must be a variable".to_string() - ); - } + Some(Symbol::Function { .. }) => return Err(format!("Cannot use input on function '{}'", id)), + None => return Err(format!("Undefined variable '{}'", id)), } - return Ok(ViprType::Void); } + } + Stmt::ExprStmt(expr) => { + // Evaluated for side effects. Only valid if it's a void function call or similar, + // but VIPR spec says "A call to a void function may only appear as a standalone expression statement." + // Wait, non-void functions can also be called as standalone statements. + self.check_expr(expr)?; + } + } + Ok(()) + } - // User-defined function - let sym = self - .symbol_table - .lookup(name) - .ok_or(format!("Undefined function '{}'", name))? - .clone(); - - match sym { - Symbol::Function { - param_types, - return_type, - } => { - if args.len() != param_types.len() { - return Err(format!( - "Function '{}' expects {} argument(s), got {}", - name, - param_types.len(), - args.len() - )); - } - for (i, (arg, expected)) in - args.iter().zip(param_types.iter()).enumerate() - { - let arg_type = self.check_expr(arg)?; - if arg_type != *expected { - return Err(format!( - "Argument {} of '{}': expected {:?}, found {:?}", - i + 1, - name, - expected, - arg_type - )); - } - } - Ok(return_type) + /// Implements conservative return-analysis rule. + fn guarantees_return(&self, stmt: &Stmt) -> bool { + match stmt { + Stmt::Return(_) => true, + Stmt::Block(stmts) => stmts.iter().any(|s| self.guarantees_return(s)), + Stmt::If { body, elif_branches, else_branch, .. } => { + if else_branch.is_none() { + return false; + } + if !self.guarantees_return(body) { + return false; + } + for (_, elif_body) in elif_branches { + if !self.guarantees_return(elif_body) { + return false; } - Symbol::Variable { .. } => { - Err(format!("'{}' is not a function", name)) + } + if let Some(else_b) = else_branch { + if !self.guarantees_return(else_b) { + return false; } } + true } + _ => false, // Loops and simple statements do not guarantee return } } } @@ -532,87 +521,91 @@ impl SemanticAnalyzer { mod tests { use super::*; - #[test] - fn test_valid_type_checking() { - // func main() int { int x = 5; return x; } - let program = Program { - declarations: vec![Decl::Function { - name: "main".to_string(), - parameters: vec![], - return_type: ViprType::Int, - body: Stmt::Block(vec![ - Stmt::VariableDecl { - is_const: false, - vipr_type: ViprType::Int, - name: "x".to_string(), - initializer: Some(Expr::Literal(Literal::Int(5))), - }, - Stmt::Return(Some(Expr::Identifier("x".to_string()))), - ]), - }], - }; - + // Helper to run analysis + fn analyze(program: Program) -> Result<(), String> { let mut analyzer = SemanticAnalyzer::new(); - assert!(analyzer.check_program(&program).is_ok()); + analyzer.analyze(&program) } #[test] - fn test_error_const_reassignment() { - // func main() void { const int X = 5; X = 10; } - let program = Program { - declarations: vec![Decl::Function { - name: "main".to_string(), - parameters: vec![], - return_type: ViprType::Void, - body: Stmt::Block(vec![ - Stmt::VariableDecl { - is_const: true, - vipr_type: ViprType::Int, - name: "X".to_string(), - initializer: Some(Expr::Literal(Literal::Int(5))), - }, - Stmt::Assignment { - identifier: "X".to_string(), - value: Expr::Literal(Literal::Int(10)), - }, - ]), - }], + fn test_invalid_main_signature() { + let prog = Program { + declarations: vec![ + TopLevel::Function { + name: "main".to_string(), + parameters: vec![], + return_type: ViprType::Int, // Invalid: should be void + body: Stmt::Block(vec![Stmt::Return(Some(Expr::Literal(Literal::Int(0))))]), + } + ], }; - - let mut analyzer = SemanticAnalyzer::new(); - let result = analyzer.check_program(&program); - assert!(result.is_err()); - assert!( - result.unwrap_err().contains("Cannot reassign constant"), - "Expected const-reassignment error" - ); + let err = analyze(prog).unwrap_err(); + assert!(err.contains("must return void")); } #[test] - fn test_error_type_mismatch() { - // func main() void { int x = true; } - let program = Program { - declarations: vec![Decl::Function { - name: "main".to_string(), - parameters: vec![], - return_type: ViprType::Void, - body: Stmt::Block(vec![Stmt::VariableDecl { - is_const: false, - vipr_type: ViprType::Int, - name: "x".to_string(), - initializer: Some(Expr::Literal(Literal::Bool(true))), - }]), - }], + fn test_break_outside_loop() { + let prog = Program { + declarations: vec![ + TopLevel::Function { + name: "main".to_string(), + parameters: vec![], + return_type: ViprType::Void, + body: Stmt::Block(vec![Stmt::Break]), + } + ], }; + let err = analyze(prog).unwrap_err(); + assert!(err.contains("outside of a loop")); + } - let mut analyzer = SemanticAnalyzer::new(); - let result = analyzer.check_program(&program); - assert!(result.is_err()); - let err = result.unwrap_err(); - assert!( - err.contains("Type mismatch") && err.contains("'x'"), - "Expected type-mismatch error for 'x', got: {}", - err - ); + #[test] + fn test_non_void_function_lacks_return_in_else() { + let prog = Program { + declarations: vec![ + TopLevel::Function { + name: "test".to_string(), + parameters: vec![], + return_type: ViprType::Int, + body: Stmt::Block(vec![ + Stmt::If { + condition: Expr::Literal(Literal::Bool(true)), + body: Box::new(Stmt::Block(vec![Stmt::Return(Some(Expr::Literal(Literal::Int(1))))])), + elif_branches: vec![], + else_branch: Some(Box::new(Stmt::Block(vec![Stmt::Pass]))), // No return here + } + ]), + }, + TopLevel::Function { + name: "main".to_string(), + parameters: vec![], + return_type: ViprType::Void, + body: Stmt::Block(vec![Stmt::Pass]), + } + ], + }; + let err = analyze(prog).unwrap_err(); + assert!(err.contains("return a value on all control paths")); + } + + #[test] + fn test_valid_program() { + let prog = Program { + declarations: vec![ + TopLevel::Function { + name: "main".to_string(), + parameters: vec![], + return_type: ViprType::Void, + body: Stmt::Block(vec![ + Stmt::Let { + identifiers: vec!["x".to_string()], + vipr_type: ViprType::Int, + initializers: Some(vec![Expr::Literal(Literal::Int(5))]), + } + ]), + } + ], + }; + assert!(analyze(prog).is_ok()); } } diff --git a/src/semantic/symbol_table.rs b/src/semantic/symbol_table.rs index 1ffae7c..ba13035 100644 --- a/src/semantic/symbol_table.rs +++ b/src/semantic/symbol_table.rs @@ -1,61 +1,59 @@ use std::collections::HashMap; use crate::parser::ast::ViprType; -/// Information about a declared symbol (variable or function). +/// Information about a declared symbol (variable, constant, or function). #[derive(Debug, Clone)] pub enum Symbol { - /// A variable or constant. Variable { vipr_type: ViprType, is_const: bool, }, - /// A function with typed parameters and a return type. Function { param_types: Vec, return_type: ViprType, }, } -/// A block-scoped symbol table implemented as a stack of environments. +/// A scoped symbol table for semantic analysis. pub struct SymbolTable { scopes: Vec>, } impl SymbolTable { - /// Create a new symbol table with an empty global scope. + /// Create a new symbol table with a global scope. pub fn new() -> Self { Self { scopes: vec![HashMap::new()], } } - /// Push a new empty scope onto the stack. + /// Enter a new inner scope. pub fn enter_scope(&mut self) { self.scopes.push(HashMap::new()); } - /// Pop the innermost scope from the stack. + /// Exit the current innermost scope. pub fn exit_scope(&mut self) { - self.scopes.pop(); + if self.scopes.len() > 1 { + self.scopes.pop(); + } } - /// Insert a symbol into the current (innermost) scope. - /// Returns an error if the name is already declared in this scope. - pub fn insert(&mut self, name: String, symbol: Symbol) -> Result<(), String> { - let scope = self - .scopes - .last_mut() - .expect("SymbolTable must always have at least one scope"); - if scope.contains_key(&name) { - return Err(format!("'{}' is already declared in this scope", name)); - } - scope.insert(name, symbol); - Ok(()) + /// Insert a symbol into the current innermost scope. + /// Returns an error if the symbol is already defined in the current scope. + pub fn insert(&mut self, name: String, symbol: Symbol) -> Result<(), String> { + let current_scope = self.scopes.last_mut().expect("ICE: Symbol table scopes stack is empty"); + if current_scope.contains_key(&name) { + Err(format!("Name '{}' is already defined in this scope", name)) + } else { + current_scope.insert(name, symbol); + Ok(()) + } } - /// Look up a symbol by name, searching from the innermost scope outward. + /// Lookup a symbol, searching from the innermost scope outwards. pub fn lookup(&self, name: &str) -> Option<&Symbol> { for scope in self.scopes.iter().rev() { if let Some(symbol) = scope.get(name) { @@ -64,80 +62,9 @@ impl SymbolTable { } None } -} - -// ── Tests ───────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_scope_visibility() { - let mut table = SymbolTable::new(); - - // Insert into global scope - table - .insert( - "x".into(), - Symbol::Variable { - vipr_type: ViprType::Int, - is_const: false, - }, - ) - .unwrap(); - - // Visible in global scope - assert!(table.lookup("x").is_some()); - - // Enter inner scope — x still visible - table.enter_scope(); - assert!(table.lookup("x").is_some()); - - // Shadow x in inner scope - table - .insert( - "x".into(), - Symbol::Variable { - vipr_type: ViprType::Float, - is_const: false, - }, - ) - .unwrap(); - - // Inner x should be Float - if let Some(Symbol::Variable { vipr_type, .. }) = table.lookup("x") { - assert_eq!(*vipr_type, ViprType::Float); - } - - // Exit inner scope — original x is back - table.exit_scope(); - if let Some(Symbol::Variable { vipr_type, .. }) = table.lookup("x") { - assert_eq!(*vipr_type, ViprType::Int); - } - } - - #[test] - fn test_duplicate_in_same_scope() { - let mut table = SymbolTable::new(); - - table - .insert( - "y".into(), - Symbol::Variable { - vipr_type: ViprType::Int, - is_const: false, - }, - ) - .unwrap(); - let result = table.insert( - "y".into(), - Symbol::Variable { - vipr_type: ViprType::Int, - is_const: false, - }, - ); - assert!(result.is_err()); + /// Lookup a symbol specifically in the global scope. + pub fn lookup_global(&self, name: &str) -> Option<&Symbol> { + self.scopes.first().and_then(|scope| scope.get(name)) } } diff --git a/tests/test.vipr b/tests/test.vipr new file mode 100644 index 0000000..ce1501b --- /dev/null +++ b/tests/test.vipr @@ -0,0 +1,14 @@ +def factorial(n: int) -> int: + if n <= 1: + return 1 + return n * factorial(n - 1) + +def main() -> void: + let n: int = 5 + let result: int = factorial(n) + print("Factorial of ", n, " is ", result) + + let a, b: int = 1, 2 + print("Before swap: a = ", a, ", b = ", b) + a, b = b, a + print("After swap: a = ", a, ", b = ", b) diff --git a/tests/test_program b/tests/test_program deleted file mode 100755 index 1847fb7d648e81a9be290968ae2041a59ff17319..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12616 zcmeHNeQX?85r21%lj}$9i=EJG5}Ivsss!Toont$x)6lh@v(Ly$jGd+d5jJ~wYkQ@; zz2j~#_zEEbLy6-cnTQVwD*i-prfd%zLvR_vYOmj}Gh`dg^nTMv}{)pibuO zA944JH;DrRVqS^EIo|xB>=rj$HQ8Xkk>;lmXB>)%3p^stML^YLgZTnYh&ks4$B0LW z#uKV@am-t!7a^FVEfR7(Ld_^4*OqWs!t3LbH{Kz_9U>fah3uJgywNwD!XOt_9F z*KtRaCL3k$sznEJj`wlEVLty;;@fJ3;w^Xm0(9M&(`AvlTnoYQG7Ri{TKw+2>&@WF z7hd`C)%t~%>xWNkog)o@ZG7aPv8n5U*i8L5x4|Rwl?#r)v=E#6RbZ|>%u?qm=qhbK z4d=#98njTJ!2UO{lz=vW31tlX<^qW5GxF!b>DN9ToBmU5>gtW*1JTHZ$fel){0~|qso@ZvH4iQShz0)Yz6PsgU;SbP>{ak2hqpz$4^ zyDJ{rUps|{qma7L^s(zx$FBR1*Uv<+UwZ^jQ;WXo`NcngkLfun`a%2+h`!6QnZq|P z%^?(!N^CxQ}}z!i_JuD!~k^WcBwQUy@e^b7CFBNri;fx zb#2E?^cHj%F+}w0O!VsX#q+D+E|gPUOo7Q%^p+2dc10II|N8kLbS?IS_R<`VU0&}c zR~Q4-sFs0R25K3oWuTUUS_WzvsAZs*f&Y&Tw56@pHEjW3h>oEL;o_CC#qo)na7TKI-J7WUX zIEgMXFf`cPr?;!^YG=85NTC9liUZ)}x{uBp=9|fO8#LSa02j)gW$zpzbGM z{}xQqFFK)(bh%BF9OsR5WV%wJ=UP_wK|O}F3J%^nKsivMzcU(lJlN0FXQUE^l;K$R zxZ+Hh%9xeQJ4z(1^jqbGpXbYiG-QYj}=m>;-g^W+fcVF3>>#ww!Kp-Ilw;! zd{-soU9{4ffzW|Q4G?MSx5#gH*`ZhecDliL)!dz~IBLKL^(O}Q2AnMS6 z3+Q;Srv5+G?JbX?8hxeU_#R+)p`xcD_?*9Q z^~&f}-De;s_Me91CFuWLWxxL@^rFHgyjBf7ODQ?b+@b?z>4b;tiO;Ra+#Qf|A~-TA0wH^&3GsVC2@TCgGkON zME3CSl9V~GP_{@pK>Y-N-x&Yz>a|PSRl5{+g@M1;p!EVF0GjYqV+CA`0 zcX)C~65@2VE2`R-FXoeGHs1z25^YA@YK6MhR1;!!xO=-8?dy(+zW(l=_b}`q>JHzd zd!Pqg3=ej9h|y?wSXiTQS~3L7wj78jqflGAr1lb9*nn`XL~G7WWfJZf!-_{I{djcJ zpGODz$9oNHM=Tx(dRlzjjY#=>jGxkVs%G;l^)atz!_V``J^b)9-a*fLH9rf4KT7Y9 zYBubL`FpkDu|9K;G4oiTpfO|ms*w+rE5th`Zy%1$2Rl>V?;e_#vB%Fr{NRc6!2a<4 ziF!eKVSPL4`F+d!7&Lt1VX-!>NfnL{=>Qx)d`r>$Q!3$vdlu-Q-`}kNDl~kex%_%R|;YV&<*8T~8LPf!HuI}rARyqcc{X}_-geGA+FUN#y9f7ikK>(c%q!S7|(`(fgk zYcGv}UJ)S?r2KIIre*BIpbvsQ=acPspuN9d@V&_T-OKnHCO_V|he59}xPqrZzsBDn zUi7@*i>Mb3;w614)*WLU z6>)RivO$?NbI^hf$DHn@^+YOTo48k-%;@8(Ox#H6Nhg!b>qcQxly@1`aCHlP3|_dp zk;@rH-L#!tQH2whw6_Spx<|U@E<(buwTG^bQDLAlW4DV(brfDbT$oofn z_D1#SzCF4w^gW;0*Ryx94?MyTpB|0rZf|VQh|mXy_V@M->HGWpN23Sy13kS%Q9yY2 zo;BWThV5nCoi59`^{kso!!d-M4Q)~{?bos2-H!&>OE#w=bNP%uVc5x(DfGepV3o9N zy^uGPVvM(YA%dM0dNQTUUm?RW(HZ= z+{w9of?M*pcib+h@qz_^6LZu~(j0O5~5>&%+a#pV5{JkH42iW3?!S4}PtI&W)Rjech|IR;23t{wMNnZTdptBNxq)wCOM+ndBUiKl^ zCm=BIfOaLG-$%2Ae;zw4h;gjKe<>>Q{GQ%MFFLM2*xoySjqsd55kw6T1>*X{Rl2db zjU0c6{|;+(#w&zZJa}9uATf6(Ax5BqH3{Q2!fS+|BtxtlaGs3k`r(tH!&EVz>!5Rl z=eh?>Bze*);-JNvgYjHv1lG9rTt9JKwquUP3Dz(ya~%^ReAsm(yyGXe_yRQWnlPU0 zrkDrM@tO1dPZGYD9C95tSp|=^niqctbeI#4&%d{434e%mVnY0`3{$p!qtW#zLs{*Rz%$@AZ=N!w7f3vng4jb|_ZRnS%9c^!L*@Ct$| z$UFYmp|1fBdR)uvNd9it8;@n?e*|n}1-`s)|HodY4Gp#f6G@C`{twVr;(2|#Ncb&;W5zhl%LDL;;$L0bsha=*