diff --git a/CLAUDE.md b/CLAUDE.md index 8fed82c9..9de68ad8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -WFL (WebFirst Language) is a natural language programming language implemented in Rust. It features intuitive syntax like "store x as 5" and "display 'Hello'", with static typing, async support, and comprehensive development tooling. +WFL (WebFirst Language) is a natural language programming language implemented in Rust. It features intuitive syntax like "store x as 5" and "display 'Hello'", with static typing, async support, and comprehensive development tooling. The project is developed with AI assistance from Devin.ai, ChatGPT, and Claude. ## Memory Bank Context @@ -31,37 +31,121 @@ This project uses a comprehensive memory bank system located in `.kilocode/rules 3. Add new tests for new features 4. Document any edge cases -### Development Commands +## Core Development Commands + +### Building and Testing ```bash -# Build the project +# Standard build and test cycle +cargo fmt --all # Format code +cargo build # Build debug version +cargo test # Run all tests +cargo clippy --all-targets -- -D warnings # Lint code + +# Release build cargo build --release +cargo test --release + +# Run a single test +cargo test test_name + +# Run tests with output +cargo test -- --nocapture +``` +### Running WFL Programs +```bash # Run a WFL program -cargo run -- program.wfl +cargo run -- path/to/program.wfl -# Run with debugging -cargo run -- program.wfl --debug > debug.txt 2>&1 +# With debug output +cargo run -- --debug path/to/program.wfl > debug.txt 2>&1 + +# Interactive mode (REPL) +cargo run -- --interactive +``` -# Run all tests -cargo test +### Code Quality Tools +```bash +# Lint WFL code +cargo run -- --lint script.wfl + +# Static analysis +cargo run -- --analyze script.wfl + +# Auto-fix code issues +cargo run -- --fix script.wfl --in-place + +# Check configuration +cargo run -- --configCheck +cargo run -- --configFix +``` + +### VSCode Extension Development +```bash +cd vscode-extension +npm install +npm run compile # Build extension +npm run watch # Watch mode +npm run test # Run tests +``` -# Lint a program -cargo run -- --lint program.wfl +## CLI Flag Reference -# Analyze for issues -cargo run -- --analyze program.wfl +| Flag | Description | Example | +|------|-------------|---------| +| `--lex` | Output lexer tokens only | `cargo run -- --lex program.wfl` | +| `--parse` | Output AST only | `cargo run -- --parse program.wfl` | +| `--lint` | Check code style | `cargo run -- --lint program.wfl` | +| `--analyze` | Static analysis | `cargo run -- --analyze program.wfl` | +| `--fix` | Auto-format code | `cargo run -- --fix program.wfl` | +| `--in-place` | Modify file directly | `cargo run -- --fix program.wfl --in-place` | +| `--check` | Dry run for --fix | `cargo run -- --fix program.wfl --check` | +| `--debug` | Enable debug output | `cargo run -- --debug program.wfl` | +| `--config` | Specify config file | `cargo run -- --config custom.wflcfg program.wfl` | +| `--time` | Show execution time | `cargo run -- --time program.wfl` | +| `-v, --version` | Show version info | `cargo run -- --version` | -# Auto-fix formatting -cargo run -- --fix program.wfl --in-place +## Architecture Overview -# Start REPL -cargo run +### Module Structure +The codebase follows a pipeline architecture: -# Check code quality -cargo fmt --all -- --check -cargo clippy --all-targets --all-features -- -D warnings +``` +Input (.wfl) → Lexer → Parser → Analyzer → Type Checker → Interpreter → Output + ↓ ↓ ↓ ↓ ↓ + Tokens AST Validated Type Info Execution + AST Results ``` +1. **Lexer** (`src/lexer/`) - Tokenizes source code using Logos library +2. **Parser** (`src/parser/`) - Builds AST with natural language support +3. **Analyzer** (`src/analyzer/`) - Semantic analysis and validation +4. **Type Checker** (`src/typechecker/`) - Static type analysis +5. **Interpreter** (`src/interpreter/`) - Executes AST with Tokio async runtime + +### Key Design Patterns + +- **Error Handling**: Comprehensive error types with codespan-reporting for user-friendly messages +- **Async Operations**: Full Tokio integration for concurrent operations +- **Standard Library**: Modular design in `src/stdlib/` with core, math, text, list, time, and pattern modules +- **Configuration**: Hierarchical config system (global → local) in `src/config.rs` and `src/wfl_config/` +- **Logging**: Dual logging system - standard logger and execution tracer using `exec_trace!` macro + +### Container System +WFL uses "containers" (similar to classes) with: +- Properties and actions (methods) +- Inheritance support +- Interface implementation +- Event handling +- Found in `src/parser/container_*.rs` + +### Natural Language Parsing +The parser supports English-like syntax: +- "store X as Y" for variable assignment +- "check if X is greater than Y" for conditionals +- "count from X to Y" for loops +- Function calls like "length of mylist" + ## Standard Debug Procedure When debugging ANY issue: @@ -71,18 +155,21 @@ When debugging ANY issue: 4. Run static analyzer: `cargo run -- --analyze test.wfl` 5. Fix issues and verify ALL existing tests still pass -## Documentation Requirements +## AI Development Rules -Before making changes: -1. Read `Docs/wfl-spec.md` for language specification -2. Check module-specific docs in `Docs/` -3. Review recent Dev Diary entries -4. Consult memory bank files in `.kilocode/rules/memory-bank/` +When working on this codebase: -After making changes: -1. Update relevant documentation -2. Create Dev Diary entry with implementation details -3. Add/update tests in appropriate locations +1. **Never break existing functionality** - All changes must maintain backward compatibility +2. **Follow the 6-step debug procedure** for any issues: + - Understand the issue + - Review code and logs + - Form hypothesis + - Make targeted change + - Test thoroughly + - Document in Dev diary +3. **Test all changes** - Run the full test suite before considering work complete +4. **Update Dev diary** - Create entries in `Dev diary/` for significant changes +5. **Maintain clean separation** - Debug output uses `exec_trace!`, never pollutes program output ## Testing Requirements @@ -96,61 +183,39 @@ After making changes: Run specific test: `cargo test test_name` Run module tests: `cargo test --package wfl --lib module_name` -## Development Workflow - -1. **Understand the task**: Read all relevant documentation -2. **Check existing code**: Search for similar patterns -3. **Write tests first**: Add to TestPrograms/ or unit tests -4. **Implement feature**: Follow existing code style -5. **Run all tests**: `cargo test` and TestPrograms/ -6. **Check quality**: `cargo fmt` and `cargo clippy` -7. **Update docs**: Modify relevant .md files -8. **Create Dev Diary**: Document your implementation - -## CLI Flag Reference +## Documentation Requirements -| Flag | Description | Example | -|------|-------------|---------| -| `--lex` | Output lexer tokens only | `cargo run -- --lex program.wfl` | -| `--parse` | Output AST only | `cargo run -- --parse program.wfl` | -| `--lint` | Check code style | `cargo run -- --lint program.wfl` | -| `--analyze` | Static analysis | `cargo run -- --analyze program.wfl` | -| `--fix` | Auto-format code | `cargo run -- --fix program.wfl` | -| `--in-place` | Modify file directly | `cargo run -- --fix program.wfl --in-place` | -| `--check` | Dry run for --fix | `cargo run -- --fix program.wfl --check` | -| `--debug` | Enable debug output | `cargo run -- --debug program.wfl` | -| `--config` | Specify config file | `cargo run -- --config custom.wflcfg program.wfl` | -| `--time` | Show execution time | `cargo run -- --time program.wfl` | -| `-v, --version` | Show version info | `cargo run -- --version` | +Before making changes: +1. Read `Docs/wfl-spec.md` for language specification +2. Check module-specific docs in `Docs/` +3. Review recent Dev Diary entries +4. Consult memory bank files in `.kilocode/rules/memory-bank/` -## Architecture Overview +After making changes: +1. Update relevant documentation +2. Create Dev Diary entry with implementation details +3. Add/update tests in appropriate locations -``` -Input (.wfl) → Lexer → Parser → Analyzer → Type Checker → Interpreter → Output - ↓ ↓ ↓ ↓ ↓ - Tokens AST Validated Type Info Execution - AST Results -``` +## Critical Implementation Notes -Key components: -- **Lexer**: Token generation with Logos -- **Parser**: Recursive descent, indentation-aware -- **Analyzer**: Semantic validation, dead code detection -- **Type Checker**: Static type analysis -- **Interpreter**: Direct AST execution with async support -- **Stdlib**: core, math, text, list, time, pattern modules +### Parser Stability +- The parser has comprehensive end token handling to prevent infinite loops +- Always consume orphaned tokens during error recovery +- Use `peek_token()` for lookahead, never `next_token()` unless consuming -## Key Implementation Notes +### Memory Management +- Optional dhat heap profiling with `--features dhat-heap` +- Careful lifetime management in parser to avoid borrow checker issues +- Async operations properly handle cleanup +- Variables stored in Environment HashMap +- Scope management with push/pop +- Automatic cleanup on scope exit -### Error Handling +### Error Reporting +- All errors use the unified diagnostic system +- Include source context with precise spans +- Provide actionable suggestions when possible - Use `InterpreterError` for runtime errors -- Include source location via spans -- Provide helpful error messages with context - -### Async Operations -- All I/O operations are async (web.get, file operations) -- Use `await` keyword in WFL code -- Tokio runtime handles execution ### Type System - Static typing with inference @@ -158,10 +223,47 @@ Key components: - Function types for callbacks - Pattern matching with regex support -### Memory Management -- Variables stored in Environment HashMap -- Scope management with push/pop -- Automatic cleanup on scope exit +### Async Operations +- All I/O operations are async (web.get, file operations) +- Use `await` keyword in WFL code +- Tokio runtime handles execution + +## Common Workflows + +### Adding a New Feature +1. Update the lexer if new tokens needed +2. Extend the parser AST and parsing logic +3. Add semantic analysis rules +4. Implement type checking rules +5. Add interpreter execution logic +6. Write comprehensive tests +7. Update documentation + +### Development Workflow +1. **Understand the task**: Read all relevant documentation +2. **Check existing code**: Search for similar patterns +3. **Write tests first**: Add to TestPrograms/ or unit tests +4. **Implement feature**: Follow existing code style +5. **Run all tests**: `cargo test` and TestPrograms/ +6. **Check quality**: `cargo fmt` and `cargo clippy` +7. **Update docs**: Modify relevant .md files +8. **Create Dev Diary**: Document your implementation + +### Updating Standard Library +1. Add function to appropriate module in `src/stdlib/` +2. Register in module's `register_functions()` +3. Add type signatures and validation +4. Write tests in the module's test section +5. Document in function catalog + +## Key Files to Understand + +- `src/parser/mod.rs` - Core parser logic and natural language handling +- `src/interpreter/mod.rs` - Execution engine with async support +- `src/stdlib/mod.rs` - Standard library registration +- `src/diagnostics/mod.rs` - Error reporting system +- `src/main.rs` - CLI entry point and command handling +- `.kilocode/rules/` - Additional AI assistant context and rules ## Current Focus Areas (June 2025) @@ -171,4 +273,4 @@ Key components: 4. **Documentation**: Keeping all docs up-to-date 5. **Stability**: Ensuring backward compatibility -Remember: The goal is to make programming accessible while maintaining professional-grade tooling and performance. \ No newline at end of file +Remember: This is alpha software under active development. Always prioritize stability and backward compatibility while implementing new features. The goal is to make programming accessible while maintaining professional-grade tooling and performance. diff --git a/Docs/wfl-spec.md b/Docs/wfl-spec.md index e7344d8b..e8ffb865 100644 --- a/Docs/wfl-spec.md +++ b/Docs/wfl-spec.md @@ -354,6 +354,109 @@ In this snippet, `req1` and `req2` act like promises or tasks for the data fetch To summarize, WFL’s async support allows you to write asynchronous code that looks very similar to synchronous logic. The explicit `async` in definitions and `await` in usage make the timing clear without introducing complex syntax. This aligns with WFL’s goal of making advanced features approachable. +### Containers and Object-Oriented Programming +WFL supports object-oriented programming through **containers**, which are similar to classes in other languages. Containers allow you to define reusable templates for objects that encapsulate data (properties) and behavior (methods). The syntax follows WFL's natural language philosophy, making object-oriented concepts accessible through English-like constructs. + +**Container Definition:** The syntax to define a container is: + +```ebnf +ContainerDefinition ::= "create" "container" Identifier [ "extends" Identifier ] + [ "implements" IdentifierList ] ":" + PropertyDefinition* MethodDefinition* EventDefinition* + "end" +PropertyDefinition ::= "property" Identifier ":" Type [ "default" Expression ] +MethodDefinition ::= "action" Identifier [ "needs" ParameterList ] [ ":" Type ] ":" + StatementList "end" +EventDefinition ::= "event" Identifier [ "needs" ParameterList ] +``` + +For example: +```wfl +create container Person: + property name: Text + property age: Number default 0 + + action greet: + display "Hello, I am " + this.name + " and I am " + this.age + "." + end + + action set age needs new age: Number: + change this.age to new age + end +end +``` + +This defines a `Person` container with two properties (`name` and `age`) and two methods (`greet` and `set age`). The `default` keyword allows specifying initial values for properties. Inside methods, `this` refers to the current instance of the container. + +**Container Inheritance:** Containers can extend other containers to inherit their properties and methods: + +```wfl +create container Student extends Person: + property school: Text + + action greet: + display "Hello, I am " + this.name + ", a student at " + this.school + "." + end +end +``` + +The `Student` container inherits `name` and `age` from `Person` but overrides the `greet` method with its own implementation. + +**Interface Implementation:** Containers can implement interfaces to ensure they provide specific methods: + +```wfl +create interface Drawable: + action draw: +end + +create container Circle implements Drawable: + property radius: Number + + action draw: + display "Drawing a circle with radius " + this.radius + end +end +``` + +**Container Instantiation:** To create instances of containers, use the `create new` syntax: + +```ebnf +ContainerInstantiation ::= "create" "new" Identifier "as" Identifier ":" + PropertyInitializer* "end" +PropertyInitializer ::= Identifier "=" Expression +``` + +For example: +```wfl +create new Person as alice: + name = "Alice" + age = 28 +end + +alice.greet() +display alice.name +``` + +This creates a new `Person` instance named `alice`, initializes its properties, then calls its `greet` method and accesses its `name` property. + +**Static Members:** Containers can have static properties and methods that belong to the container itself rather than instances: + +```wfl +create container Math: + static property PI: Number = 3.14159 + + static action square needs value: Number: Number + return value * value + end +end + +display Math.PI +store result as Math.square(5) +``` + +Static members are accessed using the container name directly, without creating an instance. + + ### Input/Output (File, Network, Database) WFL treats input/output operations (file reading/writing, HTTP requests, database queries, etc.) as high-level actions described in English. All I/O shares a unified style: you **open** a resource, perform reads/writes, and **close** it, with similar syntax for files, web URLs, and databases ([wfl-IO.md](file://file-XU2WRnQ9nsyxEU1hEuxVJX#:~:text=WebFirst%20Language%20,like%20way.%20Key%20goals%20include)) ([wfl-IO.md](file://file-XU2WRnQ9nsyxEU1hEuxVJX#:~:text=,across%20files%2C%20network%2C%20and%20databases)). This consistency means once you learn how to do one kind of I/O, the others feel familiar. @@ -732,5 +835,5 @@ The WebFirst Language brings together the above syntax and semantic rules to cre The semantics ensure that programs behave reliably: the strong type system catches mistakes early (with helpful messages), scoping rules prevent unintended interactions, and automatic memory management lets developers build complex web applications without worrying about low-level errors. WFL’s design is informed by the needs of modern web development (with first-class support for async operations and integration with web APIs) while keeping the syntax accessible to someone who might be writing their first lines of code. -By following this specification, implementers of WFL can create compilers or interpreters that uphold these syntax rules and semantics, and developers can write WFL code with confidence that it will do what it intuitively says. The end result is a language specification that reads almost like a tutorial – just as WFL code reads like plain English – fulfilling the language’s mission of making web programming more intuitive, inclusive, and robust. +By following this specification, implementers of WFL can create compilers or interpreters that uphold these syntax rules and semantics, and developers can write WFL code with confidence that it will do what it intuitively says. The end result is a language specification that reads almost like a tutorial – just as WFL code reads like plain English – fulfilling the language’s mission of making web programming more intuitive, inclusive, and robust. diff --git a/TestPrograms/container_events_test.wfl b/TestPrograms/container_events_test.wfl new file mode 100644 index 00000000..1dde70bb --- /dev/null +++ b/TestPrograms/container_events_test.wfl @@ -0,0 +1,80 @@ +// Container with events and static members +create container Button: + // Static properties + static property button_count as 0 + + // Instance properties + property label as text + property is_enabled as yes + + // Events + event clicked + event hover_start + event hover_end + + // Constructor + define action initialize with button_label: + set label to button_label + add 1 to Button button_count + display "Button created: " with label + display "Total buttons: " with Button button_count + end action + + // Methods + define action click: + if is_enabled: + trigger clicked + display "Button '" with label with "' was clicked" + else: + display "Button '" with label with "' is disabled" + end if + end action + + define action on_hover: + trigger hover_start + display "Hovering over button: " with label + end action + + define action end_hover: + trigger hover_end + display "No longer hovering over button: " with label + end action + + define action disable: + set is_enabled to no + display "Button '" with label with "' disabled" + end action + + define action enable: + set is_enabled to yes + display "Button '" with label with "' enabled" + end action +end container + +// Create button instances +create new Button with "Submit" as submit_button +create new Button with "Cancel" as cancel_button + +// Set up event handlers +on submit_button clicked: + display "Form submitted!" +end on + +on cancel_button clicked: + display "Form cancelled!" +end on + +// Interact with buttons +submit_button on_hover +submit_button click +submit_button end_hover + +cancel_button on_hover +cancel_button disable +cancel_button click // This won't trigger the event because the button is disabled +cancel_button enable +cancel_button click // Now it will trigger the event +cancel_button end_hover + +// Access static property +display "Total number of buttons created: " with Button button_count \ No newline at end of file diff --git a/TestPrograms/container_inheritance_simple_test.wfl b/TestPrograms/container_inheritance_simple_test.wfl new file mode 100644 index 00000000..2c035800 --- /dev/null +++ b/TestPrograms/container_inheritance_simple_test.wfl @@ -0,0 +1,54 @@ +// Container inheritance test + +// Base container +create container Vehicle: + property make as text + property model as text + property year as number + + define action initialize with vehicle_make and vehicle_model and vehicle_year: + set make to vehicle_make + set model to vehicle_model + set year to vehicle_year + display "Created vehicle: " with year with " " with make with " " with model + end action + + define action describe: + display year with " " with make with " " with model + end action +end container + +// Child container +create container Car extends Vehicle: + property number_of_doors as number defaults to 4 + property fuel_type as text defaults to "gasoline" + + // Override the parent's describe action + define action describe: + // Call the parent's version first + parent describe + display "This car has " with number_of_doors with " doors and runs on " with fuel_type + end action + + define action honk: + display "Beep beep!" + end action +end container + +// Create instances +create new Vehicle with "Toyota" and "Corolla" and 2023 as generic_vehicle +create new Car with "Honda" and "Civic" and 2024 as my_car + +// Set properties on the car +set my_car's fuel_type to "hybrid" + +// Call methods +display "Vehicle description:" +generic_vehicle describe + +display "Car description:" +my_car describe +my_car honk + +// Test complete +display "Container inheritance test complete" \ No newline at end of file diff --git a/TestPrograms/container_inheritance_test.wfl b/TestPrograms/container_inheritance_test.wfl new file mode 100644 index 00000000..c1182144 --- /dev/null +++ b/TestPrograms/container_inheritance_test.wfl @@ -0,0 +1,47 @@ +// Interface definition +create interface Drawable: + requires action draw + requires action resize with width and height +end interface + +// Base container +create container Shape: + property color as text + + define action describe: + display "This is a " with color with " shape" + end action +end container + +// Container with inheritance and interface implementation +create container Circle extends Shape implements Drawable: + property radius as number + + // Override parent method + define action describe: + parent describe + display "It's a circle with radius " with radius + end action + + // Implement interface methods + define action draw: + display "Drawing a " with color with " circle with radius " with radius + end action + + define action resize with width and height: + set radius to minimum of width and height divided by 2 + display "Circle resized to radius " with radius + end action +end container + +// Container instantiation +create new Circle as my_circle: + set color to "red" + set radius to 5 +end create + +// Using container methods +my_circle describe +my_circle draw +my_circle resize with 10 and 8 +my_circle draw \ No newline at end of file diff --git a/TestPrograms/container_interface_test.wfl b/TestPrograms/container_interface_test.wfl new file mode 100644 index 00000000..b57f11c0 --- /dev/null +++ b/TestPrograms/container_interface_test.wfl @@ -0,0 +1,71 @@ +// Container interface test + +// Define an interface +create interface Drawable: + requires action draw + requires action resize with width and height +end interface + +// Implement the interface in a container +create container Circle implements Drawable: + property radius as number + property color as text + + define action initialize with circle_radius and circle_color: + set radius to circle_radius + set color to circle_color + display "Created a " with color with " circle with radius " with radius + end action + + define action draw: + display "Drawing a " with color with " circle with radius " with radius + end action + + define action resize with width and height: + set radius to minimum of width and height divided by 2 + display "Circle resized to radius " with radius + end action +end container + +// Another container implementing the same interface +create container Rectangle implements Drawable: + property width as number + property height as number + property color as text + + define action initialize with rect_width and rect_height and rect_color: + set width to rect_width + set height to rect_height + set color to rect_color + display "Created a " with color with " rectangle " with width with "x" with height + end action + + define action draw: + display "Drawing a " with color with " rectangle " with width with "x" with height + end action + + define action resize with new_width and new_height: + set width to new_width + set height to new_height + display "Rectangle resized to " with width with "x" with height + end action +end container + +// Create instances +create new Circle with 5 and "red" as circle +create new Rectangle with 10 and 20 and "blue" as rectangle + +// Call methods +circle draw +rectangle draw + +// Resize both shapes +circle resize with 30 and 40 +rectangle resize with 50 and 60 + +// Draw again to see the changes +circle draw +rectangle draw + +// Test complete +display "Container interface test complete" \ No newline at end of file diff --git a/TestPrograms/container_simple_test.wfl b/TestPrograms/container_simple_test.wfl new file mode 100644 index 00000000..28b6eb74 --- /dev/null +++ b/TestPrograms/container_simple_test.wfl @@ -0,0 +1,18 @@ +// Basic container definition +create container Person: + property name: Text + property age: Number + + action greet: + display "Hello, I am Alice and I am 28." + end +end + +// Container instantiation +create new Person as alice: + name is "Alice" + age is 28 +end + +// Using container methods +alice.greet() diff --git a/TestPrograms/container_simple_test_debug.txt b/TestPrograms/container_simple_test_debug.txt new file mode 100644 index 00000000..50d8526d --- /dev/null +++ b/TestPrograms/container_simple_test_debug.txt @@ -0,0 +1,17 @@ +=== WFL Debug Report === +Script: Test Programs/container_simple_test.wfl +Time: 2025-06-03 06:55:33 + +=== Error Summary === +Runtime error at line 18, column 1: Undefined variable 'alice greet' + +=== Stack Trace === +In main script at line 18, column 1 + +=== Source Code === + 16: + 17: // Using container methods +>> 18: alice greet + +=== Local Variables === +(No local variables in global scope) diff --git a/TestPrograms/container_test.wfl b/TestPrograms/container_test.wfl new file mode 100644 index 00000000..b77df00e --- /dev/null +++ b/TestPrograms/container_test.wfl @@ -0,0 +1,26 @@ +// Basic container definition +create container Person: + // Properties + property name as text + property age as number + + // Methods + define action greet: + display "Hello, my name is " with name + end action + + define action has birthday: + add 1 to age + display name with " is now " with age with " years old" + end action +end container + +// Container instantiation +create new Person as alice: + set name to "Alice Smith" + set age to 28 +end create + +// Using container methods +alice greet +alice has birthday \ No newline at end of file diff --git a/Tools/clippy errors/clippy1.txt b/Tools/clippy errors/clippy1.txt new file mode 100644 index 00000000..3cbaac46 --- /dev/null +++ b/Tools/clippy errors/clippy1.txt @@ -0,0 +1,3751 @@ +error: variables can be used directly in the `format!` string + --> src/analyzer/mod.rs:291:37 + | +291 | ... format!("Cannot assign to immutable variable '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + = note: `-D clippy::uninlined-format-args` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::uninlined_format_args)]` +help: change this to + | +291 - format!("Cannot assign to immutable variable '{}'", name), +291 + format!("Cannot assign to immutable variable '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/analyzer/mod.rs:299:33 + | +299 | ... format!("'{}' is not a variable", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +299 - format!("'{}' is not a variable", name), +299 + format!("'{name}' is not a variable"), + | + +error: variables can be used directly in the `format!` string + --> src/analyzer/mod.rs:307:25 + | +307 | format!("Variable '{}' is not defined", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +307 - format!("Variable '{}' is not defined", name), +307 + format!("Variable '{name}' is not defined"), + | + +error: variables can be used directly in the `format!` string + --> src/analyzer/mod.rs:777:23 + | +777 | name: format!("param{}", i), + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +777 - name: format!("param{}", i), +777 + name: format!("param{i}"), + | + +error: variables can be used directly in the `format!` string + --> src/analyzer/mod.rs:836:25 + | +836 | format!("Variable '{}' is not defined", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +836 - format!("Variable '{}' is not defined", name), +836 + format!("Variable '{name}' is not defined"), + | + +error: variables can be used directly in the `format!` string + --> src/analyzer/mod.rs:869:37 + | +869 | ... format!("'{}' is not a function", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +869 - format!("'{}' is not a function", name), +869 + format!("'{name}' is not a function"), + | + +error: variables can be used directly in the `format!` string + --> src/analyzer/static_analyzer.rs:255:21 + | +255 | format!("Unused variable '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +255 - format!("Unused variable '{}'", name), +255 + format!("Unused variable '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/analyzer/static_analyzer.rs:350:33 + | +350 | ... format!("Action '{}' has inconsistent return paths", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +350 - format!("Action '{}' has inconsistent return paths", name), +350 + format!("Action '{name}' has inconsistent return paths"), + | + +error: variables can be used directly in the `format!` string + --> src/analyzer/static_analyzer.rs:1083:33 + | +1083 | / ... format!( +1084 | | ... "Variable '{}' shadows another variable with the same name", +1085 | | ... name +1086 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/analyzer/static_analyzer.rs:1087:38 + | +1087 | ... Some(format!( + | ____________________________^ +1088 | | ... "Previously defined at line {}, column {}", +1089 | | ... def_line, def_col +1090 | | ... )), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/analyzer/static_analyzer.rs:1104:29 + | +1104 | / ... format!( +1105 | | ... "Variable '{}' shadows another variable with the same name", +1106 | | ... name +1107 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/analyzer/static_analyzer.rs:1108:34 + | +1108 | ... Some(format!( + | ____________________________^ +1109 | | ... "Previously defined at line {}, column {}", +1110 | | ... def_line, def_col +1111 | | ... )), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/config.rs:124:13 + | +124 | log::debug!("Found config key: {}, value: {}", key, value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +124 - log::debug!("Found config key: {}, value: {}", key, value); +124 + log::debug!("Found config key: {key}, value: {value}"); + | + +error: variables can be used directly in the `format!` string + --> src/debug_report.rs:78:21 + | +78 | write!(f, "{}: ", k)?; + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +78 - write!(f, "{}: ", k)?; +78 + write!(f, "{k}: ")?; + | + +error: variables can be used directly in the `format!` string + --> src/debug_report.rs:154:5 + | +154 | writeln!(&mut report, "Script: {}", script_path).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +154 - writeln!(&mut report, "Script: {}", script_path).unwrap(); +154 + writeln!(&mut report, "Script: {script_path}").unwrap(); + | + +error: variables can be used directly in the `format!` string + --> src/debug_report.rs:248:27 + | +248 | if line.contains(&format!("define action called {}", func_name)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +248 - if line.contains(&format!("define action called {}", func_name)) +248 + if line.contains(&format!("define action called {func_name}")) + | + +error: variables can be used directly in the `format!` string + --> src/debug_report.rs:249:31 + | +249 | || line.contains(&format!("action called {}", func_name)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +249 - || line.contains(&format!("action called {}", func_name)) +249 + || line.contains(&format!("action called {func_name}")) + | + +error: variables can be used directly in the `format!` string + --> src/diagnostics/mod.rs:294:28 + | +294 | message_text = format!( + | ____________________________^ +295 | | "{} - Expected {} but found {}", +296 | | message_text, expected, found +297 | | ); + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/diagnostics/mod.rs:550:23 + | +550 | message = format!("Pattern '{}': {}", name, message); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +550 - message = format!("Pattern '{}': {}", name, message); +550 + message = format!("Pattern '{name}': {message}"); + | + +error: variables can be used directly in the `format!` string + --> src/diagnostics/mod.rs:559:23 + | +559 | message = format!("{} (input: \"{}\")", message, preview); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +559 - message = format!("{} (input: \"{}\")", message, preview); +559 + message = format!("{message} (input: \"{preview}\")"); + | + +error: variables can be used directly in the `format!` string + --> src/fixer/mod.rs:79:21 + | +79 | format!("Failed to parse file: {:?}", err), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +79 - format!("Failed to parse file: {:?}", err), +79 + format!("Failed to parse file: {err:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/fixer/mod.rs:246:46 + | +246 | ... output.push_str(&format!("{:?}", param_type)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +246 - output.push_str(&format!("{:?}", param_type)); +246 + output.push_str(&format!("{param_type:?}")); + | + +error: variables can be used directly in the `format!` string + --> src/fixer/mod.rs:263:38 + | +263 | output.push_str(&format!("{:?}", ret_type)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +263 - output.push_str(&format!("{:?}", ret_type)); +263 + output.push_str(&format!("{ret_type:?}")); + | + +error: variables can be used directly in the `format!` string + --> src/fixer/mod.rs:472:34 + | +472 | output.push_str(&format!("{:?}\n", statement)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +472 - output.push_str(&format!("{:?}\n", statement)); +472 + output.push_str(&format!("{statement:?}\n")); + | + +error: variables can be used directly in the `format!` string + --> src/fixer/mod.rs:647:34 + | +647 | output.push_str(&format!("{:?}", expression)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +647 - output.push_str(&format!("{:?}", expression)); +647 + output.push_str(&format!("{expression:?}")); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/environment.rs:59:17 + | +59 | Err(format!("Undefined variable '{}'", name)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +59 - Err(format!("Undefined variable '{}'", name)) +59 + Err(format!("Undefined variable '{name}'")) + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:207:33 + | +207 | Value::Number(n) => write!(f, "{}", n), + | ^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +207 - Value::Number(n) => write!(f, "{}", n), +207 + Value::Number(n) => write!(f, "{n}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:208:31 + | +208 | Value::Text(s) => write!(f, "\"{}\"", s), + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +208 - Value::Text(s) => write!(f, "\"{}\"", s), +208 + Value::Text(s) => write!(f, "\"{s}\""), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:209:31 + | +209 | Value::Bool(b) => write!(f, "{}", b), + | ^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +209 - Value::Bool(b) => write!(f, "{}", b), +209 + Value::Bool(b) => write!(f, "{b}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:218:21 + | +218 | write!(f, "{:?}", v)?; + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +218 - write!(f, "{:?}", v)?; +218 + write!(f, "{v:?}")?; + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:229:21 + | +229 | write!(f, "{}: {:?}", k, v)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +229 - write!(f, "{}: {:?}", k, v)?; +229 + write!(f, "{k}: {v:?}")?; + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:240:47 + | +240 | Value::NativeFunction(name, _) => write!(f, "NativeFunction({})", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +240 - Value::NativeFunction(name, _) => write!(f, "NativeFunction({})", name), +240 + Value::NativeFunction(name, _) => write!(f, "NativeFunction({name})"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:242:31 + | +242 | Value::Date(d) => write!(f, "Date({})", d), + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +242 - Value::Date(d) => write!(f, "Date({})", d), +242 + Value::Date(d) => write!(f, "Date({d})"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:243:31 + | +243 | Value::Time(t) => write!(f, "Time({})", t), + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +243 - Value::Time(t) => write!(f, "Time({})", t), +243 + Value::Time(t) => write!(f, "Time({t})"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:244:36 + | +244 | Value::DateTime(dt) => write!(f, "DateTime({})", dt), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +244 - Value::DateTime(dt) => write!(f, "DateTime({})", dt), +244 + Value::DateTime(dt) => write!(f, "DateTime({dt})"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:262:33 + | +262 | Value::Number(n) => write!(f, "{}", n), + | ^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +262 - Value::Number(n) => write!(f, "{}", n), +262 + Value::Number(n) => write!(f, "{n}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:263:31 + | +263 | Value::Text(s) => write!(f, "{}", s), + | ^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +263 - Value::Text(s) => write!(f, "{}", s), +263 + Value::Text(s) => write!(f, "{s}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:271:25 + | +271 | write!(f, "{}", value) + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +271 - write!(f, "{}", value) +271 + write!(f, "{value}") + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:283:25 + | +283 | write!(f, "{}: {}", k, v)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +283 - write!(f, "{}: {}", k, v)?; +283 + write!(f, "{k}: {v}")?; + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/value.rs:295:47 + | +295 | Value::NativeFunction(name, _) => write!(f, "native {}", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +295 - Value::NativeFunction(name, _) => write!(f, "native {}", name), +295 + Value::NativeFunction(name, _) => write!(f, "native {name}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:50:56 + | +50 | Statement::VariableDeclaration { name, .. } => format!("VariableDeclaration '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +50 - Statement::VariableDeclaration { name, .. } => format!("VariableDeclaration '{}'", name), +50 + Statement::VariableDeclaration { name, .. } => format!("VariableDeclaration '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:51:47 + | +51 | Statement::Assignment { name, .. } => format!("Assignment to '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +51 - Statement::Assignment { name, .. } => format!("Assignment to '{}'", name), +51 + Statement::Assignment { name, .. } => format!("Assignment to '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:55:53 + | +55 | Statement::ActionDefinition { name, .. } => format!("ActionDefinition '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +55 - Statement::ActionDefinition { name, .. } => format!("ActionDefinition '{}'", name), +55 + Statement::ActionDefinition { name, .. } => format!("ActionDefinition '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:59:53 + | +59 | Statement::ForEachLoop { item_name, .. } => format!("ForEachLoop '{}'", item_name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +59 - Statement::ForEachLoop { item_name, .. } => format!("ForEachLoop '{}'", item_name), +59 + Statement::ForEachLoop { item_name, .. } => format!("ForEachLoop '{item_name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:68:13 + | +68 | format!("OpenFileStatement '{}'", variable_name) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +68 - format!("OpenFileStatement '{}'", variable_name) +68 + format!("OpenFileStatement '{variable_name}'") + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:71:13 + | +71 | format!("ReadFileStatement '{}'", variable_name) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +71 - format!("ReadFileStatement '{}'", variable_name) +71 + format!("ReadFileStatement '{variable_name}'") + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:76:55 + | +76 | Statement::TryStatement { error_name, .. } => format!("TryStatement '{}'", error_name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +76 - Statement::TryStatement { error_name, .. } => format!("TryStatement '{}'", error_name), +76 + Statement::TryStatement { error_name, .. } => format!("TryStatement '{error_name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:78:13 + | +78 | format!("HttpGetStatement '{}'", variable_name) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +78 - format!("HttpGetStatement '{}'", variable_name) +78 + format!("HttpGetStatement '{variable_name}'") + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:81:13 + | +81 | format!("HttpPostStatement '{}'", variable_name) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +81 - format!("HttpPostStatement '{}'", variable_name) +81 + format!("HttpPostStatement '{variable_name}'") + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:85:56 + | +85 | Statement::ContainerDefinition { name, .. } => format!("ContainerDefinition '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +85 - Statement::ContainerDefinition { name, .. } => format!("ContainerDefinition '{}'", name), +85 + Statement::ContainerDefinition { name, .. } => format!("ContainerDefinition '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:90:14 + | +90 | } => format!( + | ______________^ +91 | | "ContainerInstantiation '{}' as '{}'", +92 | | container_type, instance_name +93 | | ), + | |_________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:94:56 + | +94 | Statement::InterfaceDefinition { name, .. } => format!("InterfaceDefinition '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +94 - Statement::InterfaceDefinition { name, .. } => format!("InterfaceDefinition '{}'", name), +94 + Statement::InterfaceDefinition { name, .. } => format!("InterfaceDefinition '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:95:52 + | +95 | Statement::EventDefinition { name, .. } => format!("EventDefinition '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +95 - Statement::EventDefinition { name, .. } => format!("EventDefinition '{}'", name), +95 + Statement::EventDefinition { name, .. } => format!("EventDefinition '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:96:49 + | +96 | Statement::EventTrigger { name, .. } => format!("EventTrigger '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +96 - Statement::EventTrigger { name, .. } => format!("EventTrigger '{}'", name), +96 + Statement::EventTrigger { name, .. } => format!("EventTrigger '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:97:55 + | +97 | Statement::EventHandler { event_name, .. } => format!("EventHandler '{}'", event_name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +97 - Statement::EventHandler { event_name, .. } => format!("EventHandler '{}'", event_name), +97 + Statement::EventHandler { event_name, .. } => format!("EventHandler '{event_name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:99:13 + | +99 | format!("ParentMethodCall '{}'", method_name) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +99 - format!("ParentMethodCall '{}'", method_name) +99 + format!("ParentMethodCall '{method_name}'") + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:108:35 + | +108 | Literal::String(s) => format!("StringLiteral \"{}\"", s), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +108 - Literal::String(s) => format!("StringLiteral \"{}\"", s), +108 + Literal::String(s) => format!("StringLiteral \"{s}\""), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:109:36 + | +109 | Literal::Integer(i) => format!("IntegerLiteral {}", i), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +109 - Literal::Integer(i) => format!("IntegerLiteral {}", i), +109 + Literal::Integer(i) => format!("IntegerLiteral {i}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:110:34 + | +110 | Literal::Float(f) => format!("FloatLiteral {}", f), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +110 - Literal::Float(f) => format!("FloatLiteral {}", f), +110 + Literal::Float(f) => format!("FloatLiteral {f}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:111:36 + | +111 | Literal::Boolean(b) => format!("BooleanLiteral {}", b), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +111 - Literal::Boolean(b) => format!("BooleanLiteral {}", b), +111 + Literal::Boolean(b) => format!("BooleanLiteral {b}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:113:36 + | +113 | Literal::Pattern(p) => format!("PatternLiteral \"{}\"", p), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +113 - Literal::Pattern(p) => format!("PatternLiteral \"{}\"", p), +113 + Literal::Pattern(p) => format!("PatternLiteral \"{p}\""), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:116:43 + | +116 | Expression::Variable(name, ..) => format!("Variable '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +116 - Expression::Variable(name, ..) => format!("Variable '{}'", name), +116 + Expression::Variable(name, ..) => format!("Variable '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:117:57 + | +117 | Expression::BinaryOperation { operator, .. } => format!("BinaryOperation '{:?}'", operator), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +117 - Expression::BinaryOperation { operator, .. } => format!("BinaryOperation '{:?}'", operator), +117 + Expression::BinaryOperation { operator, .. } => format!("BinaryOperation '{operator:?}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:118:56 + | +118 | Expression::UnaryOperation { operator, .. } => format!("UnaryOperation '{:?}'", operator), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +118 - Expression::UnaryOperation { operator, .. } => format!("UnaryOperation '{:?}'", operator), +118 + Expression::UnaryOperation { operator, .. } => format!("UnaryOperation '{operator:?}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:120:47 + | +120 | Expression::Variable(name, ..) => format!("FunctionCall '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +120 - Expression::Variable(name, ..) => format!("FunctionCall '{}'", name), +120 + Expression::Variable(name, ..) => format!("FunctionCall '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:123:48 + | +123 | Expression::ActionCall { name, .. } => format!("ActionCall '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +123 - Expression::ActionCall { name, .. } => format!("ActionCall '{}'", name), +123 + Expression::ActionCall { name, .. } => format!("ActionCall '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:124:54 + | +124 | Expression::MemberAccess { property, .. } => format!("MemberAccess '{}'", property), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +124 - Expression::MemberAccess { property, .. } => format!("MemberAccess '{}'", property), +124 + Expression::MemberAccess { property, .. } => format!("MemberAccess '{property}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:135:14 + | +135 | } => format!("StaticMemberAccess '{}' member '{}'", container, member), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +135 - } => format!("StaticMemberAccess '{}' member '{}'", container, member), +135 + } => format!("StaticMemberAccess '{container}' member '{member}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:136:50 + | +136 | Expression::MethodCall { method, .. } => format!("MethodCall '{}'", method), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +136 - Expression::MethodCall { method, .. } => format!("MethodCall '{}'", method), +136 + Expression::MethodCall { method, .. } => format!("MethodCall '{method}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:137:56 + | +137 | Expression::PropertyAccess { property, .. } => format!("PropertyAccess '{}'", property), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +137 - Expression::PropertyAccess { property, .. } => format!("PropertyAccess '{}'", property), +137 + Expression::PropertyAccess { property, .. } => format!("PropertyAccess '{property}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:182:31 + | +182 | Err(e) => Err(format!("Failed to read response body: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +182 - Err(e) => Err(format!("Failed to read response body: {}", e)), +182 + Err(e) => Err(format!("Failed to read response body: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:184:27 + | +184 | Err(e) => Err(format!("Failed to send HTTP GET request: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +184 - Err(e) => Err(format!("Failed to send HTTP GET request: {}", e)), +184 + Err(e) => Err(format!("Failed to send HTTP GET request: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:199:31 + | +199 | Err(e) => Err(format!("Failed to read response body: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +199 - Err(e) => Err(format!("Failed to read response body: {}", e)), +199 + Err(e) => Err(format!("Failed to read response body: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:201:27 + | +201 | Err(e) => Err(format!("Failed to send HTTP POST request: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +201 - Err(e) => Err(format!("Failed to send HTTP POST request: {}", e)), +201 + Err(e) => Err(format!("Failed to send HTTP POST request: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:231:27 + | +231 | Err(e) => Err(format!("Failed to open file {}: {}", path, e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +231 - Err(e) => Err(format!("Failed to open file {}: {}", path, e)), +231 + Err(e) => Err(format!("Failed to open file {path}: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:250:38 + | +250 | Err(e) => return Err(format!("Invalid file handle or path: {}: {}", handle_id, e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +250 - Err(e) => return Err(format!("Invalid file handle or path: {}: {}", handle_id, e)), +250 + Err(e) => return Err(format!("Invalid file handle or path: {handle_id}: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:256:34 + | +256 | Err(e) => return Err(format!("Failed to clone file handle: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +256 - Err(e) => return Err(format!("Failed to clone file handle: {}", e)), +256 + Err(e) => return Err(format!("Failed to clone file handle: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:264:27 + | +264 | Err(e) => Err(format!("Failed to read file: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +264 - Err(e) => Err(format!("Failed to read file: {}", e)), +264 + Err(e) => Err(format!("Failed to read file: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:283:38 + | +283 | Err(e) => return Err(format!("Invalid file handle or path: {}: {}", handle_id, e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +283 - Err(e) => return Err(format!("Invalid file handle or path: {}: {}", handle_id, e)), +283 + Err(e) => return Err(format!("Invalid file handle or path: {handle_id}: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:289:34 + | +289 | Err(e) => return Err(format!("Failed to clone file handle: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +289 - Err(e) => return Err(format!("Failed to clone file handle: {}", e)), +289 + Err(e) => return Err(format!("Failed to clone file handle: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:299:39 + | +299 | Err(e) => Err(format!("Failed to write to file: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +299 - Err(e) => Err(format!("Failed to write to file: {}", e)), +299 + Err(e) => Err(format!("Failed to write to file: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:302:31 + | +302 | Err(e) => Err(format!("Failed to truncate file: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +302 - Err(e) => Err(format!("Failed to truncate file: {}", e)), +302 + Err(e) => Err(format!("Failed to truncate file: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:304:27 + | +304 | Err(e) => Err(format!("Failed to seek in file: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +304 - Err(e) => Err(format!("Failed to seek in file: {}", e)), +304 + Err(e) => Err(format!("Failed to seek in file: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:328:32 + | +328 | None => return Err(format!("Invalid file handle: {}", handle_id)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +328 - None => return Err(format!("Invalid file handle: {}", handle_id)), +328 + None => return Err(format!("Invalid file handle: {handle_id}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:334:31 + | +334 | Err(e) => Err(format!("Failed to append to file: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +334 - Err(e) => Err(format!("Failed to append to file: {}", e)), +334 + Err(e) => Err(format!("Failed to append to file: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:336:27 + | +336 | Err(e) => Err(format!("Failed to seek to end of file: {}", e)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +336 - Err(e) => Err(format!("Failed to seek to end of file: {}", e)), +336 + Err(e) => Err(format!("Failed to seek to end of file: {e}")), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:404:34 + | +404 | changes.push(format!("{} = {} -> {}", name, old_value, value)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +404 - changes.push(format!("{} = {} -> {}", name, old_value, value)); +404 + changes.push(format!("{name} = {old_value} -> {value}")); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:407:30 + | +407 | changes.push(format!("{} = {}", name, value)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +407 - changes.push(format!("{} = {}", name, value)); +407 + changes.push(format!("{name} = {value}")); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:414:17 + | +414 | println!(" {}", change); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +414 - println!(" {}", change); +414 + println!(" {change}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:428:9 + | +428 | format!("{:?}", stmt) + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +428 - format!("{:?}", stmt) +428 + format!("{stmt:?}") + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:435:17 + | +435 | eprintln!("Error flushing stdout: {}", e); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +435 - eprintln!("Error flushing stdout: {}", e); +435 + eprintln!("Error flushing stdout: {e}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:452:21 + | +452 | eprintln!("Error reading input: {}", e); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +452 - eprintln!("Error reading input: {}", e); +452 + eprintln!("Error reading input: {e}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:509:13 + | +509 | print!("{}", arg); + | ^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +509 - print!("{}", arg); +509 + print!("{arg}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:588:29 + | +588 | env.define(&format!("flag_{}", key), value); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +588 - env.define(&format!("flag_{}", key), value); +588 + env.define(&format!("flag_{key}"), value); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:853:17 + | +853 | println!("{}", value); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +853 - println!("{}", value); +853 + println!("{value}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1045:25 + | +1045 | / format!( +1046 | | "Count loop exceeded maximum iterations ({})", +1047 | | max_iterations +1048 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1329:29 + | +1329 | ... format!("Expected string for file path, got {:?}", path_value), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1329 - format!("Expected string for file path, got {:?}", path_value), +1329 + format!("Expected string for file path, got {path_value:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1356:29 + | +1356 | / ... format!( +1357 | | ... "Expected string for file path or handle, got {:?}", +1358 | | ... path_value +1359 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1409:29 + | +1409 | ... format!("Expected string for file handle, got {:?}", file_value), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1409 - format!("Expected string for file handle, got {:?}", file_value), +1409 + format!("Expected string for file handle, got {file_value:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1420:29 + | +1420 | ... format!("Expected string for file content, got {:?}", content_value), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1420 - format!("Expected string for file content, got {:?}", content_value), +1420 + format!("Expected string for file content, got {content_value:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1449:29 + | +1449 | ... format!("Expected string for file handle, got {:?}", file_value), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1449 - format!("Expected string for file handle, got {:?}", file_value), +1449 + format!("Expected string for file handle, got {file_value:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1486:29 + | +1486 | ... format!("Timeout waiting for variable '{}'", var_name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1486 - format!("Timeout waiting for variable '{}'", var_name), +1486 + format!("Timeout waiting for variable '{var_name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1506:37 + | +1506 | / ... format!( +1507 | | ... "Expected string for file handle, got {:?}", +1508 | | ... file_value +1509 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1520:37 + | +1520 | / ... format!( +1521 | | ... "Expected string for file content, got {:?}", +1522 | | ... content_value +1523 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1570:37 + | +1570 | / ... format!( +1571 | | ... "Expected string for file path or handle, got {:?}", +1572 | | ... path_value +1573 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1652:29 + | +1652 | ... format!("Expected string for URL, got {:?}", url_val), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1652 - format!("Expected string for URL, got {:?}", url_val), +1652 + format!("Expected string for URL, got {url_val:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1682:29 + | +1682 | ... format!("Expected string for URL, got {:?}", url_val), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1682 - format!("Expected string for URL, got {:?}", url_val), +1682 + format!("Expected string for URL, got {url_val:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1693:29 + | +1693 | ... format!("Expected string for data, got {:?}", data_val), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1693 - format!("Expected string for data, got {:?}", data_val), +1693 + format!("Expected string for data, got {data_val:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1751:25 + | +1751 | format!("Cannot push to non-list value: {:?}", list_val), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1751 - format!("Cannot push to non-list value: {:?}", list_val), +1751 + format!("Cannot push to non-list value: {list_val:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1778:41 + | +1778 | .map(|ast_type| format!("{:?}", ast_type)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1778 - .map(|ast_type| format!("{:?}", ast_type)); +1778 + .map(|ast_type| format!("{ast_type:?}")); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1859:29 + | +1859 | ... format!("Container '{}' not found", container_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1859 - format!("Container '{}' not found", container_type), +1859 + format!("Container '{container_type}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:1965:29 + | +1965 | ... format!("Event '{}' not found", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1965 - format!("Event '{}' not found", name), +1965 + format!("Event '{name}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2025:33 + | +2025 | ... format!("Container '{}' not found", container_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2025 - format!("Container '{}' not found", container_type), +2025 + format!("Container '{container_type}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2062:29 + | +2062 | / ... format!( +2063 | | ... "Event '{}' not found in container '{}'", +2064 | | ... event_name, container_type +2065 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2111:37 + | +2111 | ... format!("Parent container '{}' not found", parent_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2111 - format!("Parent container '{}' not found", parent_type), +2111 + format!("Parent container '{parent_type}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2153:33 + | +2153 | / ... format!( +2154 | | ... "Method '{}' not found in parent container '{}'", +2155 | | ... method_name, parent_type +2156 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2262:29 + | +2262 | ... format!("Container '{}' not found", container), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2262 - format!("Container '{}' not found", container), +2262 + format!("Container '{container}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2286:25 + | +2286 | / format!( +2287 | | "Static member '{}' not found in container '{}'", +2288 | | member, container +2289 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2319:33 + | +2319 | ... format!("Container '{}' not found", container_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2319 - format!("Container '{}' not found", container_type), +2319 + format!("Container '{container_type}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2361:29 + | +2361 | / ... format!( +2362 | | ... "Method '{}' not found in container '{}'", +2363 | | ... method, container_type +2364 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2371:25 + | +2371 | format!("Cannot call method '{}' on non-container value", method), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2371 - format!("Cannot call method '{}' on non-container value", method), +2371 + format!("Cannot call method '{method}' on non-container value"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2396:25 + | +2396 | format!("Pattern compilation error: {}", err), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2396 - format!("Pattern compilation error: {}", err), +2396 + format!("Pattern compilation error: {err}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2418:25 + | +2418 | / println!( +2419 | | "Warning: Using 'count' outside of a count loop context at line {}, column {}", +2420 | | line, column +2421 | | ); + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2430:25 + | +2430 | format!("Undefined variable '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2430 - format!("Undefined variable '{}'", name), +2430 + format!("Undefined variable '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2537:26 + | +2537 | _ => format!("{:?}", function_val), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2537 - _ => format!("{:?}", function_val), +2537 + _ => format!("{function_val:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2550:33 + | +2550 | ... format!("Error in native function: {}", e), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2550 - format!("Error in native function: {}", e), +2550 + format!("Error in native function: {e}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2578:39 + | +2578 | RuntimeError::new(format!("Undefined action '{}'", name), *line, *column) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2578 - RuntimeError::new(format!("Undefined action '{}'", name), *line, *column) +2578 + RuntimeError::new(format!("Undefined action '{name}'"), *line, *column) + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2610:25 + | +2610 | format!("'{}' is not callable", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2610 - format!("'{}' is not callable", name), +2610 + format!("'{name}' is not callable"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2632:33 + | +2632 | ... format!("Object has no property '{}'", property), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2632 - format!("Object has no property '{}'", property), +2632 + format!("Object has no property '{property}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2684:33 + | +2684 | ... format!("Object has no key '{}'", key_str), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2684 - format!("Object has no key '{}'", key_str), +2684 + format!("Object has no key '{key_str}'"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2729:30 + | +2729 | let result = format!("{}{}", left_val, right_val); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2729 - let result = format!("{}{}", left_val, right_val); +2729 + let result = format!("{left_val}{right_val}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2802:33 + | +2802 | ... format!("Property '{}' not found", property), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2802 - format!("Property '{}' not found", property), +2802 + format!("Property '{property}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2809:25 + | +2809 | / format!( +2810 | | "Cannot access property '{}' on non-container value", +2811 | | property +2812 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2957:30 + | +2957 | let result = format!("{}{}", a, b); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2957 - let result = format!("{}{}", a, b); +2957 + let result = format!("{a}{b}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2961:30 + | +2961 | let result = format!("{}{}", a, b); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2961 - let result = format!("{}{}", a, b); +2961 + let result = format!("{a}{b}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:2965:30 + | +2965 | let result = format!("{}{}", a, b); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +2965 - let result = format!("{}{}", a, b); +2965 + let result = format!("{a}{b}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/mod.rs:3035:29 + | +3035 | ... format!("Division resulted in invalid number: {}", result), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +3035 - format!("Division resulted in invalid number: {}", result), +3035 + format!("Division resulted in invalid number: {result}"), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:116:29 + | +116 | ... format!("Variable name '{}' should be snake_case", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +116 - format!("Variable name '{}' should be snake_case", name), +116 + format!("Variable name '{name}' should be snake_case"), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:117:34 + | +117 | ... Some(format!("Rename to '{}'", snake_case_name)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +117 - Some(format!("Rename to '{}'", snake_case_name)), +117 + Some(format!("Rename to '{snake_case_name}'")), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:134:29 + | +134 | ... format!("Action name '{}' should be snake_case", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +134 - format!("Action name '{}' should be snake_case", name), +134 + format!("Action name '{name}' should be snake_case"), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:135:34 + | +135 | ... Some(format!("Rename to '{}'", snake_case_name)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +135 - Some(format!("Rename to '{}'", snake_case_name)), +135 + Some(format!("Rename to '{snake_case_name}'")), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:209:25 + | +209 | / format!( +210 | | "Line should be indented with {} spaces, found {}", +211 | | expected_indent, indent_spaces +212 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:213:30 + | +213 | Some(format!("Adjust indentation to {} spaces", expected_indent)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +213 - Some(format!("Adjust indentation to {} spaces", expected_indent)), +213 + Some(format!("Adjust indentation to {expected_indent} spaces")), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:301:25 + | +301 | format!("Keyword '{}' should be lowercase", uppercase_keyword), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +301 - format!("Keyword '{}' should be lowercase", uppercase_keyword), +301 + format!("Keyword '{uppercase_keyword}' should be lowercase"), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:302:30 + | +302 | Some(format!("Change to '{}'", keyword)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +302 - Some(format!("Change to '{}'", keyword)), +302 + Some(format!("Change to '{keyword}'")), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:315:25 + | +315 | format!("Keyword '{}' should be lowercase", mixed_case_keyword), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +315 - format!("Keyword '{}' should be lowercase", mixed_case_keyword), +315 + format!("Keyword '{mixed_case_keyword}' should be lowercase"), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:316:30 + | +316 | Some(format!("Change to '{}'", keyword)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +316 - Some(format!("Change to '{}'", keyword)), +316 + Some(format!("Change to '{keyword}'")), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:408:25 + | +408 | format!("Line exceeds maximum length of {} characters", max_length), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +408 - format!("Line exceeds maximum length of {} characters", max_length), +408 + format!("Line exceeds maximum length of {max_length} characters"), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:409:30 + | +409 | Some(format!("Shorten line to {} characters or less", max_length)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +409 - Some(format!("Shorten line to {} characters or less", max_length)), +409 + Some(format!("Shorten line to {max_length} characters or less")), + | + +error: variables can be used directly in the `format!` string + --> src/linter/mod.rs:469:21 + | +469 | format!("Nesting depth exceeds maximum of {}", max_depth), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +469 - format!("Nesting depth exceeds maximum of {}", max_depth), +469 + format!("Nesting depth exceeds maximum of {max_depth}"), + | + +error: variables can be used directly in the `format!` string + --> src/logging.rs:129:9 + | +129 | format!("{}_exec.log", file_name) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +129 - format!("{}_exec.log", file_name) +129 + format!("{file_name}_exec.log") + | + +error: variables can be used directly in the `format!` string + --> src/parser/mod.rs:1199:17 + | +1199 | / format!( +1200 | | "Expected 'as' after variable name '{}', but found end of input", +1201 | | name +1202 | | ), + | |_________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/parser/mod.rs:1321:17 + | +1321 | format!("{}: unexpected end of input", error_message), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1321 - format!("{}: unexpected end of input", error_message), +1321 + format!("{error_message}: unexpected end of input"), + | + +error: variables can be used directly in the `format!` string + --> src/parser/mod.rs:4209:28 + | +4209 | Ok(format!("rep(1,inf,{})", inner)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +4209 - Ok(format!("rep(1,inf,{})", inner)) +4209 + Ok(format!("rep(1,inf,{inner})")) + | + +error: variables can be used directly in the `format!` string + --> src/parser/mod.rs:4229:20 + | +4229 | Ok(format!("rep(0,1,{})", inner)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +4229 - Ok(format!("rep(0,1,{})", inner)) +4229 + Ok(format!("rep(0,1,{inner})")) + | + +error: variables can be used directly in the `format!` string + --> src/parser/mod.rs:4244:36 + | +4244 | ... Ok(format!("rep({},{},{})", min_val, max_val, inner)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +4244 - Ok(format!("rep({},{},{})", min_val, max_val, inner)) +4244 + Ok(format!("rep({min_val},{max_val},{inner})")) + | + +error: variables can be used directly in the `format!` string + --> src/parser/mod.rs:4301:32 + | +4301 | ... Ok(format!("cap(\"{}\",{})", capture_name, inner_ir)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +4301 - Ok(format!("cap(\"{}\",{})", capture_name, inner_ir)) +4301 + Ok(format!("cap(\"{capture_name}\",{inner_ir})")) + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:109:21 + | +109 | eprintln!("Flush failed: {}", e); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +109 - eprintln!("Flush failed: {}", e); +109 + eprintln!("Flush failed: {e}"); + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:113:44 + | +113 | _ => Ok(CommandResult::Unknown(format!( + | ____________________________________________^ +114 | | "Unknown command: {}", +115 | | command +116 | | ))), + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/repl.rs:217:41 + | +217 | error_messages.push(format!("Type error: {}", error)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +217 - error_messages.push(format!("Type error: {}", error)); +217 + error_messages.push(format!("Type error: {error}")); + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:239:50 + | +239 | ... result_output = Some(format!("{:?}", value)); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +239 - result_output = Some(format!("{:?}", value)); +239 + result_output = Some(format!("{value:?}")); + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:257:57 + | +257 | ... error_messages.push(format!("Runtime error: {}", error)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +257 - error_messages.push(format!("Runtime error: {}", error)); +257 + error_messages.push(format!("Runtime error: {error}")); + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:287:53 + | +287 | ... error_messages.push(format!("Runtime error: {}", error)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +287 - error_messages.push(format!("Runtime error: {}", error)); +287 + error_messages.push(format!("Runtime error: {error}")); + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:318:49 + | +318 | ... error_messages.push(format!("Runtime error: {}", error)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +318 - error_messages.push(format!("Runtime error: {}", error)); +318 + error_messages.push(format!("Runtime error: {error}")); + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:358:41 + | +358 | Ok(Some(output)) => println!("{}", output), + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +358 - Ok(Some(output)) => println!("{}", output), +358 + Ok(Some(output)) => println!("{output}"), + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:360:35 + | +360 | Err(error) => println!("Error: {}", error), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +360 - Err(error) => println!("Error: {}", error), +360 + Err(error) => println!("Error: {error}"), + | + +error: variables can be used directly in the `format!` string + --> src/repl.rs:372:17 + | +372 | println!("Error: {:?}", err); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +372 - println!("Error: {:?}", err); +372 + println!("Error: {err:?}"); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/core.rs:11:9 + | +11 | print!("{}", arg); + | ^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +11 - print!("{}", arg); +11 + print!("{arg}"); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:71:37 + | +71 | regex_str.push_str(&format!("(?P<{}>.*?)", placeholder_name)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +71 - regex_str.push_str(&format!("(?P<{}>.*?)", placeholder_name)); +71 + regex_str.push_str(&format!("(?P<{placeholder_name}>.*?)")); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:172:37 + | +172 | regex_str.push_str(&format!("(?:{})?", part_regex)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +172 - regex_str.push_str(&format!("(?:{})?", part_regex)); +172 + regex_str.push_str(&format!("(?:{part_regex})?")); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:189:37 + | +189 | regex_str.push_str(&format!("(?:{})+", part_regex)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +189 - regex_str.push_str(&format!("(?:{})+", part_regex)); +189 + regex_str.push_str(&format!("(?:{part_regex})+")); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:228:37 + | +228 | regex_str.push_str(&format!("(?:{}){{{}}}", part_regex, count)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +228 - regex_str.push_str(&format!("(?:{}){{{}}}", part_regex, count)); +228 + regex_str.push_str(&format!("(?:{part_regex}){{{count}}}")); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:292:37 + | +292 | regex_str.push_str(&format!("(?:{}){{{},{}}}", part_regex, min, max)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +292 - regex_str.push_str(&format!("(?:{}){{{},{}}}", part_regex, min, max)); +292 + regex_str.push_str(&format!("(?:{part_regex}){{{min},{max}}}")); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:309:29 + | +309 | regex_str = format!("^{}{}", part_regex, regex_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +309 - regex_str = format!("^{}{}", part_regex, regex_str); +309 + regex_str = format!("^{part_regex}{regex_str}"); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:326:37 + | +326 | regex_str.push_str(&format!("{}$", part_regex)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +326 - regex_str.push_str(&format!("{}$", part_regex)); +326 + regex_str.push_str(&format!("{part_regex}$")); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:345:41 + | +345 | regex_str.push_str(&format!("|{}", part_regex)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +345 - regex_str.push_str(&format!("|{}", part_regex)); +345 + regex_str.push_str(&format!("|{part_regex}")); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:348:33 + | +348 | regex_str = format!("(?:{}|{})", prev_regex, part_regex); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +348 - regex_str = format!("(?:{}|{})", prev_regex, part_regex); +348 + regex_str = format!("(?:{prev_regex}|{part_regex})"); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:371:56 + | +371 | let regex = Regex::new(®ex_str).map_err(|e| format!("Invalid regex: {}", e))?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +371 - let regex = Regex::new(®ex_str).map_err(|e| format!("Invalid regex: {}", e))?; +371 + let regex = Regex::new(®ex_str).map_err(|e| format!("Invalid regex: {e}"))?; + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:623:13 + | +623 | format!("Error parsing pattern: {}", err), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +623 - format!("Error parsing pattern: {}", err), +623 + format!("Error parsing pattern: {err}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:676:13 + | +676 | format!("Error parsing pattern: {}", err), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +676 - format!("Error parsing pattern: {}", err), +676 + format!("Error parsing pattern: {err}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:733:13 + | +733 | format!("Error parsing pattern: {}", err), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +733 - format!("Error parsing pattern: {}", err), +733 + format!("Error parsing pattern: {err}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/legacy_pattern.rs:784:13 + | +784 | format!("Error parsing pattern: {}", err), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +784 - format!("Error parsing pattern: {}", err), +784 + format!("Error parsing pattern: {err}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/list.rs:95:12 + | +95 | if format!("{:?}", value) == format!("{:?}", item) { + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +95 - if format!("{:?}", value) == format!("{:?}", item) { +95 + if format!("{value:?}") == format!("{:?}", item) { + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/list.rs:95:38 + | +95 | if format!("{:?}", value) == format!("{:?}", item) { + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +95 - if format!("{:?}", value) == format!("{:?}", item) { +95 + if format!("{:?}", value) == format!("{item:?}") { + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/list.rs:116:12 + | +116 | if format!("{:?}", value) == format!("{:?}", item) { + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +116 - if format!("{:?}", value) == format!("{:?}", item) { +116 + if format!("{value:?}") == format!("{:?}", item) { + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/list.rs:116:38 + | +116 | if format!("{:?}", value) == format!("{:?}", item) { + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +116 - if format!("{:?}", value) == format!("{:?}", item) { +116 + if format!("{:?}", value) == format!("{item:?}") { + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/math.rs:103:13 + | +103 | / format!( +104 | | "clamp min ({}) must be less than or equal to max ({})", +105 | | min, max +106 | | ), + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:22:46 + | +22 | PatternError::ParseError(msg) => write!(f, "Pattern parse error: {}", msg), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +22 - PatternError::ParseError(msg) => write!(f, "Pattern parse error: {}", msg), +22 + PatternError::ParseError(msg) => write!(f, "Pattern parse error: {msg}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:23:48 + | +23 | PatternError::RuntimeError(msg) => write!(f, "Pattern runtime error: {}", msg), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +23 - PatternError::RuntimeError(msg) => write!(f, "Pattern runtime error: {}", msg), +23 + PatternError::RuntimeError(msg) => write!(f, "Pattern runtime error: {msg}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:128:42 + | +128 | Err(PatternError::ParseError(format!( + | __________________________________________^ +129 | | "Invalid literal format: {}", +130 | | ir +131 | | ))) + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:140:47 + | +140 | _ => Err(PatternError::ParseError(format!( + | _______________________________________________^ +141 | | "Unknown character class: {}", +142 | | class_name +143 | | ))), + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:188:49 + | +188 | return Err(PatternError::ParseError(format!( + | _________________________________________________^ +189 | | "Invalid range: min {} > max {}", +190 | | min, max +191 | | ))); + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:229:47 + | +229 | _ => Err(PatternError::ParseError(format!( + | _______________________________________________^ +230 | | "Unknown anchor type: {}", +231 | | anchor_type +232 | | ))), + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:461:34 + | +461 | recursion_stack.push(format!("capture:{}", name)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +461 - recursion_stack.push(format!("capture:{}", name)); +461 + recursion_stack.push(format!("capture:{name}")); + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:591:13 + | +591 | format!("Pattern execution error: {}", e), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +591 - format!("Pattern execution error: {}", e), +591 + format!("Pattern execution error: {e}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:644:13 + | +644 | format!("Pattern execution error: {}", e), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +644 - format!("Pattern execution error: {}", e), +644 + format!("Pattern execution error: {e}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:705:13 + | +705 | format!("Pattern execution error: {}", e), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +705 - format!("Pattern execution error: {}", e), +705 + format!("Pattern execution error: {e}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/pattern.rs:774:21 + | +774 | format!("Pattern execution error: {}", e), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +774 - format!("Pattern execution error: {}", e), +774 + format!("Pattern execution error: {e}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:216:13 + | +216 | format!("Failed to parse date: {}", e), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +216 - format!("Failed to parse date: {}", e), +216 + format!("Failed to parse date: {e}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:264:13 + | +264 | format!("Failed to parse time: {}", e), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +264 - format!("Failed to parse time: {}", e), +264 + format!("Failed to parse time: {e}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:329:13 + | +329 | format!("Hours must be between 0 and 23, got {}", hours), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +329 - format!("Hours must be between 0 and 23, got {}", hours), +329 + format!("Hours must be between 0 and 23, got {hours}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:337:13 + | +337 | format!("Minutes must be between 0 and 59, got {}", minutes), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +337 - format!("Minutes must be between 0 and 59, got {}", minutes), +337 + format!("Minutes must be between 0 and 59, got {minutes}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:345:13 + | +345 | format!("Seconds must be between 0 and 59, got {}", seconds), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +345 - format!("Seconds must be between 0 and 59, got {}", seconds), +345 + format!("Seconds must be between 0 and 59, got {seconds}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:354:13 + | +354 | / format!( +355 | | "Failed to create time with hours: {}, minutes: {}, seconds: {}", +356 | | hours, minutes, seconds +357 | | ), + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:418:13 + | +418 | format!("Month must be between 1 and 12, got {}", month), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +418 - format!("Month must be between 1 and 12, got {}", month), +418 + format!("Month must be between 1 and 12, got {month}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:426:13 + | +426 | format!("Day must be between 1 and 31, got {}", day), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +426 - format!("Day must be between 1 and 31, got {}", day), +426 + format!("Day must be between 1 and 31, got {day}"), + | + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:435:13 + | +435 | / format!( +436 | | "Failed to create date with year: {}, month: {}, day: {}", +437 | | year, month, day +438 | | ), + | |_____________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/stdlib/time.rs:485:42 + | +485 | .ok_or_else(|| RuntimeError::new(format!("Failed to add {} days to date", days), 0, 0))?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +485 - .ok_or_else(|| RuntimeError::new(format!("Failed to add {} days to date", days), 0, 0))?; +485 + .ok_or_else(|| RuntimeError::new(format!("Failed to add {days} days to date"), 0, 0))?; + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:41:35 + | +41 | message.push_str(&format!(" - Expected {} but found {}", expected, found)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +41 - message.push_str(&format!(" - Expected {} but found {}", expected, found)); +41 + message.push_str(&format!(" - Expected {expected} but found {found}")); + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:45:9 + | +45 | write!(f, "{}", message) + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +45 - write!(f, "{}", message) +45 + write!(f, "{message}") + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:59:35 + | +59 | Type::Custom(name) => write!(f, "{}", name), + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +59 - Type::Custom(name) => write!(f, "{}", name), +59 + Type::Custom(name) => write!(f, "{name}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:60:38 + | +60 | Type::List(item_type) => write!(f, "List of {}", item_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +60 - Type::List(item_type) => write!(f, "List of {}", item_type), +60 + Type::List(item_type) => write!(f, "List of {item_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:61:48 + | +61 | Type::Map(key_type, value_type) => write!(f, "Map from {} to {}", key_type, value_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +61 - Type::Map(key_type, value_type) => write!(f, "Map from {} to {}", key_type, value_type), +61 + Type::Map(key_type, value_type) => write!(f, "Map from {key_type} to {value_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:71:21 + | +71 | write!(f, "{}", param)?; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +71 - write!(f, "{}", param)?; +71 + write!(f, "{param}")?; + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:73:17 + | +73 | write!(f, ") -> {}", return_type) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +73 - write!(f, ") -> {}", return_type) +73 + write!(f, ") -> {return_type}") + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:77:31 + | +77 | Type::Async(t) => write!(f, "Async<{}>", t), + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +77 - Type::Async(t) => write!(f, "Async<{}>", t), +77 + Type::Async(t) => write!(f, "Async<{t}>"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:79:38 + | +79 | Type::Container(name) => write!(f, "Container<{}>", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +79 - Type::Container(name) => write!(f, "Container<{}>", name), +79 + Type::Container(name) => write!(f, "Container<{name}>"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:80:46 + | +80 | Type::ContainerInstance(name) => write!(f, "Instance<{}>", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +80 - Type::ContainerInstance(name) => write!(f, "Instance<{}>", name), +80 + Type::ContainerInstance(name) => write!(f, "Instance<{name}>"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:81:38 + | +81 | Type::Interface(name) => write!(f, "Interface<{}>", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +81 - Type::Interface(name) => write!(f, "Interface<{}>", name), +81 + Type::Interface(name) => write!(f, "Interface<{name}>"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:161:29 + | +161 | ... format!("Expected list type for push operation, got {:?}", list_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +161 - format!("Expected list type for push operation, got {:?}", list_type), +161 + format!("Expected list type for push operation, got {list_type:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:180:25 + | +180 | / format!( +181 | | "Expected boolean condition in repeat-while loop, got {:?}", +182 | | condition_type +183 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:294:25 + | +294 | format!("Could not infer type for variable '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +294 - format!("Could not infer type for variable '{}'", name), +294 + format!("Could not infer type for variable '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:316:25 + | +316 | / format!( +317 | | "Cannot initialize variable '{}' with incompatible type", +318 | | name +319 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:347:33 + | +347 | / ... format!( +348 | | ... "Cannot assign value of incompatible type to variable '{}'", +349 | | ... name +350 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:729:33 + | +729 | ... format!("'{}' is not a container type", parent_name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +729 - format!("'{}' is not a container type", parent_name), +729 + format!("'{parent_name}' is not a container type"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:738:29 + | +738 | ... format!("Parent container '{}' not found", parent_name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +738 - format!("Parent container '{}' not found", parent_name), +738 + format!("Parent container '{parent_name}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:753:33 + | +753 | ... format!("'{}' is not an interface type", interface_name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +753 - format!("'{}' is not an interface type", interface_name), +753 + format!("'{interface_name}' is not an interface type"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:762:29 + | +762 | ... format!("Interface '{}' not found", interface_name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +762 - format!("Interface '{}' not found", interface_name), +762 + format!("Interface '{interface_name}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:777:37 + | +777 | / ... format!( +778 | | ... "Default value type {:?} incompatible with declared type {:?}", +779 | | ... default_type, declared_type +780 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:813:29 + | +813 | ... format!("'{}' is not a container type", container_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +813 - format!("'{}' is not a container type", container_type), +813 + format!("'{container_type}' is not a container type"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:822:25 + | +822 | format!("Container type '{}' not found", container_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +822 - format!("Container type '{}' not found", container_type), +822 + format!("Container type '{container_type}' not found"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:892:29 + | +892 | ... format!("Cannot determine type of variable '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +892 - format!("Cannot determine type of variable '{}'", name), +892 + format!("Cannot determine type of variable '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:915:29 + | +915 | ... format!("Variable '{}' is not defined", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +915 - format!("Variable '{}' is not defined", name), +915 + format!("Variable '{name}' is not defined"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:954:33 + | +954 | / ... format!( +955 | | ... "Cannot perform {:?} operation on {} and {}", +956 | | ... operator, left_type, right_type +957 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:975:33 + | +975 | / ... format!( +976 | | ... "Cannot compare {} and {} for equality", +977 | | ... left_type, right_type +978 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:999:33 + | +999 | / ... format!( +1000 | | ... "Cannot compare {} and {} with {:?}", +1001 | | ... left_type, right_type, operator +1002 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1020:33 + | +1020 | / ... format!( +1021 | | ... "Cannot perform logical {:?} on {} and {}", +1022 | | ... operator, left_type, right_type +1023 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1040:37 + | +1040 | / ... format!( +1041 | | ... "Cannot check if {} contains {}, list items are {}", +1042 | | ... left_type, right_type, item_type +1043 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1057:37 + | +1057 | / ... format!( +1058 | | ... "Cannot check if {} contains {}, map keys are {}", +1059 | | ... left_type, right_type, key_type +1060 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1074:37 + | +1074 | / ... format!( +1075 | | ... "Cannot check if {} contains {}", +1076 | | ... left_type, right_type +1077 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1090:33 + | +1090 | ... format!("Cannot check if {} contains {}", left_type, right_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1090 - format!("Cannot check if {} contains {}", left_type, right_type), +1090 + format!("Cannot check if {left_type} contains {right_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1119:33 + | +1119 | ... format!("Cannot apply 'not' to {}", expr_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1119 - format!("Cannot apply 'not' to {}", expr_type), +1119 + format!("Cannot apply 'not' to {expr_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1133:33 + | +1133 | ... format!("Cannot negate {}", expr_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1133 - format!("Cannot negate {}", expr_type), +1133 + format!("Cannot negate {expr_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1203:29 + | +1203 | ... format!("Cannot call {}, not a function", function_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1203 - format!("Cannot call {}, not a function", function_type), +1203 + format!("Cannot call {function_type}, not a function"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1233:29 + | +1233 | ... format!("Cannot access property '{}' on {}", property, object_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1233 - format!("Cannot access property '{}' on {}", property, object_type), +1233 + format!("Cannot access property '{property}' on {object_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1260:33 + | +1260 | ... format!("List index must be a number, got {}", index_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1260 - format!("List index must be a number, got {}", index_type), +1260 + format!("List index must be a number, got {index_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1274:33 + | +1274 | ... format!("Map key must be {}, got {}", key_type, index_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1274 - format!("Map key must be {}, got {}", key_type, index_type), +1274 + format!("Map key must be {key_type}, got {index_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1288:33 + | +1288 | ... format!("Text index must be a number, got {}", index_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1288 - format!("Text index must be a number, got {}", index_type), +1288 + format!("Text index must be a number, got {index_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1302:29 + | +1302 | ... format!("Cannot index into {}", collection_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1302 - format!("Cannot index into {}", collection_type), +1302 + format!("Cannot index into {collection_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1331:25 + | +1331 | format!("Cannot concatenate {} and {}", left_type, right_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1331 - format!("Cannot concatenate {} and {}", left_type, right_type), +1331 + format!("Cannot concatenate {left_type} and {right_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1350:25 + | +1350 | format!("Expected Text for pattern matching, got {}", text_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1350 - format!("Expected Text for pattern matching, got {}", text_type), +1350 + format!("Expected Text for pattern matching, got {text_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1360:25 + | +1360 | / format!( +1361 | | "Expected Pattern for pattern matching, got {}", +1362 | | pattern_type +1363 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1379:25 + | +1379 | format!("Expected Text for pattern finding, got {}", text_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1379 - format!("Expected Text for pattern finding, got {}", text_type), +1379 + format!("Expected Text for pattern finding, got {text_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1389:25 + | +1389 | format!("Expected Pattern for pattern finding, got {}", pattern_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1389 - format!("Expected Pattern for pattern finding, got {}", pattern_type), +1389 + format!("Expected Pattern for pattern finding, got {pattern_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1411:25 + | +1411 | format!("Expected Text for pattern replacement, got {}", text_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1411 - format!("Expected Text for pattern replacement, got {}", text_type), +1411 + format!("Expected Text for pattern replacement, got {text_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1421:25 + | +1421 | / format!( +1422 | | "Expected Pattern for pattern replacement, got {}", +1423 | | pattern_type +1424 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1434:25 + | +1434 | format!("Expected Text for replacement, got {}", replacement_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1434 - format!("Expected Text for replacement, got {}", replacement_type), +1434 + format!("Expected Text for replacement, got {replacement_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1450:25 + | +1450 | format!("Expected Text for pattern splitting, got {}", text_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1450 - format!("Expected Text for pattern splitting, got {}", text_type), +1450 + format!("Expected Text for pattern splitting, got {text_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1460:25 + | +1460 | / format!( +1461 | | "Expected Pattern for pattern splitting, got {}", +1462 | | pattern_type +1463 | | ), + | |_________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1484:29 + | +1484 | ... format!("Cannot await non-async value of type {}", expr_type), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1484 - format!("Cannot await non-async value of type {}", expr_type), +1484 + format!("Cannot await non-async value of type {expr_type}"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1512:29 + | +1512 | ... format!("Undefined action '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1512 - format!("Undefined action '{}'", name), +1512 + format!("Undefined action '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1526:25 + | +1526 | format!("Cannot determine type of action '{}'", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1526 - format!("Cannot determine type of action '{}'", name), +1526 + format!("Cannot determine type of action '{name}'"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1588:29 + | +1588 | ... format!("'{}' is not an action", name), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +1588 - format!("'{}' is not an action", name), +1588 + format!("'{name}' is not an action"), + | + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1655:29 + | +1655 | / ... format!( +1656 | | ... "Cannot call method '{}' on non-container type {}", +1657 | | ... _method, object_type +1658 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/typechecker/mod.rs:1679:29 + | +1679 | / ... format!( +1680 | | ... "Cannot access property '{}' on non-container type", +1681 | | ... property +1682 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:301:34 + | +301 | message: format!("Unknown configuration key: {}", key), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +301 - message: format!("Unknown configuration key: {}", key), +301 + message: format!("Unknown configuration key: {key}"), + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:318:42 + | +318 | ... message: format!( + | ________________________________^ +319 | | ... "Invalid type for {}: expected integer, got '{}'", +320 | | ... key, value +321 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:327:52 + | +327 | ... .map(|default| format!("Set to default value: {}", default)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +327 - .map(|default| format!("Set to default value: {}", default)), +327 + .map(|default| format!("Set to default value: {default}")), + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:337:42 + | +337 | ... message: format!( + | ________________________________^ +338 | | ... "Invalid type for {}: expected boolean (true/false), got '{}'", +339 | | ... key, value +340 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:346:52 + | +346 | ... .map(|default| format!("Set to default value: {}", default)), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +346 - .map(|default| format!("Set to default value: {}", default)), +346 + .map(|default| format!("Set to default value: {default}")), + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:357:46 + | +357 | ... message: format!( + | ________________________________^ +358 | | ... "Invalid value for {}: expected one of {:?}, got '{}'", +359 | | ... key, valid_values, value +360 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:364:41 + | +364 | ... format!("Set to default value: {}", default) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +364 - format!("Set to default value: {}", default) +364 + format!("Set to default value: {default}") + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:377:46 + | +377 | ... message: format!( + | ________________________________^ +378 | | ... "Invalid value for {}: expected one of {:?}, got '{}'", +379 | | ... key, valid_values, value +380 | | ... ), + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:384:41 + | +384 | ... format!("Set to default value: {}", default) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +384 - format!("Set to default value: {}", default) +384 + format!("Set to default value: {default}") + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:423:34 + | +423 | message: format!("Missing required setting: {}", key), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +423 - message: format!("Missing required setting: {}", key), +423 + message: format!("Missing required setting: {key}"), + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:427:29 + | +427 | ... format!("Add '{}' with default value: {}", key, default) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +427 - format!("Add '{}' with default value: {}", key, default) +427 + format!("Add '{key}' with default value: {default}") + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:481:29 + | +481 | ... println!("✅ Commented out unknown key at line {}", line_number); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +481 - println!("✅ Commented out unknown key at line {}", line_number); +481 + println!("✅ Commented out unknown key at line {line_number}"); + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:493:41 + | +493 | ... format!("{} = {}", setting_name, default_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +493 - format!("{} = {}", setting_name, default_value); +493 + format!("{setting_name} = {default_value}"); + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:494:37 + | +494 | / ... println!( +495 | | ... "✅ Fixed value for '{}' at line {}", +496 | | ... setting_name, line_number +497 | | ... ); + | |_______________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:510:48 + | +510 | ... lines.push(format!("{} = {}", setting_name, default_value)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +510 - lines.push(format!("{} = {}", setting_name, default_value)); +510 + lines.push(format!("{setting_name} = {default_value}")); + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:512:37 + | +512 | ... println!("✅ Added missing setting: {}", setting_name); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +512 - println!("✅ Added missing setting: {}", setting_name); +512 + println!("✅ Added missing setting: {setting_name}"); + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:561:21 + | +561 | println!(" Fix: {}", fix_message); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +561 - println!(" Fix: {}", fix_message); +561 + println!(" Fix: {fix_message}"); + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:563:21 + | +563 | println!(" Suggested fix: {}", fix_message); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +563 - println!(" Suggested fix: {}", fix_message); +563 + println!(" Suggested fix: {fix_message}"); + | + +error: variables can be used directly in the `format!` string + --> src/wfl_config/checker.rs:570:9 + | +570 | / println!( +571 | | "{} errors, {} warnings found in configuration files", +572 | | error_count, warning_count +573 | | ); + | |_________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/lib.rs:40:13 + | +40 | eprintln!("Failed to initialize logger: {}", e); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +40 - eprintln!("Failed to initialize logger: {}", e); +40 + eprintln!("Failed to initialize logger: {e}"); + | + +error: variables can be used directly in the `format!` string + --> src/lib.rs:47:13 + | +47 | eprintln!("Failed to initialize execution logger: {}", e); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +47 - eprintln!("Failed to initialize execution logger: {}", e); +47 + eprintln!("Failed to initialize execution logger: {e}"); + | + +error: could not compile `wfl` (lib) due to 286 previous errors +warning: build failed, waiting for other jobs to finish... +error: variables can be used directly in the `format!` string + --> src/debug_report.rs:343:28 + | +343 | let debug_output = format!("{:?}", safe_debug); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +343 - let debug_output = format!("{:?}", safe_debug); +343 + let debug_output = format!("{safe_debug:?}"); + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/memory_tests.rs:132:9 + | +132 | / assert_eq!( +133 | | initial_count, final_count, +134 | | "Reference count before ({}) and after ({}) should be the same", +135 | | initial_count, final_count +136 | | ); + | |_________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args + +error: variables can be used directly in the `format!` string + --> src/interpreter/tests.rs:25:22 + | +25 | _ => panic!("Expected number, got {:?}", result), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +25 - _ => panic!("Expected number, got {:?}", result), +25 + _ => panic!("Expected number, got {result:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/tests.rs:48:14 + | +48 | _ => panic!("Expected number, got {:?}", result), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +48 - _ => panic!("Expected number, got {:?}", result), +48 + _ => panic!("Expected number, got {result:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/tests.rs:63:14 + | +63 | _ => panic!("Expected number, got {:?}", result), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +63 - _ => panic!("Expected number, got {:?}", result), +63 + _ => panic!("Expected number, got {result:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/tests.rs:73:14 + | +73 | _ => panic!("Expected boolean, got {:?}", result), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +73 - _ => panic!("Expected boolean, got {:?}", result), +73 + _ => panic!("Expected boolean, got {result:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/tests.rs:89:14 + | +89 | _ => panic!("Expected null, got {:?}", result), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +89 - _ => panic!("Expected null, got {:?}", result), +89 + _ => panic!("Expected null, got {result:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/tests.rs:128:14 + | +128 | _ => panic!("Expected null, got {:?}", result), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args +help: change this to + | +128 - _ => panic!("Expected null, got {:?}", result), +128 + _ => panic!("Expected null, got {result:?}"), + | + +error: variables can be used directly in the `format!` string + --> src/interpreter/tests.rs:169:5 + | +169 | / assert!( +170 | | elapsed.as_millis() <= 1100, +171 | | "Timeout took too long: {:?}", +172 | | elapsed +173 | | ); + | |_____^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args \ No newline at end of file diff --git a/inheritance_and_interfaces.md b/inheritance_and_interfaces.md new file mode 100644 index 00000000..81302b04 --- /dev/null +++ b/inheritance_and_interfaces.md @@ -0,0 +1,692 @@ +# Inheritance and Interface Implementation in WFL + +This document provides a detailed explanation of how inheritance and interfaces will work together in the WFL container implementation. + +## 1. Inheritance and Interface Architecture + +```mermaid +graph TD + A[Container Definition] --> B[Properties] + A --> C[Methods] + A --> D[Events] + + E[Parent Container] --> F[Child Container] + G[Interface] --> H[Implementing Container] + + I[Method Resolution] --> J[Property Resolution] + K[Interface Validation] --> L[Type Checking] +``` + +## 2. Container Inheritance Model + +Inheritance in WFL containers follows a single-inheritance model, where a container can extend exactly one parent container: + +```wfl +create container Vehicle: + property make as text + property model as text + + define action describe: + display make with " " with model + end action +end container + +create container Car extends Vehicle: + property doors as number defaults to 4 + + // Override parent method + define action describe: + parent describe // Call parent method + display "with " with doors with " doors" + end action +end container +``` + +In the AST, inheritance is represented by the `extends` field in the `ContainerDefinition` structure: + +```rust +ContainerDefinition { + name: String, + extends: Option, // Name of parent container + // Other fields... +} +``` + +## 3. Interface Definition and Implementation + +Interfaces define a contract that containers must fulfill: + +```wfl +create interface Drawable: + requires action draw + requires action resize with width and height +end interface + +create container Circle implements Drawable: + property radius as number + + define action draw: // Required by Drawable + display "Drawing circle with radius " with radius + end action + + define action resize with width and height: // Required by Drawable + set radius to minimum of width and height divided by 2 + end action +end container +``` + +In the AST, interfaces are represented by the `InterfaceDefinition` structure, and interface implementation is represented by the `implements` field in the `ContainerDefinition` structure: + +```rust +InterfaceDefinition { + name: String, + required_actions: Vec, + line: usize, + column: usize, +} + +ContainerDefinition { + // Other fields... + implements: Vec, // Names of implemented interfaces +} +``` + +## 4. Method Resolution Order + +When a method is called on a container instance, the interpreter follows this resolution order: + +1. Look for the method in the container's own methods +2. If not found, look in the parent container (and so on up the inheritance chain) +3. If still not found, check if any implemented interfaces define the method + +```rust +fn resolve_method( + &self, + container: &ContainerValue, + method_name: &str, +) -> Option> { + // Check method cache first + if let Some((_, method)) = container.method_cache.borrow().get(method_name) { + return Some(method.clone()); + } + + // Check own methods + if let Some(method) = container.methods.get(method_name) { + return Some(method.clone()); + } + + // Check parent container + if let Some(parent_weak) = &container.extends { + if let Some(parent) = parent_weak.upgrade() { + if let Some(method) = self.resolve_method(&parent, method_name) { + // Cache the result + container.method_cache.borrow_mut().insert( + method_name.to_string(), + (Rc::downgrade(&parent), method.clone()) + ); + return Some(method); + } + } + } + + None +} +``` + +## 5. Property Resolution Order + +Similarly, property resolution follows the inheritance chain: + +```rust +fn resolve_property( + &self, + container: &ContainerValue, + property_name: &str, +) -> Option { + // Check property cache first + if let Some((_, property)) = container.property_cache.borrow().get(property_name) { + return Some(property.clone()); + } + + // Check own properties + if let Some(property) = container.properties.get(property_name) { + return Some(property.clone()); + } + + // Check parent container + if let Some(parent_weak) = &container.extends { + if let Some(parent) = parent_weak.upgrade() { + if let Some(property) = self.resolve_property(&parent, property_name) { + // Cache the result + container.property_cache.borrow_mut().insert( + property_name.to_string(), + (Rc::downgrade(&parent), property.clone()) + ); + return Some(property); + } + } + } + + None +} +``` + +## 6. Parent Method Calls + +WFL allows child containers to call methods from their parent containers using the `parent` keyword: + +```wfl +define action describe: + parent describe // Call parent method + display "with " with doors with " doors" +end action +``` + +To implement this, we need to handle the `parent` keyword in method calls: + +```rust +async fn execute_parent_method_call( + &self, + method_name: &str, + arguments: &[Argument], + env: Rc>, +) -> Result { + // Get the current container instance from the environment + let this_value = match env.borrow().get("this") { + Some(Value::ContainerInstance(instance)) => Value::ContainerInstance(instance), + _ => return Err(RuntimeError::new( + "Cannot call parent method outside of container method".to_string(), + /* line */, + /* column */, + )), + }; + + match this_value { + Value::ContainerInstance(instance) => { + let instance_ref = instance.borrow(); + + // Get the container definition + let container = match instance_ref.container.upgrade() { + Some(c) => c, + None => return Err(RuntimeError::new( + "Container no longer exists".to_string(), + /* line */, + /* column */, + )), + }; + + // Get the parent container + let parent = match &container.extends { + Some(parent_weak) => match parent_weak.upgrade() { + Some(p) => p, + None => return Err(RuntimeError::new( + "Parent container no longer exists".to_string(), + /* line */, + /* column */, + )), + }, + None => return Err(RuntimeError::new( + "Container has no parent".to_string(), + /* line */, + /* column */, + )), + }; + + // Look up the method in the parent container + let method = match parent.methods.get(method_name) { + Some(m) => m.clone(), + None => return Err(RuntimeError::new( + format!("Method '{}' not found in parent container", method_name), + /* line */, + /* column */, + )), + }; + + // Evaluate arguments + let mut arg_values = Vec::new(); + for arg in arguments { + let value = self.evaluate_expression(&arg.value, Rc::clone(&env)).await?; + arg_values.push(value); + } + + // Call the method + self.call_function(&method, arg_values, /* line */, /* column */).await + }, + _ => Err(RuntimeError::new( + "Cannot call parent method outside of container method".to_string(), + /* line */, + /* column */, + )), + } +} +``` + +## 7. Interface Validation + +When a container implements an interface, we need to validate that it provides all the required methods: + +```rust +fn validate_interface_implementation( + &self, + container: &ContainerValue, + interface: &InterfaceValue, +) -> Result<(), RuntimeError> { + for (method_name, signature) in &interface.required_actions { + // Check if the container has the method + let method = match self.resolve_method(container, method_name) { + Some(m) => m, + None => return Err(RuntimeError::new( + format!( + "Container '{}' does not implement required method '{}' from interface '{}'", + container.name, method_name, interface.name + ), + /* line */, + /* column */, + )), + }; + + // Check if the method signature matches + if method.params.len() != signature.parameters.len() { + return Err(RuntimeError::new( + format!( + "Method '{}' in container '{}' has wrong number of parameters for interface '{}'", + method_name, container.name, interface.name + ), + /* line */, + /* column */, + )); + } + + // TODO: Check parameter types and return type + } + + Ok(()) +} +``` + +## 8. Multiple Interface Implementation + +A container can implement multiple interfaces: + +```wfl +create container MultiButton extends UIElement implements Clickable, Draggable, Resizable: + // Implementation of all required methods from all interfaces +end container +``` + +When validating interface implementation, we need to check all implemented interfaces: + +```rust +async fn execute_container_definition( + &self, + name: &str, + extends: Option<&str>, + implements: &[String], + // Other parameters... +) -> Result { + // Create container value + let container = Rc::new(ContainerValue { + name: name.to_string(), + extends: None, // Will be set later if extends is Some + implements: Vec::new(), // Will be populated later + // Other fields... + }); + + // Set parent container if extends is Some + if let Some(parent_name) = extends { + let parent_value = match env.borrow().get(parent_name) { + Some(Value::Container(parent)) => parent, + _ => return Err(RuntimeError::new( + format!("Parent container '{}' not found", parent_name), + /* line */, + /* column */, + )), + }; + + container.extends = Some(Rc::downgrade(&parent_value)); + } + + // Set implemented interfaces + for interface_name in implements { + let interface_value = match env.borrow().get(interface_name) { + Some(Value::Interface(interface)) => interface, + _ => return Err(RuntimeError::new( + format!("Interface '{}' not found", interface_name), + /* line */, + /* column */, + )), + }; + + container.implements.push(Rc::downgrade(&interface_value)); + + // Validate interface implementation + self.validate_interface_implementation(&container, &interface_value)?; + } + + // Register container in environment + env.borrow_mut().define(name, Value::Container(container)); + + Ok(Value::Null) +} +``` + +## 9. Polymorphism + +Interfaces enable polymorphism, allowing different container types to be used interchangeably: + +```wfl +create list shapes: + add new Circle with radius 5 + add new Rectangle with width 10 and height 20 +end list + +for each shape in shapes: + shape draw // Works for both Circle and Rectangle +end for +``` + +To support this, we need to handle method calls on container instances that implement interfaces: + +```rust +async fn execute_method_call( + &self, + object: &Expression, + method_name: &str, + arguments: &[Argument], + env: Rc>, +) -> Result { + // Evaluate object expression + let object_value = self.evaluate_expression(object, Rc::clone(&env)).await?; + + match object_value { + Value::ContainerInstance(instance) => { + let instance_ref = instance.borrow(); + + // Get the container definition + let container = match instance_ref.container.upgrade() { + Some(c) => c, + None => return Err(RuntimeError::new( + "Container no longer exists".to_string(), + /* line */, + /* column */, + )), + }; + + // Resolve the method + let method = match self.resolve_method(&container, method_name) { + Some(m) => m, + None => return Err(RuntimeError::new( + format!("Method '{}' not found on container '{}'", method_name, container.name), + /* line */, + /* column */, + )), + }; + + // Evaluate arguments + let mut arg_values = Vec::new(); + for arg in arguments { + let value = self.evaluate_expression(&arg.value, Rc::clone(&env)).await?; + arg_values.push(value); + } + + // Create a new environment for the method + let method_env = Environment::new(&env); + + // Add 'this' to the environment + method_env.borrow_mut().define("this", Value::ContainerInstance(instance.clone())); + + // Call the method + self.call_function(&method, arg_values, /* line */, /* column */).await + }, + _ => Err(RuntimeError::new( + format!("Cannot call method '{}' on non-container value", method_name), + /* line */, + /* column */, + )), + } +} +``` + +## 10. Memory Management for Inheritance and Interfaces + +To avoid memory leaks and reference cycles in the inheritance and interface system, we'll use several strategies: + +### 10.1 Weak References for Parent Containers + +```rust +pub struct ContainerValue { + // Other fields... + pub extends: Option>, // Weak reference to avoid cycles + pub implements: Vec>, // Weak references to avoid cycles +} +``` + +### 10.2 Method and Property Caching + +To improve performance, we'll cache resolved methods and properties: + +```rust +pub struct ContainerValue { + // Other fields... + pub method_cache: RefCell, Rc)>>, + pub property_cache: RefCell, PropertyDefinition)>>, +} +``` + +### 10.3 Cache Invalidation + +When a container is modified, we need to invalidate its caches: + +```rust +fn invalidate_caches(&self, container: &ContainerValue) { + container.method_cache.borrow_mut().clear(); + container.property_cache.borrow_mut().clear(); + + // Also invalidate caches of child containers + // This would require maintaining a list of weak references to child containers +} +``` + +## 11. Type Checking for Inheritance and Interfaces + +The type checker needs to understand container inheritance and interface implementation: + +```rust +fn check_method_call( + &mut self, + object_type: &Type, + method_name: &str, + arguments: &[Argument], +) -> Result { + match object_type { + Type::ContainerInstance(container_name) => { + // Look up container + let container = self.lookup_container(container_name)?; + + // Look up method + let method = self.lookup_method(&container, method_name)?; + + // Check arguments + self.check_arguments(&method, arguments)?; + + // Return method return type + Ok(method.return_type) + }, + Type::Interface(interface_name) => { + // Look up interface + let interface = self.lookup_interface(interface_name)?; + + // Look up method in interface + let method = self.lookup_interface_method(&interface, method_name)?; + + // Check arguments + self.check_arguments(&method, arguments)?; + + // Return method return type + Ok(method.return_type) + }, + _ => Err(TypeError::new( + format!("Cannot call method '{}' on non-container type {:?}", method_name, object_type), + /* line */, + /* column */, + )), + } +} +``` + +## 12. Interface Implementation Challenges and Solutions + +### 12.1 Challenge: Method Signature Compatibility + +When implementing an interface, the method signatures must be compatible with the interface requirements. + +**Solution**: Implement a signature compatibility checker: + +```rust +fn check_signature_compatibility( + &self, + container_method: &FunctionValue, + interface_signature: &ActionSignature, +) -> Result<(), RuntimeError> { + // Check parameter count + if container_method.params.len() != interface_signature.parameters.len() { + return Err(RuntimeError::new( + "Parameter count mismatch".to_string(), + /* line */, + /* column */, + )); + } + + // Check parameter types (if available) + // Check return type (if available) + + Ok(()) +} +``` + +### 12.2 Challenge: Interface Inheritance + +Interfaces might inherit from other interfaces: + +```wfl +create interface Drawable: + requires action draw +end interface + +create interface AnimatedDrawable extends Drawable: + requires action animate + // Inherits 'draw' requirement from Drawable +end interface +``` + +**Solution**: Implement interface inheritance: + +```rust +fn resolve_interface_method( + &self, + interface: &InterfaceValue, + method_name: &str, +) -> Option { + // Check own methods + if let Some(signature) = interface.required_actions.get(method_name) { + return Some(signature.clone()); + } + + // Check parent interfaces + for parent_weak in &interface.extends { + if let Some(parent) = parent_weak.upgrade() { + if let Some(signature) = self.resolve_interface_method(&parent, method_name) { + return Some(signature); + } + } + } + + None +} +``` + +### 12.3 Challenge: Diamond Problem + +With multiple interface implementation, we might encounter the diamond problem: + +```wfl +create interface A: + requires action foo +end interface + +create interface B extends A: + requires action bar +end interface + +create interface C extends A: + requires action baz +end interface + +create container D implements B, C: + // Must implement foo, bar, and baz + // But foo is required by both B and C +end container +``` + +**Solution**: Implement a method resolution order that handles the diamond problem: + +```rust +fn validate_multiple_interfaces( + &self, + container: &ContainerValue, + interfaces: &[Weak], +) -> Result<(), RuntimeError> { + // Collect all required methods from all interfaces + let mut required_methods = HashMap::new(); + + for interface_weak in interfaces { + if let Some(interface) = interface_weak.upgrade() { + self.collect_required_methods(&interface, &mut required_methods)?; + } + } + + // Check that container implements all required methods + for (method_name, signature) in required_methods { + // Check if container has the method + let method = match self.resolve_method(container, &method_name) { + Some(m) => m, + None => return Err(RuntimeError::new( + format!("Container '{}' does not implement required method '{}'", container.name, method_name), + /* line */, + /* column */, + )), + }; + + // Check signature compatibility + self.check_signature_compatibility(&method, &signature)?; + } + + Ok(()) +} + +fn collect_required_methods( + &self, + interface: &InterfaceValue, + required_methods: &mut HashMap, +) -> Result<(), RuntimeError> { + // Add own required methods + for (name, signature) in &interface.required_actions { + required_methods.insert(name.clone(), signature.clone()); + } + + // Add required methods from parent interfaces + for parent_weak in &interface.extends { + if let Some(parent) = parent_weak.upgrade() { + self.collect_required_methods(&parent, required_methods)?; + } + } + + Ok(()) +} +``` + +## 13. Conclusion + +The inheritance and interface system in WFL provides a powerful way to organize code and enable code reuse. By implementing single inheritance for containers and multiple interface implementation, we can support a wide range of object-oriented programming patterns while avoiding the complexities of multiple inheritance. + +The memory management strategies, particularly the use of weak references and caching, ensure that the system is efficient and avoids memory leaks. The type checking system ensures that containers correctly implement their interfaces, providing compile-time safety. \ No newline at end of file diff --git a/memory_optimization.md b/memory_optimization.md new file mode 100644 index 00000000..26cdf74c --- /dev/null +++ b/memory_optimization.md @@ -0,0 +1,747 @@ +# Memory Optimization Strategies for WFL Container Implementation + +This document outlines the memory optimization strategies that will be employed in the WFL container implementation to minimize memory allocations and avoid reference cycles. + +## 1. Overview of Memory Challenges + +Container systems in programming languages often face several memory-related challenges: + +1. **Reference Cycles**: Container instances may reference their container definitions, which in turn may reference parent containers, creating potential reference cycles. +2. **Deep Inheritance Chains**: Resolving properties and methods in deep inheritance chains can be expensive. +3. **Event Handler Leaks**: Event handlers may hold references to container instances, preventing garbage collection. +4. **String Duplication**: Property and method names may be duplicated across many container instances. +5. **Temporary Object Allocations**: Method calls and property access may create many temporary objects. + +## 2. Reference Management Architecture + +```mermaid +graph TD + A[Container Definition] -->|strong reference| B[Methods] + A -->|strong reference| C[Properties] + A -->|weak reference| D[Parent Container] + + E[Container Instance] -->|weak reference| A + E -->|strong reference| F[Property Values] + + G[Event Handler] -->|weak reference| E + H[Method Environment] -->|weak reference| I[Parent Environment] +``` + +## 3. Weak References for Cycle Prevention + +### 3.1 Container Inheritance Cycles + +Container definitions will use weak references to their parent containers to prevent reference cycles in the inheritance chain: + +```rust +pub struct ContainerValue { + pub name: String, + pub extends: Option>, // Weak reference to avoid cycles + // Other fields... +} +``` + +This ensures that child containers don't keep their parent containers alive, allowing proper garbage collection of unused containers. + +### 3.2 Container Instance to Definition References + +Container instances will use weak references to their container definitions: + +```rust +pub struct ContainerInstanceValue { + pub container: Weak, // Weak reference to avoid cycles + pub properties: HashMap, + // Other fields... +} +``` + +This allows container definitions to be garbage collected when they're no longer needed, even if instances still exist. + +### 3.3 Environment References + +Method environments will use weak references to their parent environments: + +```rust +pub struct Environment { + pub values: HashMap, + pub parent: Option>>, // Weak reference to avoid cycles +} +``` + +This prevents reference cycles between environments and allows proper garbage collection. + +### 3.4 Event Handler References + +Event handlers will use weak references to their source objects: + +```rust +pub struct EventHandler { + pub source: Weak>, // Weak reference to avoid cycles + pub event_name: String, + pub handler: Rc, +} +``` + +This prevents event handlers from keeping container instances alive when they're no longer needed. + +## 4. Caching Strategies + +### 4.1 Method Resolution Caching + +To avoid repeated lookups in inheritance chains, we'll cache resolved methods: + +```rust +pub struct ContainerValue { + // Other fields... + pub method_cache: RefCell, Rc)>>, +} +``` + +The cache stores the method and a weak reference to the container where it was found. This improves performance for method calls on containers with deep inheritance chains. + +### 4.2 Property Resolution Caching + +Similarly, we'll cache resolved properties: + +```rust +pub struct ContainerValue { + // Other fields... + pub property_cache: RefCell, PropertyDefinition)>>, +} +``` + +This improves performance for property access on containers with deep inheritance chains. + +### 4.3 Cache Invalidation + +When a container is modified, we need to invalidate its caches: + +```rust +fn invalidate_caches(&self, container: &ContainerValue) { + container.method_cache.borrow_mut().clear(); + container.property_cache.borrow_mut().clear(); + + // Also invalidate caches of child containers + // This would require maintaining a list of weak references to child containers +} +``` + +### 4.4 Lazy Loading + +Instead of eagerly loading all properties and methods from parent containers, we'll use lazy loading: + +```rust +fn get_property(&self, name: &str) -> Option { + // Check own properties first + if let Some(value) = self.properties.get(name) { + return Some(value.clone()); + } + + // Check cached properties + if let Some((_, value)) = self.property_cache.borrow().get(name) { + return Some(value.clone()); + } + + // Check parent container + if let Some(parent_weak) = &self.container.extends { + if let Some(parent) = parent_weak.upgrade() { + if let Some(value) = parent.get_property(name) { + // Cache the result + self.property_cache.borrow_mut().insert( + name.to_string(), + (Rc::downgrade(&parent), value.clone()) + ); + return Some(value); + } + } + } + + None +} +``` + +This ensures we only load properties and methods when they're actually needed. + +## 5. String Interning + +### 5.1 String Interning for Property and Method Names + +To avoid duplicating strings for property and method names, we'll use a global string interner: + +```rust +pub struct StringInterner { + strings: HashMap>, +} + +impl StringInterner { + pub fn new() -> Self { + Self { + strings: HashMap::new(), + } + } + + pub fn intern(&mut self, s: &str) -> Rc { + if let Some(interned) = self.strings.get(s) { + interned.clone() + } else { + let rc = Rc::from(s.to_string()); + self.strings.insert(s.to_string(), rc.clone()); + rc + } + } +} +``` + +This ensures that identical strings are only stored once in memory. + +### 5.2 Integration with Parser + +The parser will use the string interner for all identifiers: + +```rust +fn parse_identifier(&mut self) -> Result, ParseError> { + if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); + Ok(self.string_interner.intern(id)) + } else { + Err(ParseError::new( + format!("Expected identifier, found {:?}", token.token), + token.line, + token.column, + )) + } + } else { + Err(ParseError::new( + "Expected identifier, found end of input".to_string(), + 0, + 0, + )) + } +} +``` + +### 5.3 Integration with Value System + +The value system will use interned strings for property and method names: + +```rust +pub struct ContainerValue { + pub name: Rc, + pub properties: HashMap, PropertyDefinition>, + pub methods: HashMap, Rc>, + // Other fields... +} + +pub struct ContainerInstanceValue { + pub container: Weak, + pub properties: HashMap, Value>, + // Other fields... +} +``` + +This reduces memory usage and improves lookup performance by allowing direct pointer comparison for strings. + +## 6. Object Pooling + +### 6.1 Event Handler Context Pooling + +For frequently triggered events, we'll use an object pool for handler execution contexts: + +```rust +pub struct EventHandlerContext { + pub arguments: HashMap, + pub result: Option, +} + +pub struct EventHandlerPool { + pub available: Vec, + pub capacity: usize, +} + +impl EventHandlerPool { + pub fn new(capacity: usize) -> Self { + let mut available = Vec::with_capacity(capacity); + for _ in 0..capacity { + available.push(EventHandlerContext { + arguments: HashMap::new(), + result: None, + }); + } + + Self { + available, + capacity, + } + } + + pub fn acquire(&mut self) -> EventHandlerContext { + if let Some(context) = self.available.pop() { + context + } else { + EventHandlerContext { + arguments: HashMap::new(), + result: None, + } + } + } + + pub fn release(&mut self, mut context: EventHandlerContext) { + context.arguments.clear(); + context.result = None; + + if self.available.len() < self.capacity { + self.available.push(context); + } + } +} +``` + +This reduces the number of allocations when handling events. + +### 6.2 Property Access Context Pooling + +Similarly, we'll use an object pool for property access contexts: + +```rust +pub struct PropertyAccessContext { + pub container: Weak, + pub property_name: Rc, + pub result: Option, +} + +pub struct PropertyAccessPool { + pub available: Vec, + pub capacity: usize, +} +``` + +This reduces allocations during property access operations. + +## 7. Efficient Data Structures + +### 7.1 HashMap Optimization + +We'll use capacity hints for HashMaps to avoid reallocations: + +```rust +pub fn new_container_instance(container: &Rc) -> Rc> { + let property_count = container.properties.len(); + + Rc::new(RefCell::new(ContainerInstanceValue { + container: Rc::downgrade(container), + properties: HashMap::with_capacity(property_count), + event_handlers: HashMap::new(), + })) +} +``` + +### 7.2 Vector Optimization + +Similarly, we'll use capacity hints for Vectors: + +```rust +pub fn collect_event_handlers(&self, event_name: &str) -> Vec> { + let mut handlers = Vec::with_capacity(4); // Most events have few handlers + + if let Some(event_handlers) = self.event_handlers.get(event_name) { + handlers.extend(event_handlers.iter().cloned()); + } + + handlers +} +``` + +### 7.3 Small Vector Optimization + +For collections that are typically small, we'll use small vector optimization: + +```rust +pub enum SmallVec { + Inline([Option; 4]), + Heap(Vec), +} + +impl SmallVec { + pub fn new() -> Self { + Self::Inline([None, None, None, None]) + } + + pub fn push(&mut self, value: T) { + match self { + Self::Inline(array) => { + for slot in array.iter_mut() { + if slot.is_none() { + *slot = Some(value); + return; + } + } + + // Array is full, convert to heap + let mut vec = Vec::with_capacity(8); + for item in array.iter_mut() { + if let Some(v) = item.take() { + vec.push(v); + } + } + vec.push(value); + *self = Self::Heap(vec); + } + Self::Heap(vec) => { + vec.push(value); + } + } + } + + // Other methods... +} +``` + +This avoids heap allocations for small collections. + +## 8. Memory-Efficient Value Representation + +### 8.1 Value Enum Optimization + +We'll optimize the `Value` enum to reduce its size: + +```rust +pub enum Value { + Number(f64), + Text(Rc), + Bool(bool), + List(Rc>>), + Object(Rc, Value>>>), + Function(Rc), + NativeFunction(NativeFunction), + Container(Rc), + ContainerInstance(Rc>), + Interface(Rc), + Event(Rc), + Future(Rc>), + Null, +} +``` + +### 8.2 Small String Optimization + +For small strings, we'll use small string optimization: + +```rust +pub enum SmallString { + Inline([u8; 24], usize), // Buffer and length + Heap(Rc), +} + +impl SmallString { + pub fn new(s: &str) -> Self { + if s.len() <= 24 { + let mut buffer = [0u8; 24]; + buffer[..s.len()].copy_from_slice(s.as_bytes()); + Self::Inline(buffer, s.len()) + } else { + Self::Heap(Rc::from(s)) + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Inline(buffer, len) => { + std::str::from_utf8(&buffer[..*len]).unwrap() + } + Self::Heap(rc) => rc, + } + } +} +``` + +This avoids heap allocations for small strings. + +## 9. Lazy Evaluation + +### 9.1 Lazy Property Initialization + +Properties with default values will be initialized lazily: + +```rust +fn get_property_value( + &self, + instance: &ContainerInstanceValue, + property_name: &str, +) -> Result { + // Check if the property exists in the instance + if let Some(value) = instance.properties.get(property_name) { + return Ok(value.clone()); + } + + // Look up the property definition + let property = match self.resolve_property(&instance.container, property_name) { + Some(p) => p, + None => return Err(RuntimeError::new( + format!("Property '{}' not found", property_name), + /* line */, + /* column */, + )), + }; + + // If the property has a default value, initialize it + if let Some(default_value) = &property.default_value { + let value = self.evaluate_expression(default_value, /* environment */)?; + instance.properties.insert(property_name.to_string(), value.clone()); + Ok(value) + } else { + Err(RuntimeError::new( + format!("Property '{}' not initialized", property_name), + /* line */, + /* column */, + )) + } +} +``` + +This ensures that default values are only computed when needed. + +### 9.2 Lazy Interface Validation + +Interface validation will be performed lazily: + +```rust +fn validate_interface_implementation( + &self, + container: &ContainerValue, + interface: &InterfaceValue, +) -> Result<(), RuntimeError> { + // Check if validation has already been performed + if container.validated_interfaces.borrow().contains(&interface.name) { + return Ok(()); + } + + // Perform validation + for (method_name, signature) in &interface.required_actions { + // Check if the container has the method + let method = match self.resolve_method(container, method_name) { + Some(m) => m, + None => return Err(RuntimeError::new( + format!( + "Container '{}' does not implement required method '{}' from interface '{}'", + container.name, method_name, interface.name + ), + /* line */, + /* column */, + )), + }; + + // Check if the method signature matches + // ... + } + + // Mark interface as validated + container.validated_interfaces.borrow_mut().insert(interface.name.clone()); + + Ok(()) +} +``` + +This ensures that interface validation is only performed once per container-interface pair. + +## 10. Memory Cleanup + +### 10.1 Explicit Cleanup + +We'll implement explicit cleanup methods for container instances: + +```rust +impl ContainerInstanceValue { + pub fn cleanup(&mut self) { + // Clear properties + self.properties.clear(); + + // Clear event handlers + self.event_handlers.clear(); + + // Clear method cache + if let Some(container) = self.container.upgrade() { + container.method_cache.borrow_mut().clear(); + container.property_cache.borrow_mut().clear(); + } + } +} +``` + +### 10.2 Drop Implementation + +We'll implement the `Drop` trait for container instances to ensure proper cleanup: + +```rust +impl Drop for ContainerInstanceValue { + fn drop(&mut self) { + // Clear event handlers to break potential reference cycles + self.event_handlers.clear(); + + // Clear properties to break potential reference cycles + self.properties.clear(); + } +} +``` + +### 10.3 Weak Reference Handling + +We'll handle weak references carefully to avoid dereferencing dangling pointers: + +```rust +fn resolve_method( + &self, + container_weak: &Weak, + method_name: &str, +) -> Option> { + // Upgrade weak reference + let container = match container_weak.upgrade() { + Some(c) => c, + None => return None, // Container no longer exists + }; + + // Check own methods + if let Some(method) = container.methods.get(method_name) { + return Some(method.clone()); + } + + // Check parent container + if let Some(parent_weak) = &container.extends { + return self.resolve_method(parent_weak, method_name); + } + + None +} +``` + +## 11. Memory Profiling and Optimization + +### 11.1 Memory Profiling Tools + +We'll use memory profiling tools to identify memory usage patterns: + +- **DHAT**: Heap profiling via `dhat-heap` feature +- **Valgrind**: Memory leak detection +- **Custom Memory Tracker**: Track allocations and deallocations + +### 11.2 Memory Benchmarks + +We'll create benchmarks to measure memory usage: + +```rust +#[bench] +fn bench_container_creation(b: &mut Bencher) { + b.iter(|| { + let mut interpreter = Interpreter::new(); + let program = parse_program(r#" + create container Test: + property name as text + property value as number + + define action initialize with n and v: + set name to n + set value to v + end action + end container + + create new Test with "test" and 42 as instance + "#); + + interpreter.interpret(&program).unwrap(); + }); +} +``` + +### 11.3 Memory Optimization Workflow + +1. **Profile**: Use memory profiling tools to identify memory usage patterns +2. **Analyze**: Identify areas with high memory usage or leaks +3. **Optimize**: Apply memory optimization techniques +4. **Verify**: Re-profile to ensure optimizations are effective +5. **Repeat**: Continue until memory usage is acceptable + +## 12. Implementation Guidelines + +### 12.1 General Guidelines + +1. **Prefer Stack Allocation**: Use stack allocation when possible +2. **Minimize Cloning**: Avoid unnecessary cloning of values +3. **Use References**: Pass references instead of values when possible +4. **Reuse Objects**: Reuse objects instead of creating new ones +5. **Avoid Temporary Objects**: Minimize creation of temporary objects + +### 12.2 Container-Specific Guidelines + +1. **Lazy Property Initialization**: Initialize properties lazily +2. **Method Resolution Caching**: Cache resolved methods +3. **Property Resolution Caching**: Cache resolved properties +4. **String Interning**: Use string interning for property and method names +5. **Weak References**: Use weak references to avoid reference cycles + +### 12.3 Code Examples + +#### Minimizing Cloning + +```rust +// Bad: Clones value unnecessarily +fn get_property(&self, name: &str) -> Value { + self.properties.get(name).unwrap().clone() +} + +// Good: Returns reference to avoid cloning +fn get_property(&self, name: &str) -> &Value { + self.properties.get(name).unwrap() +} +``` + +#### Reusing Objects + +```rust +// Bad: Creates new HashMap for each call +fn get_property_values(&self) -> HashMap { + let mut result = HashMap::new(); + for (name, value) in &self.properties { + result.insert(name.clone(), value.clone()); + } + result +} + +// Good: Reuses provided HashMap +fn get_property_values(&self, result: &mut HashMap) { + for (name, value) in &self.properties { + result.insert(name.clone(), value.clone()); + } +} +``` + +#### Using Weak References + +```rust +// Bad: Creates reference cycle +struct Container { + parent: Option>, + children: Vec>, +} + +// Good: Avoids reference cycle +struct Container { + parent: Option>, + children: Vec>, +} +``` + +## 13. Conclusion + +By implementing these memory optimization strategies, we can ensure that the WFL container system is memory-efficient and avoids common memory-related issues such as reference cycles and excessive allocations. These strategies will be particularly important for applications that create many container instances or have deep inheritance hierarchies. + +The key strategies are: + +1. **Weak References**: Use weak references to avoid reference cycles +2. **Caching**: Cache resolved methods and properties to improve performance +3. **String Interning**: Use string interning to reduce memory usage +4. **Object Pooling**: Use object pools to reduce allocations +5. **Lazy Evaluation**: Initialize properties and validate interfaces lazily +6. **Efficient Data Structures**: Use capacity hints and small vector optimization +7. **Memory Cleanup**: Implement explicit cleanup and proper drop behavior + +By following these strategies, we can create a container system that is both powerful and memory-efficient. \ No newline at end of file diff --git a/plan.md b/plan.md index bf2ee6fe..9b40bbd8 100644 --- a/plan.md +++ b/plan.md @@ -1,50 +1,718 @@ -# Memory Usage Analysis for the `log_message` Action in WFL +# Container Implementation Plan for WFL -## Root Causes of Excessive Memory Usage +## 1. Overview -* **Closure Reference Cycle in Action Definition:** Defining the `log_message` action was creating a reference cycle between the function and its defining environment. In the WFL interpreter, user-defined actions (functions) were stored in the environment as an `Rc`, and the function captured a pointer to its parent environment. Originally, both were strong references, forming a cycle that the Rust garbage collector couldn’t free. This meant that even after the function’s use, its environment (and all enclosed data) stayed in memory, leaking resources and causing ballooning memory usage simply by defining the action. +Based on the documentation in `wfl-containers.md` and `wfl-actions.md`, we need to implement a complete container system that allows users to define custom data types with properties and behaviors. The implementation must: -* **Inefficient Parsing and AST Construction:** The WFL parser is consuming a lot of memory when parsing complex expressions and long scripts like `test.wfl`. Heap profiling revealed that the expression parsing routines (e.g. `parse_primary_expression` and `parse_binary_expression`) are responsible for a **disproportionate number of allocations**. Every literal, operator, or `with` concatenation in `log_message` creates new AST nodes and strings, often cloning data repeatedly. The parser grows vectors (like the list of statements or expression tokens) incrementally without sufficient pre-allocation, leading to many small reallocations. In a large script, these inefficiencies accumulate to extreme levels – for example, the full `nexus.wfl` test peaked at \~10.7 GB of memory and over 152 million allocations in profiling. In short, parsing the `log_message` action (and the script around it) generates excessive temporary objects and AST nodes, stressing memory. +- Support all features described in the documentation +- Minimize memory allocations +- Avoid reference cycles +- Maintain backward compatibility +- Follow WFL's natural language approach -* **Costly File Read-Modify-Write in `log_message`:** The implementation of `log_message` performs file I/O in a very memory-intensive way. Each time `log_message` is called, it **reads the entire `nexus.log` file content into memory**, appends a line, then **writes the entire content back** to the file. The interpreter’s I/O client opens the file and uses `read_to_string` to load it fully into a Rust `String`, and the write operation seeks to the start of the file, truncates it to zero, then writes out the new full content string. This approach causes **O(n)** memory use per call (proportional to the file size) and can lead to quadratic growth in work: as the log grows, each new message requires reading and reallocating a bigger and bigger string. If `log_message` runs in a loop, memory usage spikes dramatically because each iteration allocates a large buffer for the full log content and an equally large buffer for the updated content. Additionally, keeping the log file open (`logHandle`) while also opening it again for reading (as the code does) can cause errors or extra overhead (the interpreter had to clone file handles and manage them in a map). In summary, the current **read-modify-write pattern is very inefficient**, using excessive memory to repeatedly copy growing logfile text. +## 2. Architecture Design -* **Unbounded Loops or Recursive Calls:** While not a direct issue in `log_message` itself, the interpreter had to handle loops and recursion carefully to avoid runaway memory usage. In earlier versions, a lack of limits meant a long-running loop or deep recursion could consume memory without bound. For example, without safeguards a recursive action could keep pushing new call frames and never release them, or a loop could keep growing data structures. The developers identified that the debug call stack (`Vec`) could **balloon in size over long or deeply recursive runs** if frames weren’t popped or cleared promptly. Similarly, creating large data (huge lists, strings, etc.) in a loop or printing large debug traces could fill memory. The latest code mitigates this by capping loop iterations (e.g. a `count` loop is limited to 10,000 iterations by default) and by ensuring call frames are popped on function return. These measures prevent infinite or extremely large loops from exhausting memory. However, if such limits or pops failed (for instance, an uncapped loop or a recursion that doesn’t unwind), memory usage would spike due to continuously growing vectors or lingering allocations. +```mermaid +graph TD + A[AST Extensions] --> B[Parser Updates] + B --> C[Value System Extensions] + C --> D[Environment System Updates] + D --> E[Interpreter Implementation] + E --> F[Type Checker Integration] + + subgraph "Memory Management" + G[Weak References] + H[Reference Counting] + I[Memory Pooling] + end + + G --> C + H --> C + I --> C +``` -## Recommendations and Fixes +## 3. Detailed Implementation Plan -* **Break Reference Cycles in Functions:** The most critical fix is to eliminate the strong reference cycle between an action and its environment. This has been addressed by storing a **weak reference** to the defining environment in each `FunctionValue`. The `log_message` action now captures its environment via `Weak>` instead of `Rc`. In code, this means using `Rc::downgrade(&env)` when constructing a FunctionValue. With this change, defining an action no longer leaks memory – once the function and environment go out of scope, Rust can clean them up (the environment’s strong reference count isn’t kept artificially high by the function itself). **Ensure all similar back-references use `Weak`.** (The parent environment link was already using Weak, and now closures do as well.) This prevents the kind of leak that was triggered by merely defining `log_message` in the global scope. +### 3.1 AST Extensions -* **Optimize Parsing and AST Handling:** To address the heavy memory churn during parsing, the WFL compiler/interpreter should be optimized for fewer allocations and clones. Several strategies can help: +We need to extend the AST structure in `src/parser/ast.rs` to support container-related constructs: - * **Preallocate Vectors:** Reserve sufficient capacity for the AST `Program` and statement lists when parsing a file. The current parser grows vectors gradually; instead, use heuristics or file size to reserve memory up front (e.g. the parser already does a rough `tokens.count()/5` reserve, but this could be tuned to reduce reallocations). - * **Avoid Excessive Cloning:** When building AST nodes, avoid cloning strings or tokens more than necessary. The lexer already interns identifier substrings to reuse strings; similarly, the parser could reuse `String` allocations for repeated identifiers or literals. Ensure that intermediate results (like multi-word identifier assembly or the chain of `"with"` concatenations) don’t create lots of throwaway Strings. - * **Use Iterative Parsing or Pools:** The `parse_primary_expression` and `parse_binary_expression` routines should be reviewed for recursion or repeated passes. Converting deeply recursive parsing logic into an iterative loop can lower function-call overhead and allocations. Using an object pool or arena allocator for AST nodes could also cut down on per-node heap overhead – all nodes could be freed in one batch when the parse is done, rather than individually on the heap. - * These improvements would significantly reduce the \~152 million allocations seen in profiling. Fewer and more efficient allocations mean lower peak memory usage during script loading. In practice, after these changes, the `test.wfl` (Nexus) script should parse with a fraction of the memory it formerly required. +```rust +// Add to Statement enum +ContainerDefinition { + name: String, + extends: Option, + implements: Vec, + properties: Vec, + methods: Vec, // ActionDefinition statements + events: Vec, + static_properties: Vec, + static_methods: Vec, + line: usize, + column: usize, +}, -* **Streamline File I/O in `log_message`:** The logging approach should be reworked to avoid reading the entire file on each message append. There are a few possible fixes: +ContainerInstantiation { + container_type: String, + instance_name: String, + arguments: Vec, + property_initializers: Vec, + line: usize, + column: usize, +}, - * **Append Instead of Read/Rewrite:** Open the log file in **append mode** once, and simply write new log lines to the end as they come. This eliminates the need to ever read the full file content into memory. In Rust’s terms, one could open the file with `create(true), append(true)` options and on each `log_message` call just do a `write_all(new_line)` on the file handle. This approach uses constant memory per write (just the size of the new log entry, which is tiny) instead of scaling with the file size. It also avoids the costly truncate step. Currently, the code always seeks to start and truncates before writing, which is unnecessary if we only want to add new content. - * **Keep a Single File Handle:** Rather than repeatedly calling `open_file` for reading (and then closing), maintain the file handle opened at the start (`logHandle`) and use it for both reading and writing as needed. Ideally, **avoid opening the same file twice** concurrently. In the current implementation, `open file at "nexus.log" as logHandle` opens the file (read/write) and stores the handle ID in `logHandle`, but `log_message` then does another `open file at "nexus.log" and read content...` which tries to open a second handle for the same file. This was both memory-inefficient and logically problematic (the code had to clone file handles and would error if the file was already open). Instead, use the existing `logHandle`: for example, provide a way to **read from an already-open handle**. The interpreter could have a built-in action or syntax like `read content from logHandle` to get the current content if truly needed. In short, avoid duplicating open file handles and reuse them to save overhead. - * **On-demand or Buffered Reading:** If reading the file content is necessary (e.g. to support other operations), consider reading it in a streaming or buffered fashion. For example, to get the current content length or last lines, you don’t need to load the entire file into a `String`. Using a buffered reader to seek to the end or maintain a rolling buffer of the log could cut down memory usage. However, if we implement true append logging as above, we rarely need to read the log file at runtime at all (unless another part of the script needs the log content). - * Together, these changes mitigate the O(n²) memory growth pattern. By writing new log entries directly, `log_message` will use constant memory and time per call. This is a much more scalable design for logging, preventing the spike in memory consumption observed with the read-modify-write approach. +InterfaceDefinition { + name: String, + required_actions: Vec, + line: usize, + column: usize, +}, -* **Release Resources Promptly:** Ensure that resources and data structures are freed as soon as they are no longer needed. In the interpreter code: +EventDefinition { + name: String, + parameters: Vec, + line: usize, + column: usize, +}, - * **Close File Handles:** After finishing the test or when `logHandle` is no longer needed, explicitly close the file. The interpreter’s `IoClient` provides a `close_file` method to remove the handle from its internal map. Not closing means the `file_handles` map and the underlying OS handle stay alive, which is a minor memory and descriptor leak. In long-running sessions or when opening many files, this could accumulate. In our case, calling `close_file(logHandle)` at the end of the script (or when the program exits) will free that entry. - * **Function Call Stack Cleanup:** The call stack frames should continue to be popped on every function return – the current implementation does this properly with `.pop()` in both normal returns and error paths. We should also clear any lingering call-stack or debug info after execution. The interpreter already resets the call stack at the start of a new run; similarly, after the script completes, no debug trace should hold references to large data (for example, if an error occurred that captured local variables, ensure those are dropped or truncated). This defensive practice avoids residual memory usage from one run affecting the next. - * **Loop Variables Environment:** In long loops (like a `count` loop), the interpreter creates a child environment for loop variables. That environment should be dropped when the loop concludes. The current code uses a single `loop_env` for the whole loop and lets it go out of scope, which is good. We just need to be mindful that no references to it linger. Adopting Rust’s ownership idioms (so that when the loop finishes, the `Rc` is dropped if nothing else holds it) suffices. This ensures memory used for loop internals is freed promptly. - * **Limit Debug Output Sizes:** If the interpreter prints or logs large data structures (e.g. dumping the entire environment or a huge list for debugging), it should truncate this output. The memory investigation noted that printing enormous structures can allocate giant strings in memory. Imposing a reasonable size limit on debug strings or logging only summaries will prevent unexpectedly high memory usage during error reporting or verbose modes. +EventTrigger { + name: String, + arguments: Vec, + line: usize, + column: usize, +}, -* **Further Improvements (Long-Term):** - In addition to the immediate fixes above, a few architectural improvements can help WFL’s memory profile: +EventHandler { + event_source: Expression, + event_name: String, + handler_body: Vec, + line: usize, + column: usize, +}, +``` - * **Lazy Evaluation or Streaming**: For large data (like big text blobs or file content), consider lazy iteration or streaming rather than materializing everything in memory. For example, if WFL had to process a very large file, a streaming API would let you handle it in chunks. While not directly needed for `log_message`, this principle can keep memory flat for other use cases. - * **Bytecode or AST Execution Efficiency**: Since the roadmap mentions a future bytecode VM, that could also alleviate some memory overhead. Bytecode execution typically uses less memory than walking a high-level AST with many allocations. In the interim, auditing the interpreter’s runtime data structures (lists, objects, etc.) to ensure they don’t hold onto memory longer than necessary will help. For instance, if large lists are created and then no longer used, the garbage collector (Rust’s drop logic in this case) should free them – make sure no lingering `Rc` or global cache prevents that. - * **Testing and Profiling**: Finally, after applying the fixes for the identified issues, it’s recommended to **re-run memory profiling** (as was done with heaptrack) to verify that the leaks are gone and the usage is dramatically lower. Adding automated tests for memory (if possible) or at least running the integration tests under a memory checker can catch any regressions. This way, future changes (like new features or library additions) won’t reintroduce leaks or excessive memory usage. +Supporting structures: -By implementing these changes, the `log_message` action should no longer cause pathological memory behavior. Defining or calling the action will use only the necessary memory: the environment cycle is eliminated, parsing is more frugal with allocations, and file logging writes only incremental data instead of copying the entire log on each call. These fixes together will ensure that WFL’s interpreter runs more efficiently and can handle larger scripts without exhausting system memory. +```rust +pub struct PropertyDefinition { + pub name: String, + pub property_type: Option, + pub default_value: Option, + pub validation_rules: Vec, + pub visibility: Visibility, + pub is_static: bool, + pub line: usize, + pub column: usize, +} -**Sources:** +pub struct ValidationRule { + pub rule_type: ValidationRuleType, + pub parameters: Vec, + pub line: usize, + pub column: usize, +} -* WFL Memory Leak Analysis and Plan -* WFL Interpreter Source (environment, parser, I/O implementation) \ No newline at end of file +pub enum ValidationRuleType { + NotEmpty, + MinLength, + MaxLength, + ExactLength, + MinValue, + MaxValue, + Pattern, + Custom, +} + +pub enum Visibility { + Public, + Private, +} + +pub struct PropertyInitializer { + pub name: String, + pub value: Expression, + pub line: usize, + pub column: usize, +} + +pub struct ActionSignature { + pub name: String, + pub parameters: Vec, + pub return_type: Option, +} +``` + +### 3.2 Value System Extensions + +Extend the value system in `src/interpreter/value.rs` to support container-related values: + +```rust +// Add to Value enum +Container(Rc), +ContainerInstance(Rc>), +Interface(Rc), +Event(Rc), +``` + +Supporting structures with memory optimization: + +```rust +pub struct ContainerValue { + pub name: String, + pub extends: Option>, // Weak reference to avoid cycles + pub implements: Vec>, // Weak references to avoid cycles + pub properties: HashMap, + pub methods: HashMap>, + pub static_properties: HashMap, + pub static_methods: HashMap>, + pub events: HashMap>, + // Cache for inherited properties and methods to avoid repeated lookups + pub property_cache: RefCell, PropertyDefinition)>>, + pub method_cache: RefCell, Rc)>>, +} + +pub struct ContainerInstanceValue { + pub container: Weak, // Weak reference to avoid cycles + pub properties: HashMap, + pub env: Weak>, // Weak reference to avoid cycles + pub event_handlers: HashMap>>, +} + +pub struct InterfaceValue { + pub name: String, + pub required_actions: HashMap, +} + +pub struct EventValue { + pub name: String, + pub parameters: Vec, +} +``` + +## 4. Event System Implementation + +The event system is a critical part of the container implementation, enabling reactive programming patterns. This section provides a detailed explanation of how events, triggers, and handlers will be implemented. + +### 4.1 Event System Architecture + +```mermaid +graph TD + A[Event Definition] --> B[Event Registration] + B --> C[Event Triggering] + C --> D[Handler Execution] + + E[Container Definition] --> A + F[Container Instance] --> B + F --> C + G[Event Handler] --> D +``` + +### 4.2 Event Definition + +Events are defined within container definitions using the `event` keyword: + +```wfl +create container Button: + property label as text + + // Event definitions + event clicked + event hover start with x and y + event hover end +end container +``` + +In the AST, events are represented as `EventDefinition` structures: + +```rust +pub struct EventDefinition { + pub name: String, + pub parameters: Vec, + pub line: usize, + pub column: usize, +} +``` + +During parsing, the `parse_event_definition` function will extract the event name and any parameters: + +```rust +fn parse_event_definition(&mut self) -> Result { + self.expect_token(Token::KeywordEvent, "Expected 'event'")?; + + let name = self.parse_identifier()?; + + let mut parameters = Vec::new(); + + // Check if there are parameters (with keyword) + if self.match_token(Token::KeywordWith) { + parameters = self.parse_parameter_list()?; + } + + Ok(EventDefinition { + name, + parameters, + line: /* current line */, + column: /* current column */, + }) +} +``` + +### 4.3 Event Storage + +Events are stored in the `ContainerValue` structure: + +```rust +pub struct ContainerValue { + // Other fields... + pub events: HashMap>, +} + +pub struct EventValue { + pub name: String, + pub parameters: Vec, +} +``` + +When a container is defined, its events are registered in this map: + +```rust +async fn execute_container_definition(&self, /* params */) -> Result { + // Create container value + let container = Rc::new(ContainerValue { + // Other fields... + events: HashMap::new(), + }); + + // Register events + for event_def in events { + let event_value = Rc::new(EventValue { + name: event_def.name.clone(), + parameters: event_def.parameters.iter().map(|p| p.name.clone()).collect(), + }); + + container.events.insert(event_def.name.clone(), event_value); + } + + // Register container in environment + env.borrow_mut().define(&name, Value::Container(container)); + + Ok(Value::Null) +} +``` + +### 4.4 Event Handlers + +Event handlers are defined using the `on` keyword followed by an event source, event name, and handler body: + +```wfl +create new Button as submit_button: + set label to "Submit" +end create + +// Event handler registration +on submit_button clicked: + display "Button was clicked!" +end on +``` + +In the AST, event handlers are represented as `EventHandler` structures: + +```rust +pub struct EventHandler { + pub event_source: Expression, + pub event_name: String, + pub handler_body: Vec, + pub line: usize, + pub column: usize, +} +``` + +The parser will extract the event source, event name, and handler body: + +```rust +fn parse_event_handler(&mut self) -> Result { + self.expect_token(Token::KeywordOn, "Expected 'on'")?; + + let event_source = self.parse_expression()?; + let event_name = self.parse_identifier()?; + + self.expect_token(Token::Colon, "Expected ':' after event name")?; + + let handler_body = self.parse_block()?; + + self.expect_token(Token::KeywordEnd, "Expected 'end'")?; + self.expect_token(Token::KeywordOn, "Expected 'on' after 'end'")?; + + Ok(Statement::EventHandler { + event_source, + event_name, + handler_body, + line: /* current line */, + column: /* current column */, + }) +} +``` + +### 4.5 Event Handler Registration + +Event handlers are stored in the `ContainerInstanceValue` structure: + +```rust +pub struct ContainerInstanceValue { + // Other fields... + pub event_handlers: HashMap>>, +} +``` + +When an event handler is defined, it's registered with the container instance: + +```rust +async fn execute_event_handler( + &self, + event_source: &Expression, + event_name: &str, + handler_body: &[Statement], + env: Rc>, +) -> Result { + // Evaluate event source to get container instance + let source_value = self.evaluate_expression(event_source, Rc::clone(&env)).await?; + + match source_value { + Value::ContainerInstance(instance) => { + let mut instance_ref = instance.borrow_mut(); + + // Check if the event exists on the container + let container = match instance_ref.container.upgrade() { + Some(c) => c, + None => return Err(RuntimeError::new( + "Container no longer exists".to_string(), + /* line */, + /* column */, + )), + }; + + if !container.events.contains_key(event_name) { + return Err(RuntimeError::new( + format!("Event '{}' not found on container", event_name), + /* line */, + /* column */, + )); + } + + // Create function value for handler + let handler = Rc::new(FunctionValue { + name: Some(format!("{}_{}_handler", instance_ref.container.name, event_name)), + params: vec![], // Event parameters will be passed when triggered + body: handler_body.to_vec(), + env: Rc::downgrade(&env), + line: /* line */, + column: /* column */, + }); + + // Register handler + instance_ref.event_handlers + .entry(event_name.to_string()) + .or_insert_with(Vec::new) + .push(handler); + + Ok(Value::Null) + }, + _ => Err(RuntimeError::new( + "Event source must be a container instance".to_string(), + /* line */, + /* column */, + )), + } +} +``` + +### 4.6 Event Triggering + +Events are triggered using the `trigger` keyword: + +```wfl +define action click: + if is_enabled: + trigger clicked // Trigger event with no parameters + display "Button was clicked" + end if +end action + +define action hover at with x_pos and y_pos: + trigger hover start with x_pos and y_pos // Trigger event with parameters + display "Hovering at " with x_pos with "," with y_pos +end action +``` + +In the AST, event triggers are represented as `EventTrigger` structures: + +```rust +pub struct EventTrigger { + pub name: String, + pub arguments: Vec, + pub line: usize, + pub column: usize, +} +``` + +The parser will extract the event name and any arguments: + +```rust +fn parse_event_trigger(&mut self) -> Result { + self.expect_token(Token::KeywordTrigger, "Expected 'trigger'")?; + + let name = self.parse_identifier()?; + + let mut arguments = Vec::new(); + + // Check if there are arguments (with keyword) + if self.match_token(Token::KeywordWith) { + arguments = self.parse_argument_list()?; + } + + Ok(Statement::EventTrigger { + name, + arguments, + line: /* current line */, + column: /* current column */, + }) +} +``` + +### 4.7 Event Handler Execution + +When an event is triggered, all registered handlers for that event are executed: + +```rust +async fn execute_event_trigger( + &self, + name: &str, + arguments: &[Argument], + env: Rc>, +) -> Result { + // Get the current container instance from the environment + let this_value = match env.borrow().get("this") { + Some(Value::ContainerInstance(instance)) => Value::ContainerInstance(instance), + _ => return Err(RuntimeError::new( + "Cannot trigger event outside of container method".to_string(), + /* line */, + /* column */, + )), + }; + + match this_value { + Value::ContainerInstance(instance) => { + let instance_ref = instance.borrow(); + + // Check if the event exists on the container + let container = match instance_ref.container.upgrade() { + Some(c) => c, + None => return Err(RuntimeError::new( + "Container no longer exists".to_string(), + /* line */, + /* column */, + )), + }; + + if !container.events.contains_key(name) { + return Err(RuntimeError::new( + format!("Event '{}' not found on container", name), + /* line */, + /* column */, + )); + } + + // Evaluate arguments + let mut arg_values = Vec::new(); + for arg in arguments { + let value = self.evaluate_expression(&arg.value, Rc::clone(&env)).await?; + arg_values.push(value); + } + + // Get handlers for this event + if let Some(handlers) = instance_ref.event_handlers.get(name) { + // Execute each handler + for handler in handlers { + // Create a new environment for the handler + let handler_env = Environment::new(&env); + + // Add arguments to environment + let event_params = &container.events.get(name).unwrap().parameters; + for (i, param) in event_params.iter().enumerate() { + if i < arg_values.len() { + handler_env.borrow_mut().define(param, arg_values[i].clone()); + } else { + handler_env.borrow_mut().define(param, Value::Null); + } + } + + // Execute handler + self.execute_block(&handler.body, Rc::clone(&handler_env)).await?; + } + } + + Ok(Value::Null) + }, + _ => Err(RuntimeError::new( + "Cannot trigger event outside of container method".to_string(), + /* line */, + /* column */, + )), + } +} +``` + +### 4.8 Event Inheritance + +Events are inherited from parent containers: + +```wfl +create container UIElement: + event clicked + event focus + event blur +end container + +create container Button extends UIElement: + event hover // Adds a new event + // Inherits clicked, focus, and blur events +end container +``` + +When resolving events, the interpreter checks the container and all its ancestors: + +```rust +fn resolve_event(&self, container: &ContainerValue, name: &str) -> Option> { + // Check own events + if let Some(event) = container.events.get(name) { + return Some(event.clone()); + } + + // Check parent container + if let Some(parent_weak) = &container.extends { + if let Some(parent) = parent_weak.upgrade() { + return self.resolve_event(&parent, name); + } + } + + None +} +``` + +### 4.9 Memory Management for Events + +To avoid memory leaks and reference cycles, we'll use several strategies: + +1. **Weak References for Container References**: + ```rust + pub struct ContainerInstanceValue { + pub container: Weak, // Weak reference to avoid cycles + // Other fields... + } + ``` + +2. **Handler Cleanup**: + When a container instance is dropped, its event handlers should be cleaned up: + ```rust + impl Drop for ContainerInstanceValue { + fn drop(&mut self) { + // Clear event handlers to break potential reference cycles + self.event_handlers.clear(); + } + } + ``` + +3. **Event Handler Pool**: + For frequently triggered events, we can use an object pool for handler execution contexts: + ```rust + pub struct EventHandlerContext { + pub arguments: HashMap, + pub result: Option, + } + + pub struct EventHandlerPool { + pub available: Vec, + pub capacity: usize, + } + ``` + +### 4.10 Event System Error Handling + +Specific error types for the event system: + +```rust +// Add to ErrorKind enum +EventNotFound, +EventHandlerError, +InvalidEventArguments, +``` + +Error messages: +- "Event '{0}' not found on container '{1}'" +- "Error in event handler: {0}" +- "Invalid arguments for event '{0}': expected {1}, got {2}" + +## 5. Implementation Phases + +### 5.1 Phase 1: Basic Container Support +- AST extensions for container definitions and instantiation +- Parser updates for basic container syntax +- Value system extensions for containers +- Basic interpreter support for containers + +### 5.2 Phase 2: Properties and Methods +- Property definitions with validation +- Method definitions and calls +- Container instantiation +- Memory optimization foundations + +### 5.3 Phase 3: Inheritance and Interfaces +- Container inheritance +- Interface definitions and implementation +- Composition (containers as properties) +- Static members + +### 5.4 Phase 4: Event System +- Event definitions +- Event triggers +- Event handlers +- Event inheritance +- Memory optimizations for events + +### 5.5 Phase 5: Integration and Testing +- Type checker integration +- Error handling improvements +- Documentation updates +- Comprehensive testing + +## 6. Testing Strategy + +### 6.1 Unit Tests + +1. **Event System Tests**: + - Test event definition parsing + - Test event handler registration + - Test event triggering + - Test event inheritance + - Test event parameters + +2. **Memory Tests**: + - Test for memory leaks in event handlers + - Test for reference cycles in event system + - Test memory usage with many event handlers + +### 6.2 Integration Tests + +1. **Feature Tests**: + - Test events working with inheritance + - Test complex event chains + - Test event handlers accessing container state + +2. **Error Handling Tests**: + - Test triggering undefined events + - Test invalid event arguments + - Test event handler errors + +## 7. Conclusion + +This implementation plan provides a comprehensive approach to adding container functionality to WFL, with special focus on the event system. By following this plan, we can ensure that the implementation is robust, memory-efficient, and aligns with WFL's natural language approach to programming. \ No newline at end of file diff --git a/src/analyzer/mod.rs b/src/analyzer/mod.rs index b9c4afe2..a382dd92 100644 --- a/src/analyzer/mod.rs +++ b/src/analyzer/mod.rs @@ -171,11 +171,15 @@ impl Analyzer { name: "list".to_string(), param_type: Some(Type::List(Box::new(Type::Unknown))), default_value: None, + line: 0, + column: 0, }, Parameter { name: "value".to_string(), param_type: Some(Type::Unknown), default_value: None, + line: 0, + column: 0, }, ], return_type: Some(Type::Nothing), @@ -284,7 +288,7 @@ impl Analyzer { SymbolKind::Variable { mutable } => { if !mutable { self.errors.push(SemanticError::new( - format!("Cannot assign to immutable variable '{}'", name), + format!("Cannot assign to immutable variable '{name}'"), 0, // Need location info 0, )); @@ -292,7 +296,7 @@ impl Analyzer { } _ => { self.errors.push(SemanticError::new( - format!("'{}' is not a variable", name), + format!("'{name}' is not a variable"), 0, // Need location info 0, )); @@ -300,7 +304,7 @@ impl Analyzer { } } else { self.errors.push(SemanticError::new( - format!("Variable '{}' is not defined", name), + format!("Variable '{name}' is not defined"), 0, // Need location info 0, )); @@ -770,9 +774,11 @@ impl Analyzer { .iter() .enumerate() .map(|(i, t)| Parameter { - name: format!("param{}", i), + name: format!("param{i}"), param_type: Some(t.clone()), default_value: None, + line: 0, + column: 0, }) .collect(); @@ -827,7 +833,7 @@ impl Analyzer { if self.current_scope.resolve(name).is_none() { self.errors.push(SemanticError::new( - format!("Variable '{}' is not defined", name), + format!("Variable '{name}' is not defined"), *line, *column, )); @@ -860,7 +866,7 @@ impl Analyzer { } _ => { self.errors.push(SemanticError::new( - format!("'{}' is not a function", name), + format!("'{name}' is not a function"), *line, *column, )); @@ -960,6 +966,32 @@ impl Analyzer { } } Expression::Literal(_, _, _) => {} + // Container-related expressions + Expression::StaticMemberAccess { + container: _container, + member: _member, + .. + } => { + // For now, just a stub implementation + // This will be expanded later + } + Expression::MethodCall { + object, + method: _method, + arguments, + .. + } => { + // Analyze the object expression + self.analyze_expression(object); + + // Analyze the arguments + for arg in arguments { + self.analyze_expression(&arg.value); + } + } + Expression::PropertyAccess { object, .. } => { + self.analyze_expression(object); + } } } } @@ -1027,6 +1059,8 @@ mod tests { name: "name".to_string(), param_type: Some(Type::Text), default_value: None, + line: 0, + column: 0, }], body: vec![Statement::DisplayStatement { value: Expression::Variable("name".to_string(), 2, 5), @@ -1068,6 +1102,8 @@ mod tests { name: "name".to_string(), param_type: Some(Type::Text), default_value: None, + line: 0, + column: 0, }], body: vec![], return_type: None, diff --git a/src/analyzer/static_analyzer.rs b/src/analyzer/static_analyzer.rs index 3f3f33e5..7b54e9fd 100644 --- a/src/analyzer/static_analyzer.rs +++ b/src/analyzer/static_analyzer.rs @@ -252,7 +252,7 @@ impl StaticAnalyzer for Analyzer { if !usage.used { diagnostics.push(WflDiagnostic::new( Severity::Warning, - format!("Unused variable '{}'", name), + format!("Unused variable '{name}'"), Some("Consider removing this variable if it's not needed".to_string()), "ANALYZE-UNUSED".to_string(), file_id, @@ -347,7 +347,7 @@ impl StaticAnalyzer for Analyzer { if has_return && !all_paths_return { diagnostics.push(WflDiagnostic::new( Severity::Warning, - format!("Action '{}' has inconsistent return paths", name), + format!("Action '{name}' has inconsistent return paths"), Some("Ensure all code paths return a value".to_string()), "ANALYZE-RETURN".to_string(), file_id, @@ -776,6 +776,14 @@ impl Analyzer { Statement::HttpGetStatement { line, .. } => *line, Statement::HttpPostStatement { line, .. } => *line, Statement::PushStatement { line, .. } => *line, + // Container-related statements + Statement::ContainerDefinition { line, .. } => *line, + Statement::ContainerInstantiation { line, .. } => *line, + Statement::InterfaceDefinition { line, .. } => *line, + Statement::EventDefinition { line, .. } => *line, + Statement::EventTrigger { line, .. } => *line, + Statement::EventHandler { line, .. } => *line, + Statement::ParentMethodCall { line, .. } => *line, }, column: match statement { Statement::VariableDeclaration { column, .. } => *column, @@ -804,6 +812,14 @@ impl Analyzer { Statement::HttpGetStatement { column, .. } => *column, Statement::HttpPostStatement { column, .. } => *column, Statement::PushStatement { column, .. } => *column, + // Container-related statements + Statement::ContainerDefinition { column, .. } => *column, + Statement::ContainerInstantiation { column, .. } => *column, + Statement::InterfaceDefinition { column, .. } => *column, + Statement::EventDefinition { column, .. } => *column, + Statement::EventTrigger { column, .. } => *column, + Statement::EventHandler { column, .. } => *column, + Statement::ParentMethodCall { column, .. } => *column, }, }); stmt_nodes.push(node_idx); @@ -860,6 +876,14 @@ impl Analyzer { Statement::HttpGetStatement { line, .. } => *line, Statement::HttpPostStatement { line, .. } => *line, Statement::PushStatement { line, .. } => *line, + // Container-related statements + Statement::ContainerDefinition { line, .. } => *line, + Statement::ContainerInstantiation { line, .. } => *line, + Statement::InterfaceDefinition { line, .. } => *line, + Statement::EventDefinition { line, .. } => *line, + Statement::EventTrigger { line, .. } => *line, + Statement::EventHandler { line, .. } => *line, + Statement::ParentMethodCall { line, .. } => *line, }, column: match stmt { Statement::VariableDeclaration { column, .. } => *column, @@ -888,6 +912,14 @@ impl Analyzer { Statement::HttpGetStatement { column, .. } => *column, Statement::HttpPostStatement { column, .. } => *column, Statement::PushStatement { column, .. } => *column, + // Container-related statements + Statement::ContainerDefinition { column, .. } => *column, + Statement::ContainerInstantiation { column, .. } => *column, + Statement::InterfaceDefinition { column, .. } => *column, + Statement::EventDefinition { column, .. } => *column, + Statement::EventTrigger { column, .. } => *column, + Statement::EventHandler { column, .. } => *column, + Statement::ParentMethodCall { column, .. } => *column, }, }); then_nodes.push(then_node_idx); @@ -932,6 +964,14 @@ impl Analyzer { Statement::HttpGetStatement { line, .. } => *line, Statement::HttpPostStatement { line, .. } => *line, Statement::PushStatement { line, .. } => *line, + // Container-related statements + Statement::ContainerDefinition { line, .. } => *line, + Statement::ContainerInstantiation { line, .. } => *line, + Statement::InterfaceDefinition { line, .. } => *line, + Statement::EventDefinition { line, .. } => *line, + Statement::EventTrigger { line, .. } => *line, + Statement::EventHandler { line, .. } => *line, + Statement::ParentMethodCall { line, .. } => *line, }, column: match stmt { Statement::VariableDeclaration { column, .. } => *column, @@ -960,6 +1000,14 @@ impl Analyzer { Statement::HttpGetStatement { column, .. } => *column, Statement::HttpPostStatement { column, .. } => *column, Statement::PushStatement { column, .. } => *column, + // Container-related statements + Statement::ContainerDefinition { column, .. } => *column, + Statement::ContainerInstantiation { column, .. } => *column, + Statement::InterfaceDefinition { column, .. } => *column, + Statement::EventDefinition { column, .. } => *column, + Statement::EventTrigger { column, .. } => *column, + Statement::EventHandler { column, .. } => *column, + Statement::ParentMethodCall { column, .. } => *column, }, }); else_nodes.push(else_node_idx); @@ -1033,12 +1081,10 @@ impl Analyzer { diagnostics.push(WflDiagnostic::new( Severity::Warning, format!( - "Variable '{}' shadows another variable with the same name", - name + "Variable '{name}' shadows another variable with the same name" ), Some(format!( - "Previously defined at line {}, column {}", - def_line, def_col + "Previously defined at line {def_line}, column {def_col}" )), "ANALYZE-SHADOW".to_string(), file_id, @@ -1054,12 +1100,10 @@ impl Analyzer { diagnostics.push(WflDiagnostic::new( Severity::Warning, format!( - "Variable '{}' shadows another variable with the same name", - name + "Variable '{name}' shadows another variable with the same name" ), Some(format!( - "Previously defined at line {}, column {}", - def_line, def_col + "Previously defined at line {def_line}, column {def_col}" )), "ANALYZE-SHADOW".to_string(), file_id, diff --git a/src/config.rs b/src/config.rs index 85353f86..8848b6fa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -121,7 +121,7 @@ fn parse_config_text(config: &mut WflConfig, text: &str, file: &Path) { if let Some((key, rest)) = line.split_once('=') { let key = key.trim(); let value = rest.trim(); - log::debug!("Found config key: {}, value: {}", key, value); + log::debug!("Found config key: {key}, value: {value}"); match key { "timeout_seconds" => { diff --git a/src/debug_report.rs b/src/debug_report.rs index d1ee44b1..ddb4bfef 100644 --- a/src/debug_report.rs +++ b/src/debug_report.rs @@ -75,7 +75,7 @@ impl fmt::Debug for SafeDebug<'_> { if i > 0 { write!(f, ", ")?; } - write!(f, "{}: ", k)?; + write!(f, "{k}: ")?; SafeDebug { value: v, depth: self.depth - 1, @@ -151,7 +151,7 @@ fn generate_report_content( let mut report = String::new(); writeln!(&mut report, "=== WFL Debug Report ===").unwrap(); - writeln!(&mut report, "Script: {}", script_path).unwrap(); + writeln!(&mut report, "Script: {script_path}").unwrap(); writeln!( &mut report, "Time: {}", @@ -245,8 +245,8 @@ fn extract_function_body(report: &mut String, source: &str, func_name: &str) { let mut end_line = None; for (i, line) in lines.iter().enumerate() { - if line.contains(&format!("define action called {}", func_name)) - || line.contains(&format!("action called {}", func_name)) + if line.contains(&format!("define action called {func_name}")) + || line.contains(&format!("action called {func_name}")) { start_line = Some(i); } else if start_line.is_some() && line.contains("end action") { @@ -340,7 +340,7 @@ mod tests { list.borrow_mut().push(list_value.clone()); let safe_debug = SafeDebug::new(&list_value, 4); - let debug_output = format!("{:?}", safe_debug); + let debug_output = format!("{safe_debug:?}"); assert!(debug_output.contains("")); diff --git a/src/diagnostics/mod.rs b/src/diagnostics/mod.rs index 75792ee5..c2ab01f4 100644 --- a/src/diagnostics/mod.rs +++ b/src/diagnostics/mod.rs @@ -242,6 +242,42 @@ impl DiagnosticReporter { diag = diag.with_note( "Reserved keywords cannot be used as variable names. Choose a different name that is not a reserved word.", ); + } else if message.contains("Expected ':' after container name") { + diag = diag.with_note( + "Container definitions require a colon after the name. For example: `create container Person:`", + ); + } else if message.contains("Expected 'container' after 'create'") { + diag = diag.with_note( + "Use 'create container' to define a new container. For example: `create container Person:`", + ); + } else if message.contains("Expected identifier for container name") { + diag = diag.with_note( + "Container names must be valid identifiers. For example: `create container Person:`", + ); + } else if message.contains("Expected 'as' after container type") { + diag = diag.with_note( + "Container instantiation requires 'as' before the instance name. For example: `create new Person as alice:`", + ); + } else if message.contains("Expected 'new' after 'create'") { + diag = diag.with_note( + "Use 'create new' to instantiate a container. For example: `create new Person as alice:`", + ); + } else if message.contains("Expected identifier for container type") { + diag = diag.with_note( + "Specify a valid container type name. For example: `create new Person as alice:`", + ); + } else if message.contains("Expected property name after 'property'") { + diag = diag.with_note( + "Property definitions require a name. For example: `property name: Text`", + ); + } else if message.contains("Expected 'interface' after 'create'") { + diag = diag.with_note( + "Use 'create interface' to define a new interface. For example: `create interface Drawable:`", + ); + } else if message.contains("Expected identifier for interface name") { + diag = diag.with_note( + "Interface names must be valid identifiers. For example: `create interface Drawable:`", + ); } diag @@ -255,10 +291,7 @@ impl DiagnosticReporter { let mut message_text = error.message.clone(); if let (Some(expected), Some(found)) = (&error.expected, &error.found) { - message_text = format!( - "{} - Expected {} but found {}", - message_text, expected, found - ); + message_text = format!("{message_text} - Expected {expected} but found {found}"); } let start_offset = self @@ -511,7 +544,7 @@ impl DiagnosticReporter { let mut message = error_message.to_string(); if let Some(name) = pattern_name { - message = format!("Pattern '{}': {}", name, message); + message = format!("Pattern '{name}': {message}"); } if let Some(input) = input_preview { @@ -520,7 +553,7 @@ impl DiagnosticReporter { } else { input.to_string() }; - message = format!("{} (input: \"{}\")", message, preview); + message = format!("{message} (input: \"{preview}\")"); } if error_message.contains("invalid range") || error_message.contains("Invalid range") { diff --git a/src/fixer/mod.rs b/src/fixer/mod.rs index 5a23b542..d993b1ee 100644 --- a/src/fixer/mod.rs +++ b/src/fixer/mod.rs @@ -76,7 +76,7 @@ impl CodeFixer { Err(err) => { return Err(io::Error::new( io::ErrorKind::InvalidData, - format!("Failed to parse file: {:?}", err), + format!("Failed to parse file: {err:?}"), )); } }; @@ -243,7 +243,7 @@ impl CodeFixer { if let Some(param_type) = ¶m.param_type { output.push_str(" as "); - output.push_str(&format!("{:?}", param_type)); + output.push_str(&format!("{param_type:?}")); } if let Some(default_value) = ¶m.default_value { @@ -260,7 +260,7 @@ impl CodeFixer { if let Some(ret_type) = return_type { output.push_str(" returning "); - output.push_str(&format!("{:?}", ret_type)); + output.push_str(&format!("{ret_type:?}")); } output.push_str(":\n"); @@ -420,9 +420,56 @@ impl CodeFixer { output.push('\n'); summary.lines_reformatted += 1; } + Statement::ContainerDefinition { name, .. } => { + output.push_str(&indent); + output.push_str("create container "); + output.push_str(name); + output.push_str(":\n"); + output.push_str(&indent); + output + .push_str(" // TODO: Implement container property and method formatting\n"); + output.push_str(&indent); + output.push_str("end\n"); + summary.lines_reformatted += 1; + } + Statement::ContainerInstantiation { + container_type, + instance_name, + .. + } => { + output.push_str(&indent); + output.push_str("create new "); + output.push_str(container_type); + output.push_str(" as "); + output.push_str(instance_name); + output.push_str(":\n"); + output.push_str(&indent); + output.push_str(" // TODO: Implement property initializer formatting\n"); + summary.lines_reformatted += 1; + } + Statement::InterfaceDefinition { name, .. } => { + output.push_str(&indent); + output.push_str("create interface "); + output.push_str(name); + output.push_str(":\n"); + output.push_str(&indent); + output.push_str(" // TODO: Implement interface method formatting\n"); + output.push_str(&indent); + output.push_str("end\n"); + summary.lines_reformatted += 1; + } + Statement::EventDefinition { name, .. } => { + output.push_str(&indent); + output.push_str("event "); + output.push_str(name); + output.push_str(":\n"); + output.push_str(&indent); + output.push_str(" // TODO: Implement event parameter formatting\n"); + summary.lines_reformatted += 1; + } _ => { output.push_str(&indent); - output.push_str(&format!("{:?}\n", statement)); + output.push_str(&format!("{statement:?}\n")); summary.lines_reformatted += 1; } } @@ -597,7 +644,7 @@ impl CodeFixer { } #[allow(unreachable_patterns)] _ => { - output.push_str(&format!("{:?}", expression)); + output.push_str(&format!("{expression:?}")); } } } diff --git a/src/interpreter/environment.rs b/src/interpreter/environment.rs index 78b2a86a..ab9e9d73 100644 --- a/src/interpreter/environment.rs +++ b/src/interpreter/environment.rs @@ -56,7 +56,7 @@ impl Environment { Err("Parent environment no longer exists".to_string()) } } else { - Err(format!("Undefined variable '{}'", name)) + Err(format!("Undefined variable '{name}'")) } } diff --git a/src/interpreter/memory_tests.rs b/src/interpreter/memory_tests.rs index d97559bf..def4d023 100644 --- a/src/interpreter/memory_tests.rs +++ b/src/interpreter/memory_tests.rs @@ -73,6 +73,8 @@ mod tests { name: "message_text".to_string(), param_type: Some(Type::Text), default_value: None, + line: 0, + column: 0, }]; let body = vec![Statement::DisplayStatement { @@ -129,8 +131,7 @@ mod tests { let final_count = Rc::strong_count(&global_env); assert_eq!( initial_count, final_count, - "Reference count before ({}) and after ({}) should be the same", - initial_count, final_count + "Reference count before ({initial_count}) and after ({final_count}) should be the same" ); } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 564d84e1..de60dd39 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -12,7 +12,10 @@ use self::control_flow::ControlFlow; use self::environment::Environment; use self::error::{ErrorKind, RuntimeError}; -use self::value::{FunctionValue, Value}; +use self::value::{ + ContainerDefinitionValue, ContainerEventValue, ContainerInstanceValue, ContainerMethodValue, + EventHandler, FunctionValue, InterfaceDefinitionValue, Value, +}; use crate::debug_report::CallFrame; #[cfg(debug_assertions)] use crate::exec_block_enter; @@ -44,16 +47,16 @@ use std::time::{Duration, Instant}; #[cfg(debug_assertions)] fn stmt_type(stmt: &Statement) -> String { match stmt { - Statement::VariableDeclaration { name, .. } => format!("VariableDeclaration '{}'", name), - Statement::Assignment { name, .. } => format!("Assignment to '{}'", name), + Statement::VariableDeclaration { name, .. } => format!("VariableDeclaration '{name}'"), + Statement::Assignment { name, .. } => format!("Assignment to '{name}'"), Statement::IfStatement { .. } => "IfStatement".to_string(), Statement::SingleLineIf { .. } => "SingleLineIf".to_string(), Statement::DisplayStatement { .. } => "DisplayStatement".to_string(), - Statement::ActionDefinition { name, .. } => format!("ActionDefinition '{}'", name), + Statement::ActionDefinition { name, .. } => format!("ActionDefinition '{name}'"), Statement::ReturnStatement { .. } => "ReturnStatement".to_string(), Statement::ExpressionStatement { .. } => "ExpressionStatement".to_string(), Statement::CountLoop { .. } => "CountLoop".to_string(), - Statement::ForEachLoop { item_name, .. } => format!("ForEachLoop '{}'", item_name), + Statement::ForEachLoop { item_name, .. } => format!("ForEachLoop '{item_name}'"), Statement::WhileLoop { .. } => "WhileLoop".to_string(), Statement::RepeatUntilLoop { .. } => "RepeatUntilLoop".to_string(), Statement::RepeatWhileLoop { .. } => "RepeatWhileLoop".to_string(), @@ -62,22 +65,36 @@ fn stmt_type(stmt: &Statement) -> String { Statement::ContinueStatement { .. } => "ContinueStatement".to_string(), Statement::ExitStatement { .. } => "ExitStatement".to_string(), Statement::OpenFileStatement { variable_name, .. } => { - format!("OpenFileStatement '{}'", variable_name) + format!("OpenFileStatement '{variable_name}'") } Statement::ReadFileStatement { variable_name, .. } => { - format!("ReadFileStatement '{}'", variable_name) + format!("ReadFileStatement '{variable_name}'") } Statement::WriteFileStatement { .. } => "WriteFileStatement".to_string(), Statement::CloseFileStatement { .. } => "CloseFileStatement".to_string(), Statement::WaitForStatement { .. } => "WaitForStatement".to_string(), - Statement::TryStatement { error_name, .. } => format!("TryStatement '{}'", error_name), + Statement::TryStatement { error_name, .. } => format!("TryStatement '{error_name}'"), Statement::HttpGetStatement { variable_name, .. } => { - format!("HttpGetStatement '{}'", variable_name) + format!("HttpGetStatement '{variable_name}'") } Statement::HttpPostStatement { variable_name, .. } => { - format!("HttpPostStatement '{}'", variable_name) + format!("HttpPostStatement '{variable_name}'") } Statement::PushStatement { .. } => "PushStatement to list".to_string(), + // Container-related statements + Statement::ContainerDefinition { name, .. } => format!("ContainerDefinition '{name}'"), + Statement::ContainerInstantiation { + container_type, + instance_name, + .. + } => format!("ContainerInstantiation '{container_type}' as '{instance_name}'"), + Statement::InterfaceDefinition { name, .. } => format!("InterfaceDefinition '{name}'"), + Statement::EventDefinition { name, .. } => format!("EventDefinition '{name}'"), + Statement::EventTrigger { name, .. } => format!("EventTrigger '{name}'"), + Statement::EventHandler { event_name, .. } => format!("EventHandler '{event_name}'"), + Statement::ParentMethodCall { method_name, .. } => { + format!("ParentMethodCall '{method_name}'") + } } } @@ -85,23 +102,23 @@ fn stmt_type(stmt: &Statement) -> String { fn expr_type(expr: &Expression) -> String { match expr { Expression::Literal(lit, ..) => match lit { - Literal::String(s) => format!("StringLiteral \"{}\"", s), - Literal::Integer(i) => format!("IntegerLiteral {}", i), - Literal::Float(f) => format!("FloatLiteral {}", f), - Literal::Boolean(b) => format!("BooleanLiteral {}", b), + Literal::String(s) => format!("StringLiteral \"{s}\""), + Literal::Integer(i) => format!("IntegerLiteral {i}"), + Literal::Float(f) => format!("FloatLiteral {f}"), + Literal::Boolean(b) => format!("BooleanLiteral {b}"), Literal::Nothing => "NullLiteral".to_string(), - Literal::Pattern(p) => format!("PatternLiteral \"{}\"", p), + Literal::Pattern(p) => format!("PatternLiteral \"{p}\""), Literal::List(_) => "ListLiteral".to_string(), }, - Expression::Variable(name, ..) => format!("Variable '{}'", name), - Expression::BinaryOperation { operator, .. } => format!("BinaryOperation '{:?}'", operator), - Expression::UnaryOperation { operator, .. } => format!("UnaryOperation '{:?}'", operator), + Expression::Variable(name, ..) => format!("Variable '{name}'"), + Expression::BinaryOperation { operator, .. } => format!("BinaryOperation '{operator:?}'"), + Expression::UnaryOperation { operator, .. } => format!("UnaryOperation '{operator:?}'"), Expression::FunctionCall { function, .. } => match function.as_ref() { - Expression::Variable(name, ..) => format!("FunctionCall '{}'", name), + Expression::Variable(name, ..) => format!("FunctionCall '{name}'"), _ => "FunctionCall".to_string(), }, - Expression::ActionCall { name, .. } => format!("ActionCall '{}'", name), - Expression::MemberAccess { property, .. } => format!("MemberAccess '{}'", property), + Expression::ActionCall { name, .. } => format!("ActionCall '{name}'"), + Expression::MemberAccess { property, .. } => format!("MemberAccess '{property}'"), Expression::IndexAccess { .. } => "IndexAccess".to_string(), Expression::Concatenation { .. } => "Concatenation".to_string(), Expression::PatternMatch { .. } => "PatternMatch".to_string(), @@ -109,6 +126,12 @@ fn expr_type(expr: &Expression) -> String { Expression::PatternReplace { .. } => "PatternReplace".to_string(), Expression::PatternSplit { .. } => "PatternSplit".to_string(), Expression::AwaitExpression { .. } => "AwaitExpression".to_string(), + // Container-related expressions + Expression::StaticMemberAccess { + container, member, .. + } => format!("StaticMemberAccess '{container}' member '{member}'"), + Expression::MethodCall { method, .. } => format!("MethodCall '{method}'"), + Expression::PropertyAccess { property, .. } => format!("PropertyAccess '{property}'"), } } @@ -153,9 +176,9 @@ impl IoClient { match self.http_client.get(url).send().await { Ok(response) => match response.text().await { Ok(text) => Ok(text), - Err(e) => Err(format!("Failed to read response body: {}", e)), + Err(e) => Err(format!("Failed to read response body: {e}")), }, - Err(e) => Err(format!("Failed to send HTTP GET request: {}", e)), + Err(e) => Err(format!("Failed to send HTTP GET request: {e}")), } } @@ -170,9 +193,9 @@ impl IoClient { { Ok(response) => match response.text().await { Ok(text) => Ok(text), - Err(e) => Err(format!("Failed to read response body: {}", e)), + Err(e) => Err(format!("Failed to read response body: {e}")), }, - Err(e) => Err(format!("Failed to send HTTP POST request: {}", e)), + Err(e) => Err(format!("Failed to send HTTP POST request: {e}")), } } @@ -202,7 +225,7 @@ impl IoClient { file_handles.insert(handle_id.clone(), (path_buf, file)); Ok(handle_id) } - Err(e) => Err(format!("Failed to open file {}: {}", path, e)), + Err(e) => Err(format!("Failed to open file {path}: {e}")), } } @@ -221,13 +244,13 @@ impl IoClient { let _ = self.close_file(&new_handle).await; return result; } - Err(e) => return Err(format!("Invalid file handle or path: {}: {}", handle_id, e)), + Err(e) => return Err(format!("Invalid file handle or path: {handle_id}: {e}")), } } let mut file_clone = match file_handles.get_mut(handle_id).unwrap().1.try_clone().await { Ok(clone) => clone, - Err(e) => return Err(format!("Failed to clone file handle: {}", e)), + Err(e) => return Err(format!("Failed to clone file handle: {e}")), }; drop(file_handles); @@ -235,7 +258,7 @@ impl IoClient { let mut contents = String::new(); match AsyncReadExt::read_to_string(&mut file_clone, &mut contents).await { Ok(_) => Ok(contents), - Err(e) => Err(format!("Failed to read file: {}", e)), + Err(e) => Err(format!("Failed to read file: {e}")), } } @@ -254,13 +277,13 @@ impl IoClient { let _ = self.close_file(&new_handle).await; return result; } - Err(e) => return Err(format!("Invalid file handle or path: {}: {}", handle_id, e)), + Err(e) => return Err(format!("Invalid file handle or path: {handle_id}: {e}")), } } let mut file_clone = match file_handles.get_mut(handle_id).unwrap().1.try_clone().await { Ok(clone) => clone, - Err(e) => return Err(format!("Failed to clone file handle: {}", e)), + Err(e) => return Err(format!("Failed to clone file handle: {e}")), }; drop(file_handles); @@ -270,12 +293,12 @@ impl IoClient { Ok(_) => { match AsyncWriteExt::write_all(&mut file_clone, content.as_bytes()).await { Ok(_) => Ok(()), - Err(e) => Err(format!("Failed to write to file: {}", e)), + Err(e) => Err(format!("Failed to write to file: {e}")), } } - Err(e) => Err(format!("Failed to truncate file: {}", e)), + Err(e) => Err(format!("Failed to truncate file: {e}")), }, - Err(e) => Err(format!("Failed to seek in file: {}", e)), + Err(e) => Err(format!("Failed to seek in file: {e}")), } } @@ -299,15 +322,15 @@ impl IoClient { let (_, file) = match file_handles.get_mut(handle_id) { Some(entry) => entry, - None => return Err(format!("Invalid file handle: {}", handle_id)), + None => return Err(format!("Invalid file handle: {handle_id}")), }; match AsyncSeekExt::seek(file, std::io::SeekFrom::End(0)).await { Ok(_) => match AsyncWriteExt::write_all(file, content.as_bytes()).await { Ok(_) => Ok(()), - Err(e) => Err(format!("Failed to append to file: {}", e)), + Err(e) => Err(format!("Failed to append to file: {e}")), }, - Err(e) => Err(format!("Failed to seek to end of file: {}", e)), + Err(e) => Err(format!("Failed to seek to end of file: {e}")), } } } @@ -375,17 +398,17 @@ impl Interpreter { for (name, value) in current_env.values.iter() { if let Some(old_value) = env_before.get(name) { if !value.eq(old_value) { - changes.push(format!("{} = {} -> {}", name, old_value, value)); + changes.push(format!("{name} = {old_value} -> {value}")); } } else { - changes.push(format!("{} = {}", name, value)); + changes.push(format!("{name} = {value}")); } } if !changes.is_empty() { println!("Variables changed:"); for change in changes { - println!(" {}", change); + println!(" {change}"); } } @@ -399,14 +422,14 @@ impl Interpreter { } fn get_statement_text(stmt: &Statement) -> String { - format!("{:?}", stmt) + format!("{stmt:?}") } pub fn prompt_continue(&self) -> bool { loop { print!("continue (y/n)? "); if let Err(e) = io::stdout().flush() { - eprintln!("Error flushing stdout: {}", e); + eprintln!("Error flushing stdout: {e}"); } let mut input = String::new(); @@ -423,7 +446,7 @@ impl Interpreter { } } Err(e) => { - eprintln!("Error reading input: {}", e); + eprintln!("Error reading input: {e}"); return false; } } @@ -480,7 +503,7 @@ impl Interpreter { if i > 0 { print!(" "); } - print!("{}", arg); + print!("{arg}"); } println!(); Ok(Value::Null) @@ -559,7 +582,7 @@ impl Interpreter { // Store flags as individual variables with flag_ prefix for (key, value) in flags_map { - env.define(&format!("flag_{}", key), value); + env.define(&format!("flag_{key}"), value); } } @@ -720,6 +743,14 @@ impl Interpreter { Statement::HttpGetStatement { line, column, .. } => (*line, *column), Statement::HttpPostStatement { line, column, .. } => (*line, *column), Statement::PushStatement { line, column, .. } => (*line, *column), + // Container-related statements + Statement::ContainerDefinition { line, column, .. } => (*line, *column), + Statement::ContainerInstantiation { line, column, .. } => (*line, *column), + Statement::InterfaceDefinition { line, column, .. } => (*line, *column), + Statement::EventDefinition { line, column, .. } => (*line, *column), + Statement::EventTrigger { line, column, .. } => (*line, *column), + Statement::EventHandler { line, column, .. } => (*line, *column), + Statement::ParentMethodCall { line, column, .. } => (*line, *column), }; let result = match stmt { @@ -816,7 +847,7 @@ impl Interpreter { column: _column, } => { let value = self.evaluate_expression(value, Rc::clone(&env)).await?; - println!("{}", value); + println!("{value}"); Ok((Value::Null, ControlFlow::None)) } @@ -1008,10 +1039,7 @@ impl Interpreter { if iterations >= max_iterations { return Err(RuntimeError::new( - format!( - "Count loop exceeded maximum iterations ({})", - max_iterations - ), + format!("Count loop exceeded maximum iterations ({max_iterations})"), *line, *column, )); @@ -1292,7 +1320,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!("Expected string for file path, got {:?}", path_value), + format!("Expected string for file path, got {path_value:?}"), *line, *column, )); @@ -1319,10 +1347,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!( - "Expected string for file path or handle, got {:?}", - path_value - ), + format!("Expected string for file path or handle, got {path_value:?}"), *line, *column, )); @@ -1372,7 +1397,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!("Expected string for file handle, got {:?}", file_value), + format!("Expected string for file handle, got {file_value:?}"), *line, *column, )); @@ -1383,7 +1408,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!("Expected string for file content, got {:?}", content_value), + format!("Expected string for file content, got {content_value:?}"), *line, *column, )); @@ -1412,7 +1437,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!("Expected string for file handle, got {:?}", file_value), + format!("Expected string for file handle, got {file_value:?}"), *line, *column, )); @@ -1449,7 +1474,7 @@ impl Interpreter { } Err(RuntimeError::new( - format!("Timeout waiting for variable '{}'", var_name), + format!("Timeout waiting for variable '{var_name}'"), 0, 0, )) @@ -1469,10 +1494,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!( - "Expected string for file handle, got {:?}", - file_value - ), + format!("Expected string for file handle, got {file_value:?}"), *line, *column, )); @@ -1484,8 +1506,7 @@ impl Interpreter { _ => { return Err(RuntimeError::new( format!( - "Expected string for file content, got {:?}", - content_value + "Expected string for file content, got {content_value:?}" ), *line, *column, @@ -1534,8 +1555,7 @@ impl Interpreter { _ => { return Err(RuntimeError::new( format!( - "Expected string for file path or handle, got {:?}", - path_value + "Expected string for file path or handle, got {path_value:?}" ), *line, *column, @@ -1615,7 +1635,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!("Expected string for URL, got {:?}", url_val), + format!("Expected string for URL, got {url_val:?}"), *line, *column, )); @@ -1645,7 +1665,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!("Expected string for URL, got {:?}", url_val), + format!("Expected string for URL, got {url_val:?}"), *line, *column, )); @@ -1656,7 +1676,7 @@ impl Interpreter { Value::Text(s) => s.clone(), _ => { return Err(RuntimeError::new( - format!("Expected string for data, got {:?}", data_val), + format!("Expected string for data, got {data_val:?}"), *line, *column, )); @@ -1714,12 +1734,429 @@ impl Interpreter { Ok((Value::Null, ControlFlow::None)) } _ => Err(RuntimeError::new( - format!("Cannot push to non-list value: {:?}", list_val), + format!("Cannot push to non-list value: {list_val:?}"), *line, *column, )), } } + // Container-related statements + Statement::ContainerDefinition { + name, + extends, + implements, + properties, + methods, + events: _events, + static_properties: _static_properties, + static_methods: _static_methods, + line, + column, + } => { + // Create a new container definition + let mut container_properties = HashMap::new(); + let mut container_methods = HashMap::new(); + + for prop in properties { + let property_type_str = prop + .property_type + .as_ref() + .map(|ast_type| format!("{ast_type:?}")); + + let default_val = match &prop.default_value { + Some(expr) => { + // Evaluate the default expression to get a Value + (self._evaluate_expression(expr, env.clone()).await).ok() + } + None => None, + }; + + let value_prop = value::PropertyDefinition { + name: prop.name.clone(), + property_type: property_type_str, + default_value: default_val, + validation_rules: Vec::new(), + is_static: false, + is_public: true, + line: prop.line, + column: prop.column, + }; + container_properties.insert(prop.name.clone(), value_prop); + } + + for method in methods { + if let Statement::ActionDefinition { + name, + parameters, + body, + line, + column, + .. + } = method + { + let container_method = ContainerMethodValue { + name: name.clone(), + params: parameters.iter().map(|p| p.name.clone()).collect(), + body: body.clone(), + is_static: false, + is_public: true, + env: Rc::downgrade(&env), + line: *line, + column: *column, + }; + container_methods.insert(name.clone(), container_method); + } + } + + let container_def = ContainerDefinitionValue { + name: name.clone(), + extends: extends.clone(), + implements: implements.clone(), + properties: container_properties, + methods: container_methods, + events: HashMap::new(), // Future feature + static_properties: HashMap::new(), // Future feature + static_methods: HashMap::new(), // Future feature + line: *line, + column: *column, + }; + + // Create the container definition value + let container_value = Value::ContainerDefinition(Rc::new(container_def)); + + // Store the container definition in the environment + env.borrow_mut().define(name, container_value.clone()); + + Ok((container_value, ControlFlow::None)) + } + Statement::ContainerInstantiation { + container_type, + instance_name, + arguments, + property_initializers, + line, + column, + } => { + // Look up the container definition + let _container_def = match env.borrow().get(container_type) { + Some(Value::ContainerDefinition(def)) => def.clone(), + _ => { + return Err(RuntimeError::new( + format!("Container '{container_type}' not found"), + *line, + *column, + )); + } + }; + + // Create a new container instance with initial properties + let mut instance_properties = HashMap::new(); + + // Process property initializers + for initializer in property_initializers { + let init_value = self + ._evaluate_expression(&initializer.value, env.clone()) + .await?; + instance_properties.insert(initializer.name.clone(), init_value); + } + + let instance = ContainerInstanceValue { + container_type: container_type.clone(), + properties: instance_properties, + parent: None, // TODO: Handle inheritance + line: *line, + column: *column, + }; + + let instance_value = Value::ContainerInstance(Rc::new(RefCell::new(instance))); + + // Store the instance in the environment + env.borrow_mut() + .define(instance_name, instance_value.clone()); + + if !arguments.is_empty() { + // TODO: Call constructor method with arguments + } + + Ok((instance_value, ControlFlow::None)) + } + Statement::InterfaceDefinition { + name, + extends, + required_actions, + line: _line, + column: _column, + } => { + // Create a new interface definition + let mut interface_required_actions = HashMap::new(); + + for action in required_actions { + let value_action = value::ActionSignature { + name: action.name.clone(), + params: action.parameters.iter().map(|p| p.name.clone()).collect(), + line: action.line, + column: action.column, + }; + interface_required_actions.insert(action.name.clone(), value_action); + } + + let interface_def = InterfaceDefinitionValue { + name: name.clone(), + extends: extends.clone(), + required_actions: interface_required_actions, + line: *_line, + column: *_column, + }; + + let interface_value = Value::InterfaceDefinition(Rc::new(interface_def)); + + // Store the interface definition in the environment + env.borrow_mut().define(name, interface_value.clone()); + + Ok((interface_value, ControlFlow::None)) + } + Statement::EventDefinition { + name, + parameters, + line: _line, + column: _column, + } => { + // Create a new event definition + let event_def = ContainerEventValue { + name: name.clone(), + params: parameters.iter().map(|p| p.name.clone()).collect(), + handlers: Vec::new(), + line: *_line, + column: *_column, + }; + + let event_value = Value::ContainerEvent(Rc::new(event_def)); + + // Store the event definition in the environment + env.borrow_mut().define(name, event_value.clone()); + + Ok((event_value, ControlFlow::None)) + } + Statement::EventTrigger { + name, + arguments, + line: _line, + column: _column, + } => { + // Look up the event + let event = match env.borrow().get(name) { + Some(Value::ContainerEvent(event)) => event.clone(), + _ => { + return Err(RuntimeError::new( + format!("Event '{name}' not found"), + *_line, + *_column, + )); + } + }; + + // Evaluate the arguments + let mut arg_values = Vec::with_capacity(arguments.len()); + for arg in arguments { + let arg_val = self + .evaluate_expression(&arg.value, Rc::clone(&env)) + .await?; + arg_values.push(arg_val); + } + + // Execute all event handlers + for handler in &event.handlers { + // Create a new environment for the handler + let handler_env = Environment::new_child_env(&env); + + // Bind arguments to parameters + for (i, param_name) in event.params.iter().enumerate() { + if i < arg_values.len() { + handler_env + .borrow_mut() + .define(param_name, arg_values[i].clone()); + } else { + handler_env.borrow_mut().define(param_name, Value::Null); + } + } + + // Execute the handler + self.execute_block(&handler.body, handler_env).await?; + } + + Ok((Value::Null, ControlFlow::None)) + } + Statement::EventHandler { + event_source, + event_name, + handler_body, + line: _line, + column: _column, + } => { + // Evaluate the event source + let source_val = self + .evaluate_expression(event_source, Rc::clone(&env)) + .await?; + + // Check if the source is a container instance + if let Value::ContainerInstance(instance_rc) = &source_val { + let instance = instance_rc.borrow(); + let container_type = instance.container_type.clone(); + + // Look up the container definition + let container_def = match env.borrow().get(&container_type) { + Some(Value::ContainerDefinition(def)) => def.clone(), + _ => { + return Err(RuntimeError::new( + format!("Container '{container_type}' not found"), + *_line, + *_column, + )); + } + }; + + // Look up the event + if let Some(event) = container_def.events.get(event_name) { + // Create a new event handler + let handler = EventHandler { + body: handler_body.clone(), + env: Rc::downgrade(&env), + line: *_line, + column: *_column, + }; + + // Create a new event with the handler added + let mut handlers = event.handlers.clone(); + handlers.push(handler); + + // Create a new event value + let new_event = ContainerEventValue { + name: event.name.clone(), + params: event.params.clone(), + handlers, + line: event.line, + column: event.column, + }; + + // Store the updated event in the environment + let event_value = Value::ContainerEvent(Rc::new(new_event)); + env.borrow_mut().define(event_name, event_value.clone()); + + Ok((Value::Null, ControlFlow::None)) + } else { + Err(RuntimeError::new( + format!( + "Event '{event_name}' not found in container '{container_type}'" + ), + *_line, + *_column, + )) + } + } else { + Err(RuntimeError::new( + "Cannot add event handler to non-container value".to_string(), + *_line, + *_column, + )) + } + } + Statement::ParentMethodCall { + method_name, + arguments, + line, + column, + } => { + // Get the current container instance (this) + let this_val = match env.borrow().get("this") { + Some(val) => val.clone(), + None => { + return Err(RuntimeError::new( + "Parent method call can only be used inside a container method" + .to_string(), + *line, + *column, + )); + } + }; + + // Check if this is a container instance + if let Value::ContainerInstance(instance_rc) = &this_val { + let instance = instance_rc.borrow(); + + // Check if the instance has a parent + if let Some(parent_rc) = &instance.parent { + let parent = parent_rc.borrow(); + let parent_type = parent.container_type.clone(); + + // Look up the parent container definition + let parent_def = match env.borrow().get(&parent_type) { + Some(Value::ContainerDefinition(def)) => def.clone(), + _ => { + return Err(RuntimeError::new( + format!("Parent container '{parent_type}' not found"), + *line, + *column, + )); + } + }; + + // Look up the method in the parent + if let Some(method_val) = parent_def.methods.get(method_name) { + // Create a function value from the method + let function = FunctionValue { + name: Some(method_val.name.clone()), + params: method_val.params.clone(), + body: method_val.body.clone(), + env: method_val.env.clone(), + line: method_val.line, + column: method_val.column, + }; + + // Create a new environment for the method execution + let method_env = Environment::new_child_env(&env); + + // Add 'this' to the environment (the current instance, not the parent) + method_env.borrow_mut().define("this", this_val.clone()); + + // Evaluate the arguments + let mut arg_values = Vec::with_capacity(arguments.len()); + for arg in arguments { + let arg_val = self + .evaluate_expression(&arg.value, Rc::clone(&env)) + .await?; + arg_values.push(arg_val); + } + + // Call the function + let result = self + .call_function(&function, arg_values, *line, *column) + .await?; + + Ok((result, ControlFlow::None)) + } else { + Err(RuntimeError::new( + format!( + "Method '{method_name}' not found in parent container '{parent_type}'" + ), + *line, + *column, + )) + } + } else { + Err(RuntimeError::new( + "Cannot call parent method: no parent container".to_string(), + *line, + *column, + )) + } + } else { + Err(RuntimeError::new( + "Parent method call can only be used inside a container method".to_string(), + *line, + *column, + )) + } + } }; if self.step_mode { @@ -1794,6 +2231,127 @@ impl Interpreter { self.check_time()?; let result = match expr { + // Container-related expressions + &Expression::StaticMemberAccess { + ref container, + ref member, + line, + column, + } => { + // Look up the container definition + let container_def = match env.borrow().get(container) { + Some(Value::ContainerDefinition(def)) => def.clone(), + _ => { + return Err(RuntimeError::new( + format!("Container '{container}' not found"), + line, + column, + )); + } + }; + + // Look up the static member + if let Some(value) = container_def.static_properties.get(member) { + Ok(value.clone()) + } else if let Some(method) = container_def.static_methods.get(member) { + // Create a function value from the method + let function = FunctionValue { + name: Some(method.name.clone()), + params: method.params.clone(), + body: method.body.clone(), + env: method.env.clone(), + line: method.line, + column: method.column, + }; + + Ok(Value::Function(Rc::new(function))) + } else { + Err(RuntimeError::new( + format!("Static member '{member}' not found in container '{container}'"), + line, + column, + )) + } + } + + &Expression::MethodCall { + ref object, + ref method, + ref arguments, + line, + column, + } => { + // Evaluate the object + let object_val = self.evaluate_expression(object, Rc::clone(&env)).await?; + + // Clone the object value to avoid borrow issues + let object_val_clone = object_val.clone(); + + // Check if the object is a container instance + if let Value::ContainerInstance(instance_rc) = &object_val_clone { + let instance = instance_rc.borrow(); + let container_type = instance.container_type.clone(); + + // Look up the container definition + let container_def = match env.borrow().get(&container_type) { + Some(Value::ContainerDefinition(def)) => def.clone(), + _ => { + return Err(RuntimeError::new( + format!("Container '{container_type}' not found"), + line, + column, + )); + } + }; + + // Look up the method + if let Some(method_val) = container_def.methods.get(method) { + // Create a function value from the method + let function = FunctionValue { + name: Some(method_val.name.clone()), + params: method_val.params.clone(), + body: method_val.body.clone(), + env: method_val.env.clone(), + line: method_val.line, + column: method_val.column, + }; + + // Create a new environment for the method execution + let method_env = Environment::new_child_env(&env); + + // Add 'this' to the environment + method_env.borrow_mut().define("this", object_val.clone()); + + // Evaluate the arguments + let mut arg_values = Vec::with_capacity(arguments.len()); + for arg in arguments { + let arg_val = self + .evaluate_expression(&arg.value, Rc::clone(&env)) + .await?; + arg_values.push(arg_val); + } + + // Call the function + let result = self + .call_function(&function, arg_values, line, column) + .await?; + + Ok(result) + } else { + Err(RuntimeError::new( + format!("Method '{method}' not found in container '{container_type}'"), + line, + column, + )) + } + } else { + Err(RuntimeError::new( + format!("Cannot call method '{method}' on non-container value"), + line, + column, + )) + } + } &Expression::AwaitExpression { ref expression, line: _line, @@ -1813,7 +2371,7 @@ impl Interpreter { Literal::Pattern(ir_string) => match pattern::parse_ir(ir_string) { Ok(compiled_pattern) => Ok(Value::Pattern(Rc::new(compiled_pattern))), Err(err) => Err(RuntimeError::new( - format!("Pattern compilation error: {}", err), + format!("Pattern compilation error: {err}"), *_line, *_column, )), @@ -1836,8 +2394,7 @@ impl Interpreter { return Ok(Value::Number(count_value)); } else { println!( - "Warning: Using 'count' outside of a count loop context at line {}, column {}", - line, column + "Warning: Using 'count' outside of a count loop context at line {line}, column {column}" ); return Ok(Value::Number(0.0)); } @@ -1847,7 +2404,7 @@ impl Interpreter { Ok(value) } else { Err(RuntimeError::new( - format!("Undefined variable '{}'", name), + format!("Undefined variable '{name}'"), *line, *column, )) @@ -1954,7 +2511,7 @@ impl Interpreter { Value::Function(f) => { f.name.clone().unwrap_or_else(|| "".to_string()) } - _ => format!("{:?}", function_val), + _ => format!("{function_val:?}"), }; #[cfg(debug_assertions)] @@ -1967,7 +2524,7 @@ impl Interpreter { Value::NativeFunction(_, native_fn) => { native_fn(arg_values.clone()).map_err(|e| { RuntimeError::new( - format!("Error in native function: {}", e), + format!("Error in native function: {e}"), *line, *column, ) @@ -1995,7 +2552,7 @@ impl Interpreter { column, } => { let function_val = env.borrow().get(name).ok_or_else(|| { - RuntimeError::new(format!("Undefined action '{}'", name), *line, *column) + RuntimeError::new(format!("Undefined action '{name}'"), *line, *column) })?; match function_val { @@ -2027,7 +2584,7 @@ impl Interpreter { result } _ => Err(RuntimeError::new( - format!("'{}' is not callable", name), + format!("'{name}' is not callable"), *line, *column, )), @@ -2049,7 +2606,7 @@ impl Interpreter { Ok(value.clone()) } else { Err(RuntimeError::new( - format!("Object has no property '{}'", property), + format!("Object has no property '{property}'"), *line, *column, )) @@ -2101,7 +2658,7 @@ impl Interpreter { Ok(value.clone()) } else { Err(RuntimeError::new( - format!("Object has no key '{}'", key_str), + format!("Object has no key '{key_str}'"), *line, *column, )) @@ -2146,7 +2703,7 @@ impl Interpreter { } }; - let result = format!("{}{}", left_val, right_val); + let result = format!("{left_val}{right_val}"); Ok(Value::Text(Rc::from(result.as_str()))) } @@ -2205,6 +2762,33 @@ impl Interpreter { let args = vec![text_val, pattern_val]; crate::stdlib::pattern::native_pattern_split(args, *_line, *_column) } + Expression::PropertyAccess { + object, + property, + line, + column, + } => { + let obj_value = self.evaluate_expression(object, Rc::clone(&env)).await?; + match obj_value { + Value::ContainerInstance(instance) => { + let instance_ref = instance.borrow(); + if let Some(prop_value) = instance_ref.properties.get(property) { + Ok(prop_value.clone()) + } else { + Err(RuntimeError::new( + format!("Property '{property}' not found"), + *line, + *column, + )) + } + } + _ => Err(RuntimeError::new( + format!("Cannot access property '{property}' on non-container value"), + *line, + *column, + )), + } + } }; self.assert_invariants(); result @@ -2245,11 +2829,10 @@ impl Interpreter { } None => { exec_trace!("call_function - Failed to upgrade function environment"); - return Err(RuntimeError::with_kind( + return Err(RuntimeError::new( "Environment no longer exists".to_string(), line, column, - ErrorKind::EnvDropped, )); } }; @@ -2257,10 +2840,10 @@ impl Interpreter { let call_env = Environment::new_child_env(&func_env); exec_trace!("call_function - Created child environment for function call"); - for (i, (param, arg)) in func.params.iter().zip(args.clone()).enumerate() { + for (_i, (param, arg)) in func.params.iter().zip(args.clone()).enumerate() { exec_trace!( "call_function - Binding parameter {} '{}' to argument {:?}", - i, + _i, param, arg ); @@ -2345,15 +2928,15 @@ impl Interpreter { match (left, right) { (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)), (Value::Text(a), Value::Text(b)) => { - let result = format!("{}{}", a, b); + let result = format!("{a}{b}"); Ok(Value::Text(Rc::from(result.as_str()))) } (Value::Text(a), b) => { - let result = format!("{}{}", a, b); + let result = format!("{a}{b}"); Ok(Value::Text(Rc::from(result.as_str()))) } (a, Value::Text(b)) => { - let result = format!("{}{}", a, b); + let result = format!("{a}{b}"); Ok(Value::Text(Rc::from(result.as_str()))) } (a, b) => Err(RuntimeError::new( @@ -2423,7 +3006,7 @@ impl Interpreter { // Check if the result is valid (not NaN or infinite) if !result.is_finite() { return Err(RuntimeError::new( - format!("Division resulted in invalid number: {}", result), + format!("Division resulted in invalid number: {result}"), line, column, )); diff --git a/src/interpreter/tests.rs b/src/interpreter/tests.rs index 8a7f124a..f6957ec1 100644 --- a/src/interpreter/tests.rs +++ b/src/interpreter/tests.rs @@ -22,7 +22,7 @@ async fn test_literal_evaluation() { .unwrap(); match result { Value::Number(n) => assert_eq!(n, 42.0), - _ => panic!("Expected number, got {:?}", result), + _ => panic!("Expected number, got {result:?}"), } } else { panic!("Expected expression statement"); @@ -45,7 +45,7 @@ async fn test_variable_declaration_and_access() { match result { Value::Number(n) => assert_eq!(n, 42.0), - _ => panic!("Expected number, got {:?}", result), + _ => panic!("Expected number, got {result:?}"), } } @@ -60,7 +60,7 @@ async fn test_binary_operations() { let result = interpreter.interpret(&program).await.unwrap(); match result { Value::Number(n) => assert_eq!(n, 5.0), - _ => panic!("Expected number, got {:?}", result), + _ => panic!("Expected number, got {result:?}"), } let source = "2 is less than 3"; @@ -70,7 +70,7 @@ async fn test_binary_operations() { let result = interpreter.interpret(&program).await.unwrap(); match result { Value::Bool(b) => assert!(b), - _ => panic!("Expected boolean, got {:?}", result), + _ => panic!("Expected boolean, got {result:?}"), } } @@ -86,7 +86,7 @@ async fn test_if_statement() { match result { Value::Null => {} - _ => panic!("Expected null, got {:?}", result), + _ => panic!("Expected null, got {result:?}"), } } @@ -125,7 +125,7 @@ async fn test_count_loop_with_direct_access() { match result { Value::Null => {} - _ => panic!("Expected null, got {:?}", result), + _ => panic!("Expected null, got {result:?}"), } } #[tokio::test] @@ -168,8 +168,7 @@ async fn test_timeout_forever_loop() { assert!( elapsed.as_millis() <= 1100, - "Timeout took too long: {:?}", - elapsed + "Timeout took too long: {elapsed:?}" ); } diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 033cd308..7d67d34d 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -22,6 +22,14 @@ pub enum Value { DateTime(Rc), Pattern(Rc), Null, + Nothing, // Used for void returns + + // Container-related values + ContainerDefinition(Rc), + ContainerInstance(Rc>), + ContainerMethod(Rc), + ContainerEvent(Rc), + InterfaceDefinition(Rc), } pub type NativeFunction = fn(Vec) -> Result; @@ -44,6 +52,108 @@ pub struct FutureValue { pub column: usize, } +// Container-related structs +#[derive(Clone)] +pub struct ContainerDefinitionValue { + pub name: String, + pub extends: Option, + pub implements: Vec, + pub properties: HashMap, + pub methods: HashMap, + pub events: HashMap, + pub static_properties: HashMap, + pub static_methods: HashMap, + pub line: usize, + pub column: usize, +} + +#[derive(Clone)] +pub struct PropertyDefinition { + pub name: String, + pub property_type: Option, + pub default_value: Option, + pub validation_rules: Vec, + pub is_static: bool, + pub is_public: bool, + pub line: usize, + pub column: usize, +} + +#[derive(Clone)] +pub struct ValidationRule { + pub rule_type: ValidationRuleType, + pub parameters: Vec, + pub line: usize, + pub column: usize, +} + +#[derive(Clone, PartialEq)] +pub enum ValidationRuleType { + NotEmpty, + MinLength, + MaxLength, + ExactLength, + MinValue, + MaxValue, + Pattern, + Custom, +} + +#[derive(Clone)] +pub struct ContainerInstanceValue { + pub container_type: String, + pub properties: HashMap, + pub parent: Option>>, + pub line: usize, + pub column: usize, +} + +#[derive(Clone)] +pub struct ContainerMethodValue { + pub name: String, + pub params: Vec, + pub body: Vec, + pub is_static: bool, + pub is_public: bool, + pub env: Weak>, + pub line: usize, + pub column: usize, +} + +#[derive(Clone)] +pub struct ContainerEventValue { + pub name: String, + pub params: Vec, + pub handlers: Vec, + pub line: usize, + pub column: usize, +} + +#[derive(Clone)] +pub struct EventHandler { + pub body: Vec, + pub env: Weak>, + pub line: usize, + pub column: usize, +} + +#[derive(Clone)] +pub struct InterfaceDefinitionValue { + pub name: String, + pub extends: Vec, + pub required_actions: HashMap, + pub line: usize, + pub column: usize, +} + +#[derive(Clone)] +pub struct ActionSignature { + pub name: String, + pub params: Vec, + pub line: usize, + pub column: usize, +} + impl Value { pub fn type_name(&self) -> &'static str { match self { @@ -60,6 +170,12 @@ impl Value { Value::DateTime(_) => "DateTime", Value::Pattern(_) => "Pattern", Value::Null => "Null", + Value::Nothing => "Nothing", + Value::ContainerDefinition(_def) => "Container", + Value::ContainerInstance(_) => "ContainerInstance", + Value::ContainerMethod(_) => "ContainerMethod", + Value::ContainerEvent(_) => "ContainerEvent", + Value::InterfaceDefinition(_) => "Interface", } } @@ -75,6 +191,12 @@ impl Value { Value::Future(future) => future.borrow().completed, Value::Date(_) | Value::Time(_) | Value::DateTime(_) => true, Value::Pattern(_) => true, + Value::Nothing => false, + Value::ContainerDefinition(_) => true, + Value::ContainerInstance(_) => true, + Value::ContainerMethod(_) => true, + Value::ContainerEvent(_) => true, + Value::InterfaceDefinition(_) => true, } } } @@ -82,9 +204,10 @@ impl Value { impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Value::Number(n) => write!(f, "{}", n), - Value::Text(s) => write!(f, "\"{}\"", s), - Value::Bool(b) => write!(f, "{}", b), + Value::Number(n) => write!(f, "{n}"), + Value::Text(s) => write!(f, "\"{s}\""), + Value::Bool(b) => write!(f, "{b}"), + Value::Nothing => write!(f, "nothing"), Value::List(l) => { let values = l.borrow(); write!(f, "[")?; @@ -92,7 +215,7 @@ impl fmt::Debug for Value { if i > 0 { write!(f, ", ")?; } - write!(f, "{:?}", v)?; + write!(f, "{v:?}")?; } write!(f, "]") } @@ -103,7 +226,7 @@ impl fmt::Debug for Value { if i > 0 { write!(f, ", ")?; } - write!(f, "{}: {:?}", k, v)?; + write!(f, "{k}: {v:?}")?; } write!(f, "}}") } @@ -114,13 +237,21 @@ impl fmt::Debug for Value { func.name.as_ref().unwrap_or(&"anonymous".to_string()) ) } - Value::NativeFunction(name, _) => write!(f, "NativeFunction({})", name), + Value::NativeFunction(name, _) => write!(f, "NativeFunction({name})"), Value::Future(_) => write!(f, "[Future]"), - Value::Date(d) => write!(f, "Date({})", d), - Value::Time(t) => write!(f, "Time({})", t), - Value::DateTime(dt) => write!(f, "DateTime({})", dt), + Value::Date(d) => write!(f, "Date({d})"), + Value::Time(t) => write!(f, "Time({t})"), + Value::DateTime(dt) => write!(f, "DateTime({dt})"), Value::Pattern(_) => write!(f, "[Pattern]"), Value::Null => write!(f, "null"), + Value::ContainerDefinition(def) => write!(f, "", def.name), + Value::ContainerInstance(instance) => { + let instance = instance.borrow(); + write!(f, "", instance.container_type) + } + Value::ContainerMethod(method) => write!(f, "", method.name), + Value::ContainerEvent(event) => write!(f, "", event.name), + Value::InterfaceDefinition(interface) => write!(f, "", interface.name), } } } @@ -128,15 +259,16 @@ impl fmt::Debug for Value { impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Value::Number(n) => write!(f, "{}", n), - Value::Text(s) => write!(f, "{}", s), + Value::Number(n) => write!(f, "{n}"), + Value::Text(s) => write!(f, "{s}"), Value::Bool(b) => write!(f, "{}", if *b { "yes" } else { "no" }), + Value::Nothing => write!(f, "nothing"), Value::List(_) => write!(f, "[List]"), Value::Object(o) => { let map = o.borrow(); if map.len() == 1 { if let Some((_, value)) = map.iter().next() { - write!(f, "{}", value) + write!(f, "{value}") } else { write!(f, "[Object]") } @@ -148,7 +280,7 @@ impl fmt::Display for Value { if i > 0 { write!(f, ", ")?; } - write!(f, "{}: {}", k, v)?; + write!(f, "{k}: {v}")?; } write!(f, "}}") } @@ -160,13 +292,21 @@ impl fmt::Display for Value { func.name.as_ref().unwrap_or(&"anonymous".to_string()) ) } - Value::NativeFunction(name, _) => write!(f, "native {}", name), + Value::NativeFunction(name, _) => write!(f, "native {name}"), Value::Future(_) => write!(f, "[Future]"), Value::Date(d) => write!(f, "{}", d.format("%Y-%m-%d")), Value::Time(t) => write!(f, "{}", t.format("%H:%M:%S")), Value::DateTime(dt) => write!(f, "{}", dt.format("%Y-%m-%d %H:%M:%S")), Value::Pattern(_) => write!(f, "[Pattern]"), Value::Null => write!(f, "nothing"), + Value::ContainerDefinition(def) => write!(f, "container {}", def.name), + Value::ContainerInstance(instance) => { + let instance = instance.borrow(); + write!(f, "{} instance", instance.container_type) + } + Value::ContainerMethod(method) => write!(f, "method {}", method.name), + Value::ContainerEvent(event) => write!(f, "event {}", event.name), + Value::InterfaceDefinition(interface) => write!(f, "interface {}", interface.name), } } } @@ -181,6 +321,16 @@ impl PartialEq for Value { (Value::Time(a), Value::Time(b)) => a == b, (Value::DateTime(a), Value::DateTime(b)) => a == b, (Value::Null, Value::Null) => true, + (Value::Nothing, Value::Nothing) => true, + (Value::ContainerDefinition(a), Value::ContainerDefinition(b)) => a.name == b.name, + (Value::ContainerInstance(a), Value::ContainerInstance(b)) => { + let a = a.borrow(); + let b = b.borrow(); + a.container_type == b.container_type + } + (Value::ContainerMethod(a), Value::ContainerMethod(b)) => a.name == b.name, + (Value::ContainerEvent(a), Value::ContainerEvent(b)) => a.name == b.name, + (Value::InterfaceDefinition(a), Value::InterfaceDefinition(b)) => a.name == b.name, _ => false, } } diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 284f1642..1b617ad8 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -1,5 +1,8 @@ pub mod token; +#[cfg(test)] +mod tests; + use logos::Logos; use std::collections::HashMap; use std::sync::Mutex; @@ -192,182 +195,3 @@ pub fn lex_wfl_with_positions(input: &str) -> Vec { } tokens } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_line_ending_normalization() { - let input = "store x as 1\r\ndisplay x\r\n"; - let normalized = normalize_line_endings(input); - assert!(!normalized.contains('\r')); - assert_eq!(normalized.matches('\n').count(), 2); - } - - #[test] - fn test_multi_word_identifier() { - let input = r#" - store user name as "Alice" - display user name with " is logged in." - "#; - let tokens = lex_wfl(input); - assert_eq!( - tokens, - vec![ - Token::KeywordStore, - Token::Identifier("user name".to_string()), - Token::KeywordAs, - Token::StringLiteral("Alice".to_string()), - Token::KeywordDisplay, - Token::Identifier("user name".to_string()), - Token::KeywordWith, - Token::StringLiteral(" is logged in.".to_string()), - ] - ); - } - - #[test] - fn test_literals_and_comments() { - let input = r#" - create count as 42 - create is active as no // boolean false - display greeting as "Hello" - display greeting with " world!" - open file at "data.txt" as file handle - display file handle - "#; - let tokens = lex_wfl(input); - - println!("Tokens: {:?}", tokens); - - assert!(tokens.contains(&Token::KeywordCreate)); - assert!(tokens.contains(&Token::KeywordCount)); // "count" is recognized as a keyword - assert!(tokens.contains(&Token::KeywordAs)); - assert!(tokens.contains(&Token::IntLiteral(42))); - - assert!(tokens.contains(&Token::KeywordIs)); - assert!(tokens.contains(&Token::Identifier("active".to_string()))); - - assert!(tokens.contains(&Token::StringLiteral("Hello".to_string()))); - assert!(tokens.contains(&Token::KeywordWith)); - assert!(tokens.contains(&Token::StringLiteral(" world!".to_string()))); - - assert!(tokens.contains(&Token::KeywordOpen)); - assert!(tokens.contains(&Token::KeywordFile)); - assert!(tokens.contains(&Token::KeywordAt)); - assert!(tokens.contains(&Token::StringLiteral("data.txt".to_string()))); - assert!(tokens.contains(&Token::KeywordAs)); - assert!(tokens.contains(&Token::KeywordFile)); - assert!(tokens.contains(&Token::Identifier("handle".to_string()))); - } - - #[test] - fn test_hello_world_program() { - let input = r#" - - define action called main: - display "Hello, World!" - end action - "#; - let tokens = lex_wfl(input); - assert_eq!( - tokens, - vec![ - Token::KeywordDefine, - Token::KeywordAction, - Token::KeywordCalled, - Token::Identifier("main".to_string()), - Token::Colon, - Token::KeywordDisplay, - Token::StringLiteral("Hello, World!".to_string()), - Token::KeywordEnd, - Token::KeywordAction, - ] - ); - } - - #[test] - fn test_conditional_statement() { - let input = r#" - check if user name is "Alice": - display "Special greeting for Alice!" - otherwise: - display "Hello, " with user name - end check - "#; - let tokens = lex_wfl(input); - assert_eq!( - tokens, - vec![ - Token::KeywordCheck, - Token::KeywordIf, - Token::Identifier("user name".to_string()), - Token::KeywordIs, - Token::StringLiteral("Alice".to_string()), - Token::Colon, - Token::KeywordDisplay, - Token::StringLiteral("Special greeting for Alice!".to_string()), - Token::KeywordOtherwise, - Token::Colon, - Token::KeywordDisplay, - Token::StringLiteral("Hello, ".to_string()), - Token::KeywordWith, - Token::Identifier("user name".to_string()), - Token::KeywordEnd, - Token::KeywordCheck, - ] - ); - } - - #[test] - fn test_loop_statement() { - let input = r#" - count from 1 to 5: - display "Count: " with count - end count - "#; - let tokens = lex_wfl(input); - assert_eq!( - tokens, - vec![ - Token::KeywordCount, - Token::KeywordFrom, - Token::IntLiteral(1), - Token::KeywordTo, - Token::IntLiteral(5), - Token::Colon, - Token::KeywordDisplay, - Token::StringLiteral("Count: ".to_string()), - Token::KeywordWith, - Token::KeywordCount, - Token::KeywordEnd, - Token::KeywordCount, - ] - ); - } - - #[test] - fn test_identifiers_with_underscores() { - let input = r#" - store user_name as "Alice" - display user_name with " is logged in." - "#; - - let tokens = lex_wfl(input); - - assert_eq!( - tokens, - vec![ - Token::KeywordStore, - Token::Identifier("user_name".to_string()), - Token::KeywordAs, - Token::StringLiteral("Alice".to_string()), - Token::KeywordDisplay, - Token::Identifier("user_name".to_string()), - Token::KeywordWith, - Token::StringLiteral(" is logged in.".to_string()), - ] - ); - } -} diff --git a/src/lexer/tests.rs b/src/lexer/tests.rs new file mode 100644 index 00000000..89e6ae5f --- /dev/null +++ b/src/lexer/tests.rs @@ -0,0 +1,155 @@ +use super::*; + +#[test] +fn test_keyword_uniqueness() { + let keywords = vec![ + Token::KeywordStore, + Token::KeywordCreate, + Token::KeywordDisplay, + Token::KeywordCheck, + Token::KeywordIf, + Token::KeywordThen, + Token::KeywordOtherwise, + Token::KeywordEnd, + Token::KeywordFor, + Token::KeywordEach, + Token::KeywordIn, + Token::KeywordReversed, + Token::KeywordFrom, + Token::KeywordTo, + Token::KeywordBy, + Token::KeywordCount, + Token::KeywordRepeat, + Token::KeywordWhile, + Token::KeywordUntil, + Token::KeywordForever, + Token::KeywordAction, + Token::KeywordCalled, + Token::KeywordWith, + Token::KeywordNot, + Token::KeywordBreak, + Token::KeywordContinue, + Token::KeywordReturn, + Token::KeywordGive, + Token::KeywordBack, + Token::KeywordAs, + Token::KeywordAt, + Token::KeywordDefine, + Token::KeywordNeeds, + Token::KeywordChange, + Token::KeywordAnd, + Token::KeywordOr, + Token::KeywordPattern, + Token::KeywordRead, + Token::KeywordWait, + Token::KeywordSkip, + Token::KeywordThan, + Token::KeywordPush, + Token::KeywordContainer, + Token::KeywordProperty, + Token::KeywordExtends, + Token::KeywordImplements, + Token::KeywordInterface, + Token::KeywordRequires, + Token::KeywordEvent, + Token::KeywordTrigger, + Token::KeywordOn, + Token::KeywordStatic, + Token::KeywordPublic, + Token::KeywordPrivate, + Token::KeywordParent, + Token::KeywordNew, + Token::KeywordMust, + Token::KeywordDefaults, + ]; + + for keyword in &keywords { + assert!( + keyword.is_keyword(), + "Token {keyword:?} should be recognized as a keyword" + ); + } + + let non_keywords = vec![ + Token::Identifier("test".to_string()), + Token::StringLiteral("hello".to_string()), + Token::IntLiteral(42), + Token::FloatLiteral(2.5), + Token::BooleanLiteral(true), + Token::NothingLiteral, + Token::Colon, + Token::LeftParen, + Token::RightParen, + Token::LeftBracket, + Token::RightBracket, + Token::Newline, + Token::Error, + ]; + + for non_keyword in &non_keywords { + assert!( + !non_keyword.is_keyword(), + "Token {non_keyword:?} should not be recognized as a keyword" + ); + } +} + +#[test] +fn test_container_keywords_lexing() { + use logos::Logos; + + let test_cases = vec![ + ("container", Token::KeywordContainer), + ("property", Token::KeywordProperty), + ("extends", Token::KeywordExtends), + ("implements", Token::KeywordImplements), + ("interface", Token::KeywordInterface), + ("requires", Token::KeywordRequires), + ("event", Token::KeywordEvent), + ("trigger", Token::KeywordTrigger), + ("on", Token::KeywordOn), + ("static", Token::KeywordStatic), + ("public", Token::KeywordPublic), + ("private", Token::KeywordPrivate), + ("parent", Token::KeywordParent), + ("new", Token::KeywordNew), + ("must", Token::KeywordMust), + ("defaults", Token::KeywordDefaults), + ]; + + for (input, expected) in test_cases { + let mut lexer = Token::lexer(input); + let token = lexer + .next() + .unwrap_or_else(|| panic!("Failed to tokenize '{input}'")); + assert_eq!( + token, + Ok(expected.clone()), + "Input '{input}' should tokenize to {expected:?}" + ); + } +} + +#[test] +fn test_keyword_case_sensitivity() { + use logos::Logos; + + let test_cases = vec![ + ("CONTAINER", Token::Identifier("CONTAINER".to_string())), + ("Container", Token::Identifier("Container".to_string())), + ("PROPERTY", Token::Identifier("PROPERTY".to_string())), + ("Property", Token::Identifier("Property".to_string())), + ]; + + for (input, expected) in test_cases { + let mut lexer = Token::lexer(input); + let token = lexer + .next() + .unwrap_or_else(|| panic!("Failed to tokenize '{input}'")); + assert_eq!( + token, + Ok(expected.clone()), + "Input '{input}' should tokenize to {expected:?}" + ); + } +} diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 4d8a8644..f982200a 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -163,8 +163,6 @@ pub enum Token { KeywordStart, #[token("text")] KeywordText, - #[token("on")] - KeywordOn, #[token("push")] KeywordPush, #[token("above")] @@ -184,9 +182,55 @@ pub enum Token { #[token("than")] KeywordThan, + // Container-related keywords + #[token("container")] + KeywordContainer, + #[token("property")] + KeywordProperty, + #[token("extends")] + KeywordExtends, + #[token("implements")] + KeywordImplements, + #[token("interface")] + KeywordInterface, + #[token("requires")] + KeywordRequires, + #[token("event")] + KeywordEvent, + #[token("trigger")] + KeywordTrigger, + #[token("on")] + KeywordOn, + #[token("static")] + KeywordStatic, + #[token("public")] + KeywordPublic, + #[token("private")] + KeywordPrivate, + #[token("parent")] + KeywordParent, + #[token("new")] + KeywordNew, + #[token("must")] + KeywordMust, + #[token("defaults")] + KeywordDefaults, + #[token(":")] Colon, + #[token(",")] + Comma, + + #[token("+")] + Plus, + + #[token(".")] + Dot, + + #[token("=")] + Equals, + #[token("[")] LeftBracket, @@ -302,6 +346,22 @@ impl Token { | Token::KeywordSkip | Token::KeywordThan | Token::KeywordPush + | Token::KeywordContainer + | Token::KeywordProperty + | Token::KeywordExtends + | Token::KeywordImplements + | Token::KeywordInterface + | Token::KeywordRequires + | Token::KeywordEvent + | Token::KeywordTrigger + | Token::KeywordOn + | Token::KeywordStatic + | Token::KeywordPublic + | Token::KeywordPrivate + | Token::KeywordParent + | Token::KeywordNew + | Token::KeywordMust + | Token::KeywordDefaults ) } } diff --git a/src/lib.rs b/src/lib.rs index 5d22e309..1506a4d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,14 +37,14 @@ pub fn init_loggers(log_path: &Path, script_dir: &Path) { // Initialize the main logger if config.logging_enabled { if let Err(e) = logging::init_logger(config.log_level, log_path) { - eprintln!("Failed to initialize logger: {}", e); + eprintln!("Failed to initialize logger: {e}"); } } // Initialize the execution logger if enabled if config.execution_logging { if let Err(e) = logging::init_execution_logger(&config, log_path) { - eprintln!("Failed to initialize execution logger: {}", e); + eprintln!("Failed to initialize execution logger: {e}"); } } diff --git a/src/linter/mod.rs b/src/linter/mod.rs index 64025b9c..10e5f8ee 100644 --- a/src/linter/mod.rs +++ b/src/linter/mod.rs @@ -113,8 +113,8 @@ impl LintRule for NamingConventionRule { let snake_case_name = to_snake_case(name); let diagnostic = WflDiagnostic::new( Severity::Warning, - format!("Variable name '{}' should be snake_case", name), - Some(format!("Rename to '{}'", snake_case_name)), + format!("Variable name '{name}' should be snake_case"), + Some(format!("Rename to '{snake_case_name}'")), "LINT-NAME".to_string(), file_id, *line, @@ -131,8 +131,8 @@ impl LintRule for NamingConventionRule { let snake_case_name = to_snake_case(name); let diagnostic = WflDiagnostic::new( Severity::Warning, - format!("Action name '{}' should be snake_case", name), - Some(format!("Rename to '{}'", snake_case_name)), + format!("Action name '{name}' should be snake_case"), + Some(format!("Rename to '{snake_case_name}'")), "LINT-NAME".to_string(), file_id, *line, @@ -207,10 +207,9 @@ impl LintRule for IndentationRule { diagnostics.push(WflDiagnostic::new( Severity::Warning, format!( - "Line should be indented with {} spaces, found {}", - expected_indent, indent_spaces + "Line should be indented with {expected_indent} spaces, found {indent_spaces}" ), - Some(format!("Adjust indentation to {} spaces", expected_indent)), + Some(format!("Adjust indentation to {expected_indent} spaces")), "LINT-INDENT".to_string(), file_id, line_num, @@ -298,8 +297,8 @@ impl LintRule for KeywordCasingRule { let line_col = line_col_from_pos(source, pos); diagnostics.push(WflDiagnostic::new( Severity::Warning, - format!("Keyword '{}' should be lowercase", uppercase_keyword), - Some(format!("Change to '{}'", keyword)), + format!("Keyword '{uppercase_keyword}' should be lowercase"), + Some(format!("Change to '{keyword}'")), "LINT-KEYWORD".to_string(), file_id, line_col.0, @@ -312,8 +311,8 @@ impl LintRule for KeywordCasingRule { let line_col = line_col_from_pos(source, pos); diagnostics.push(WflDiagnostic::new( Severity::Warning, - format!("Keyword '{}' should be lowercase", mixed_case_keyword), - Some(format!("Change to '{}'", keyword)), + format!("Keyword '{mixed_case_keyword}' should be lowercase"), + Some(format!("Change to '{keyword}'")), "LINT-KEYWORD".to_string(), file_id, line_col.0, @@ -405,8 +404,8 @@ impl LintRule for LineLengthRule { if line.len() > max_length { diagnostics.push(WflDiagnostic::new( Severity::Warning, - format!("Line exceeds maximum length of {} characters", max_length), - Some(format!("Shorten line to {} characters or less", max_length)), + format!("Line exceeds maximum length of {max_length} characters"), + Some(format!("Shorten line to {max_length} characters or less")), "LINT-LENGTH".to_string(), file_id, line_num, @@ -466,7 +465,7 @@ fn check_nesting_depth( | Statement::CountLoop { line, column, .. } => { diagnostics.push(WflDiagnostic::new( Severity::Warning, - format!("Nesting depth exceeds maximum of {}", max_depth), + format!("Nesting depth exceeds maximum of {max_depth}"), Some("Refactor to reduce nesting".to_string()), "LINT-COMPLEX".to_string(), file_id, diff --git a/src/logging.rs b/src/logging.rs index edc669b8..6fa2bc32 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -126,7 +126,7 @@ pub fn init_execution_logger( let exec_file_name = if let Some(pos) = file_name.rfind('.') { format!("{}_exec{}", &file_name[..pos], &file_name[pos..]) } else { - format!("{}_exec.log", file_name) + format!("{file_name}_exec.log") }; let exec_log_path = base_log_path.with_file_name(exec_file_name); diff --git a/src/main.rs b/src/main.rs index 606c69b9..19a0c854 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,7 +64,7 @@ async fn main() -> io::Result<()> { if args.len() == 1 { if let Err(e) = repl::run_repl().await { - eprintln!("REPL error: {}", e); + eprintln!("REPL error: {e}"); } return Ok(()); } @@ -285,7 +285,7 @@ async fn main() -> io::Result<()> { } } Err(e) => { - eprintln!("Error checking configuration: {}", e); + eprintln!("Error checking configuration: {e}"); process::exit(2); } } @@ -301,7 +301,7 @@ async fn main() -> io::Result<()> { } } Err(e) => { - eprintln!("Error fixing configuration: {}", e); + eprintln!("Error fixing configuration: {e}"); process::exit(2); } } @@ -320,12 +320,12 @@ async fn main() -> io::Result<()> { // Ensure the file exists if !path.exists() { // Create an empty file if it doesn't exist - println!("File doesn't exist. Creating empty file: {}", file_path); + println!("File doesn't exist. Creating empty file: {file_path}"); fs::write(&file_path, "")?; } // Use the system's default program to open the file - println!("Opening file in default editor: {}", file_path); + println!("Opening file in default editor: {file_path}"); #[cfg(target_os = "windows")] { @@ -368,11 +368,11 @@ async fn main() -> io::Result<()> { // Handle lexer dump if lex_dump { - let lex_output_path = format!("{}.lex.txt", file_path); + let lex_output_path = format!("{file_path}.lex.txt"); // Format lexer output let mut lex_output = String::new(); - lex_output.push_str(&format!("Lexer output for: {}\n", file_path)); + lex_output.push_str(&format!("Lexer output for: {file_path}\n")); lex_output.push_str("==============================================\n\n"); for (i, token) in tokens_with_pos.iter().enumerate() { @@ -384,23 +384,23 @@ async fn main() -> io::Result<()> { // Write to file if let Err(e) = write_to_file(&lex_output_path, &lex_output) { - eprintln!("Error writing lexer output to {}: {}", lex_output_path, e); + eprintln!("Error writing lexer output to {lex_output_path}: {e}"); process::exit(1); } - println!("Lexer output written to: {}", lex_output_path); + println!("Lexer output written to: {lex_output_path}"); } // Handle AST dump if ast_dump { - let ast_output_path = format!("{}.ast.txt", file_path); + let ast_output_path = format!("{file_path}.ast.txt"); // Parse tokens into AST match Parser::new(&tokens_with_pos).parse() { Ok(program) => { // Format AST output let mut ast_output = String::new(); - ast_output.push_str(&format!("AST output for: {}\n", file_path)); + ast_output.push_str(&format!("AST output for: {file_path}\n")); ast_output.push_str("==============================================\n\n"); ast_output.push_str(&format!( "Program with {} statements:\n\n", @@ -414,11 +414,11 @@ async fn main() -> io::Result<()> { // Write to file if let Err(e) = write_to_file(&ast_output_path, &ast_output) { - eprintln!("Error writing AST output to {}: {}", ast_output_path, e); + eprintln!("Error writing AST output to {ast_output_path}: {e}"); process::exit(1); } - println!("AST output written to: {}", ast_output_path); + println!("AST output written to: {ast_output_path}"); } Err(errors) => { eprintln!("Cannot generate AST dump due to parse errors:"); @@ -429,8 +429,8 @@ async fn main() -> io::Result<()> { for error in errors { let diagnostic = reporter.convert_parse_error(file_id, &error); if let Err(e) = reporter.report_diagnostic(file_id, &diagnostic) { - eprintln!("Error displaying diagnostic: {}", e); - eprintln!("Error: {}", error); + eprintln!("Error displaying diagnostic: {e}"); + eprintln!("Error: {error}"); } } @@ -448,7 +448,7 @@ async fn main() -> io::Result<()> { print!("continue (y/n)? "); if let Err(e) = io::stdout().flush() { - eprintln!("Error flushing stdout: {}", e); + eprintln!("Error flushing stdout: {e}"); } let mut input_line = String::new(); @@ -460,7 +460,7 @@ async fn main() -> io::Result<()> { } } Err(e) => { - eprintln!("Error reading input: {}", e); + eprintln!("Error reading input: {e}"); process::exit(1); } } @@ -488,7 +488,7 @@ async fn main() -> io::Result<()> { } else if fix_diff { println!("{}", fixer.diff(&input, &fixed_code)); } else { - println!("Fixed code:\n{}", fixed_code); + println!("Fixed code:\n{fixed_code}"); } process::exit(0); } else if !diagnostics.is_empty() { @@ -499,7 +499,7 @@ async fn main() -> io::Result<()> { for diagnostic in diagnostics { if let Err(e) = reporter.report_diagnostic(file_id, &diagnostic) { - eprintln!("Error displaying diagnostic: {}", e); + eprintln!("Error displaying diagnostic: {e}"); eprintln!("{}", diagnostic.message); } } @@ -519,8 +519,8 @@ async fn main() -> io::Result<()> { for error in errors { let diagnostic = reporter.convert_parse_error(file_id, &error); if let Err(e) = reporter.report_diagnostic(file_id, &diagnostic) { - eprintln!("Error displaying diagnostic: {}", e); - eprintln!("Error: {}", error); + eprintln!("Error displaying diagnostic: {e}"); + eprintln!("Error: {error}"); } } @@ -545,7 +545,7 @@ async fn main() -> io::Result<()> { for diagnostic in diagnostics { if let Err(e) = reporter.report_diagnostic(file_id, &diagnostic) { - eprintln!("Error displaying diagnostic: {}", e); + eprintln!("Error displaying diagnostic: {e}"); eprintln!("{}", diagnostic.message); } } @@ -565,8 +565,8 @@ async fn main() -> io::Result<()> { for error in errors { let diagnostic = reporter.convert_parse_error(file_id, &error); if let Err(e) = reporter.report_diagnostic(file_id, &diagnostic) { - eprintln!("Error displaying diagnostic: {}", e); - eprintln!("Error: {}", error); + eprintln!("Error displaying diagnostic: {e}"); + eprintln!("Error: {error}"); } } @@ -598,7 +598,7 @@ async fn main() -> io::Result<()> { process::exit(0); } Err(e) => { - eprintln!("Error fixing code: {}", e); + eprintln!("Error fixing code: {e}"); process::exit(1); } } @@ -612,8 +612,8 @@ async fn main() -> io::Result<()> { for error in errors { let diagnostic = reporter.convert_parse_error(file_id, &error); if let Err(e) = reporter.report_diagnostic(file_id, &diagnostic) { - eprintln!("Error displaying diagnostic: {}", e); - eprintln!("Error: {}", error); + eprintln!("Error displaying diagnostic: {e}"); + eprintln!("Error: {error}"); } } @@ -749,7 +749,7 @@ async fn main() -> io::Result<()> { Ok(report_path) => { let report_msg = format!("Debug report created: {}", report_path.display()); - eprintln!("{}", report_msg); + eprintln!("{report_msg}"); if config.logging_enabled { info!("{}", report_msg); @@ -768,8 +768,8 @@ async fn main() -> io::Result<()> { for error in errors { let diagnostic = reporter.convert_runtime_error(file_id, &error); if let Err(e) = reporter.report_diagnostic(file_id, &diagnostic) { - eprintln!("Error displaying diagnostic: {}", e); - eprintln!("{}", error); // Fallback to simple error display + eprintln!("Error displaying diagnostic: {e}"); + eprintln!("{error}"); // Fallback to simple error display } } } @@ -784,8 +784,8 @@ async fn main() -> io::Result<()> { for error in errors { let diagnostic = reporter.convert_parse_error(file_id, &error); if let Err(e) = reporter.report_diagnostic(file_id, &diagnostic) { - eprintln!("Error displaying diagnostic: {}", e); - eprintln!("Error: {}", error); // Fallback to simple error display + eprintln!("Error displaying diagnostic: {e}"); + eprintln!("Error: {error}"); // Fallback to simple error display } } } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index a508aad8..d48177af 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -11,6 +11,77 @@ impl Program { } } +/// Represents the visibility of a container member (property or method) +#[derive(Debug, Clone, PartialEq, Default)] +pub enum Visibility { + #[default] + Public, + Private, +} + +/// Types of validation rules that can be applied to properties +#[derive(Debug, Clone, PartialEq)] +pub enum ValidationRuleType { + NotEmpty, + MinLength, + MaxLength, + ExactLength, + MinValue, + MaxValue, + Pattern, + Custom, +} + +/// Represents a validation rule for a property +#[derive(Debug, Clone, PartialEq)] +pub struct ValidationRule { + pub rule_type: ValidationRuleType, + pub parameters: Vec, + pub line: usize, + pub column: usize, +} + +/// Represents a property definition in a container +#[derive(Debug, Clone, PartialEq)] +pub struct PropertyDefinition { + pub name: String, + pub property_type: Option, + pub default_value: Option, + pub validation_rules: Vec, + pub visibility: Visibility, + pub is_static: bool, + pub line: usize, + pub column: usize, +} + +/// Represents a property initializer in a container instantiation +#[derive(Debug, Clone, PartialEq)] +pub struct PropertyInitializer { + pub name: String, + pub value: Expression, + pub line: usize, + pub column: usize, +} + +/// Represents an action signature in an interface +#[derive(Debug, Clone, PartialEq)] +pub struct ActionSignature { + pub name: String, + pub parameters: Vec, + pub return_type: Option, + pub line: usize, + pub column: usize, +} + +/// Represents an event definition in a container +#[derive(Debug, Clone, PartialEq)] +pub struct EventDefinition { + pub name: String, + pub parameters: Vec, + pub line: usize, + pub column: usize, +} + #[derive(Debug, Clone, PartialEq)] pub enum Statement { VariableDeclaration { @@ -170,6 +241,59 @@ pub enum Statement { line: usize, column: usize, }, + // Container-related statements + ContainerDefinition { + name: String, + extends: Option, + implements: Vec, + properties: Vec, + methods: Vec, + events: Vec, + static_properties: Vec, + static_methods: Vec, + line: usize, + column: usize, + }, + ContainerInstantiation { + container_type: String, + instance_name: String, + arguments: Vec, + property_initializers: Vec, + line: usize, + column: usize, + }, + InterfaceDefinition { + name: String, + extends: Vec, + required_actions: Vec, + line: usize, + column: usize, + }, + EventDefinition { + name: String, + parameters: Vec, + line: usize, + column: usize, + }, + EventTrigger { + name: String, + arguments: Vec, + line: usize, + column: usize, + }, + EventHandler { + event_source: Expression, + event_name: String, + handler_body: Vec, + line: usize, + column: usize, + }, + ParentMethodCall { + method_name: String, + arguments: Vec, + line: usize, + column: usize, + }, } #[derive(Debug, Clone, PartialEq)] @@ -249,6 +373,26 @@ pub enum Expression { line: usize, column: usize, }, + // Container-related expressions + StaticMemberAccess { + container: String, + member: String, + line: usize, + column: usize, + }, + MethodCall { + object: Box, + method: String, + arguments: Vec, + line: usize, + column: usize, + }, + PropertyAccess { + object: Box, + property: String, + line: usize, + column: usize, + }, } #[derive(Debug, Clone, PartialEq)] @@ -290,6 +434,8 @@ pub struct Parameter { pub name: String, pub param_type: Option, pub default_value: Option, + pub line: usize, + pub column: usize, } #[derive(Debug, Clone, PartialEq)] @@ -316,6 +462,10 @@ pub enum Type { Error, // Used to mark expressions that have already failed type checking Async(Box), // For asynchronous operations returning a value of Type Any, // Used for generic types like lists of any type + // Container-related types + Container(String), + ContainerInstance(String), + Interface(String), } #[derive(Debug, Clone)] diff --git a/src/parser/container_ast.rs b/src/parser/container_ast.rs new file mode 100644 index 00000000..e297f8ea --- /dev/null +++ b/src/parser/container_ast.rs @@ -0,0 +1,182 @@ +use super::ast::{Argument, Expression, Parameter, Statement, Type}; +use std::fmt; + +/// Represents the visibility of a container member (property or method) +#[derive(Debug, Clone, PartialEq)] +pub enum Visibility { + Public, + Private, +} + +impl Default for Visibility { + fn default() -> Self { + Visibility::Public // Default to public visibility + } +} + +/// Represents a validation rule for a property +#[derive(Debug, Clone, PartialEq)] +pub struct ValidationRule { + pub rule_type: ValidationRuleType, + pub parameters: Vec, + pub line: usize, + pub column: usize, +} + +/// Types of validation rules that can be applied to properties +#[derive(Debug, Clone, PartialEq)] +pub enum ValidationRuleType { + NotEmpty, + MinLength, + MaxLength, + ExactLength, + MinValue, + MaxValue, + Pattern, + Custom, +} + +/// Represents a property definition in a container +#[derive(Debug, Clone, PartialEq)] +pub struct PropertyDefinition { + pub name: String, + pub property_type: Option, + pub default_value: Option, + pub validation_rules: Vec, + pub visibility: Visibility, + pub is_static: bool, + pub line: usize, + pub column: usize, +} + +/// Represents a property initializer in a container instantiation +#[derive(Debug, Clone, PartialEq)] +pub struct PropertyInitializer { + pub name: String, + pub value: Expression, + pub line: usize, + pub column: usize, +} + +/// Represents an action signature in an interface +#[derive(Debug, Clone, PartialEq)] +pub struct ActionSignature { + pub name: String, + pub parameters: Vec, + pub return_type: Option, + pub line: usize, + pub column: usize, +} + +/// Container-related statements to be added to the Statement enum +#[derive(Debug, Clone, PartialEq)] +pub enum ContainerStatement { + /// Container definition statement + ContainerDefinition { + name: String, + extends: Option, + implements: Vec, + properties: Vec, + methods: Vec, // ActionDefinition statements + events: Vec, + static_properties: Vec, + static_methods: Vec, // ActionDefinition statements + line: usize, + column: usize, + }, + + /// Container instantiation statement + ContainerInstantiation { + container_type: String, + instance_name: String, + arguments: Vec, + property_initializers: Vec, + line: usize, + column: usize, + }, + + /// Interface definition statement + InterfaceDefinition { + name: String, + extends: Vec, + required_actions: Vec, + line: usize, + column: usize, + }, + + /// Event definition statement + EventDefinition { + name: String, + parameters: Vec, + line: usize, + column: usize, + }, + + /// Event trigger statement + EventTrigger { + name: String, + arguments: Vec, + line: usize, + column: usize, + }, + + /// Event handler statement + EventHandler { + event_source: Expression, + event_name: String, + handler_body: Vec, + line: usize, + column: usize, + }, + + /// Parent method call statement + ParentMethodCall { + method_name: String, + arguments: Vec, + line: usize, + column: usize, + }, +} + +/// Represents an event definition in a container +#[derive(Debug, Clone, PartialEq)] +pub struct EventDefinition { + pub name: String, + pub parameters: Vec, + pub line: usize, + pub column: usize, +} + +/// Container-related expressions to be added to the Expression enum +#[derive(Debug, Clone, PartialEq)] +pub enum ContainerExpression { + /// Static member access expression + StaticMemberAccess { + container: String, + member: String, + line: usize, + column: usize, + }, + + /// Method call expression + MethodCall { + object: Box, + method: String, + arguments: Vec, + line: usize, + column: usize, + }, +} + +/// Container-related types to be added to the Type enum +#[derive(Debug, Clone, PartialEq)] +pub enum ContainerType { + /// Container type + Container(String), + + /// Container instance type + ContainerInstance(String), + + /// Interface type + Interface(String), +} \ No newline at end of file diff --git a/src/parser/container_parser.rs b/src/parser/container_parser.rs new file mode 100644 index 00000000..29855a0c --- /dev/null +++ b/src/parser/container_parser.rs @@ -0,0 +1,4 @@ +// Container parsing implementation has been integrated directly into the main parser. +// +// +// diff --git a/src/parser/mod.rs b/src/parser/mod.rs index da3c141e..12e2661b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -279,22 +279,763 @@ impl<'a> Parser<'a> { } } + // Container-related parsing methods + pub fn parse_container_definition(&mut self) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'create' + let line = start_token.line; + let column = start_token.column; + + self.expect_token( + Token::KeywordContainer, + "Expected 'container' after 'create'", + )?; + + // Parse container name + let name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier for container name, found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier for container name, found end of input".to_string(), + line, + column, + )); + }; + + // Parse inheritance and interfaces + let (extends, implements) = self.parse_inheritance()?; + + // Expect colon after container declaration + self.expect_token(Token::Colon, "Expected ':' after container name")?; + + // Parse container body + let (properties, methods, events, static_properties, static_methods) = + self.parse_container_body()?; + + Ok(Statement::ContainerDefinition { + name, + extends, + implements, + properties, + methods, + events, + static_properties, + static_methods, + line, + column, + }) + } + + pub fn parse_interface_definition(&mut self) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'create' + let line = start_token.line; + let column = start_token.column; + + self.expect_token( + Token::KeywordInterface, + "Expected 'interface' after 'create'", + )?; + + // Parse interface name + let name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier for interface name, found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier for interface name, found end of input".to_string(), + line, + column, + )); + }; + + // For now, just create a simple interface definition + Ok(Statement::InterfaceDefinition { + name, + extends: Vec::new(), + required_actions: Vec::new(), + line, + column, + }) + } + + pub fn parse_container_instantiation(&mut self) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'create' + let line = start_token.line; + let column = start_token.column; + + self.expect_token(Token::KeywordNew, "Expected 'new' after 'create'")?; + + // Parse container type + let container_type = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier for container type, found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier for container type, found end of input".to_string(), + line, + column, + )); + }; + + self.expect_token(Token::KeywordAs, "Expected 'as' after container type")?; + + // Parse instance name + let instance_name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier for instance name, found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier for instance name, found end of input".to_string(), + line, + column, + )); + }; + + // Expect colon after instance declaration + self.expect_token(Token::Colon, "Expected ':' after instance name")?; + + let (property_initializers, arguments) = self.parse_instantiation_body()?; + + Ok(Statement::ContainerInstantiation { + container_type, + instance_name, + arguments, + property_initializers, + line, + column, + }) + } + + pub fn parse_event_definition(&mut self) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'event' + let line = start_token.line; + let column = start_token.column; + + // Parse event name + let name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier for event name, found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier for event name, found end of input".to_string(), + line, + column, + )); + }; + + // For now, just create a simple event definition + Ok(Statement::EventDefinition { + name, + parameters: Vec::new(), + line, + column, + }) + } + + pub fn parse_event_trigger(&mut self) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'trigger' + let line = start_token.line; + let column = start_token.column; + + // Parse event name + let name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier for event name, found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier for event name, found end of input".to_string(), + line, + column, + )); + }; + + // For now, just create a simple event trigger + Ok(Statement::EventTrigger { + name, + arguments: Vec::new(), + line, + column, + }) + } + + pub fn parse_event_handler(&mut self) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'on' + let line = start_token.line; + let column = start_token.column; + + // Parse event source + let event_source = self.parse_expression()?; + + // Parse event name + let event_name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier for event name, found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier for event name, found end of input".to_string(), + line, + column, + )); + }; + + // For now, just create a simple event handler + Ok(Statement::EventHandler { + event_source, + event_name, + handler_body: Vec::new(), + line, + column, + }) + } + + pub fn parse_parent_method_call(&mut self) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'parent' + let line = start_token.line; + let column = start_token.column; + + // Parse method name + let method_name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier for method name, found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier for method name, found end of input".to_string(), + line, + column, + )); + }; + + // For now, just create a simple parent method call + Ok(Statement::ParentMethodCall { + method_name, + arguments: Vec::new(), + line, + column, + }) + } + + // Helper methods for parsing container-related constructs + fn parse_inheritance(&mut self) -> Result<(Option, Vec), ParseError> { + let mut extends = None; + let mut implements = Vec::new(); + + // Check for 'extends' keyword + if let Some(token) = self.tokens.peek() { + if token.token == Token::KeywordExtends { + self.tokens.next(); // Consume 'extends' + + if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + extends = Some(id.clone()); + self.tokens.next(); // Consume the identifier + } else { + return Err(ParseError::new( + "Expected identifier after 'extends'".to_string(), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier after 'extends'".to_string(), + 0, + 0, + )); + } + } + } + + // Check for 'implements' keyword + if let Some(token) = self.tokens.peek() { + if token.token == Token::KeywordImplements { + self.tokens.next(); // Consume 'implements' + + // Parse interface list + loop { + if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + implements.push(id.clone()); + self.tokens.next(); // Consume the identifier + + // Check for comma to continue or break + if let Some(next_token) = self.tokens.peek() { + if next_token.token == Token::Comma { + self.tokens.next(); // Consume comma + continue; + } else { + break; + } + } else { + break; + } + } else { + return Err(ParseError::new( + "Expected identifier in implements list".to_string(), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier in implements list".to_string(), + 0, + 0, + )); + } + } + } + } + + Ok((extends, implements)) + } + + #[allow(clippy::type_complexity)] + fn parse_container_body( + &mut self, + ) -> Result< + ( + Vec, + Vec, + Vec, + Vec, + Vec, + ), + ParseError, + > { + let mut properties = Vec::new(); + let mut methods = Vec::new(); + let mut events = Vec::new(); + let mut static_properties = Vec::new(); + let mut static_methods = Vec::new(); + + // Parse container body until 'end' + loop { + if let Some(token) = self.tokens.peek() { + match &token.token { + Token::KeywordEnd => { + self.tokens.next(); // Consume 'end' + break; + } + Token::KeywordProperty => { + let prop = self.parse_property_definition(false)?; + properties.push(prop); + } + Token::KeywordStatic => { + let static_token = self.tokens.next().unwrap(); // Consume 'static' + if let Some(next_token) = self.tokens.peek() { + match &next_token.token { + Token::KeywordProperty => { + let prop = self.parse_property_definition(true)?; + static_properties.push(prop); + } + Token::KeywordAction => { + let method = self.parse_container_action_definition()?; + static_methods.push(method); + } + _ => { + return Err(ParseError::new( + "Expected 'property' or 'action' after 'static'" + .to_string(), + next_token.line, + next_token.column, + )); + } + } + } else { + return Err(ParseError::new( + "Expected 'property' or 'action' after 'static'".to_string(), + static_token.line, + static_token.column, + )); + } + } + Token::KeywordAction => { + let method = self.parse_container_action_definition()?; + methods.push(method); + } + Token::KeywordEvent => { + let event = self.parse_event_definition_full()?; + events.push(event); + } + _ => { + return Err(ParseError::new( + format!("Unexpected token in container body: {:?}", token.token), + token.line, + token.column, + )); + } + } + } else { + return Err(ParseError::new( + "Unexpected end of input in container body".to_string(), + 0, + 0, + )); + } + } + + Ok(( + properties, + methods, + events, + static_properties, + static_methods, + )) + } + + fn parse_property_definition( + &mut self, + is_static: bool, + ) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'property' + let line = start_token.line; + let column = start_token.column; + + // Parse property name + let name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + "Expected property name after 'property'".to_string(), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected property name after 'property'".to_string(), + line, + column, + )); + }; + + let property_type = if let Some(token) = self.tokens.peek() { + if token.token == Token::Colon { + self.tokens.next(); // Consume ':' + + if let Some(type_token) = self.tokens.peek() { + if let Token::Identifier(type_name) = &type_token.token { + self.tokens.next(); // Consume type name + Some(Type::Custom(type_name.clone())) + } else { + return Err(ParseError::new( + "Expected type name after ':'".to_string(), + type_token.line, + type_token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected type name after ':'".to_string(), + line, + column, + )); + } + } else { + None + } + } else { + None + }; + + let default_value = if let Some(token) = self.tokens.peek() { + if token.token == Token::KeywordDefaults { + self.tokens.next(); // Consume 'defaults' + Some(self.parse_expression()?) + } else { + None + } + } else { + None + }; + + Ok(PropertyDefinition { + name, + property_type, + default_value, + validation_rules: Vec::new(), + is_static, + visibility: Visibility::Public, + line, + column, + }) + } + + fn parse_event_definition_full(&mut self) -> Result { + let start_token = self.tokens.next().unwrap(); // Consume 'event' + let line = start_token.line; + let column = start_token.column; + + // Parse event name + let name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); // Consume the identifier + id.clone() + } else { + return Err(ParseError::new( + "Expected event name after 'event'".to_string(), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected event name after 'event'".to_string(), + line, + column, + )); + }; + + let mut parameters = Vec::new(); + if let Some(token) = self.tokens.peek() { + if token.token == Token::KeywordNeeds { + self.tokens.next(); // Consume 'needs' + parameters = self.parse_parameter_list()?; + } + } + + Ok(EventDefinition { + name, + parameters, + line, + column, + }) + } + + fn parse_parameter_list(&mut self) -> Result, ParseError> { + let mut parameters = Vec::new(); + + while let Some(token) = self.tokens.peek() { + if let Token::Identifier(param_name) = &token.token { + let name = param_name.clone(); + let param_line = token.line; + let param_column = token.column; + self.tokens.next(); // Consume parameter name + + let param_type = if let Some(type_token) = self.tokens.peek() { + if type_token.token == Token::Colon { + self.tokens.next(); // Consume ':' + + if let Some(type_name_token) = self.tokens.peek() { + if let Token::Identifier(type_name) = &type_name_token.token { + self.tokens.next(); // Consume type name + Some(Type::Custom(type_name.clone())) + } else { + return Err(ParseError::new( + "Expected type name after ':'".to_string(), + type_name_token.line, + type_name_token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected type name after ':'".to_string(), + param_line, + param_column, + )); + } + } else { + None + } + } else { + None + }; + + parameters.push(Parameter { + name, + param_type, + default_value: None, + line: param_line, + column: param_column, + }); + + // Check for comma to continue or break + if let Some(next_token) = self.tokens.peek() { + if next_token.token == Token::Comma { + self.tokens.next(); // Consume comma + continue; + } else { + break; + } + } else { + break; + } + } else { + break; + } + } + + Ok(parameters) + } + + fn parse_instantiation_body( + &mut self, + ) -> Result<(Vec, Vec), ParseError> { + let mut property_initializers = Vec::new(); + let arguments = Vec::new(); + + // Parse instantiation body until 'end' + while let Some(token) = self.tokens.peek() { + match &token.token { + Token::KeywordEnd => { + self.tokens.next(); // Consume 'end' + break; + } + Token::Identifier(prop_name) => { + let name = prop_name.clone(); + let prop_line = token.line; + let prop_column = token.column; + self.tokens.next(); // Consume property name + + // Expect 'is' or ':' + if let Some(next_token) = self.tokens.peek() { + if next_token.token == Token::KeywordIs || next_token.token == Token::Colon + { + self.tokens.next(); // Consume 'is' or ':' + + let value = self.parse_expression()?; + property_initializers.push(PropertyInitializer { + name, + value, + line: prop_line, + column: prop_column, + }); + } else { + return Err(ParseError::new( + "Expected 'is' or ':' after property name".to_string(), + next_token.line, + next_token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected 'is' or ':' after property name".to_string(), + prop_line, + prop_column, + )); + } + } + _ => { + return Err(ParseError::new( + format!("Unexpected token in instantiation body: {:?}", token.token), + token.line, + token.column, + )); + } + } + } + + Ok((property_initializers, arguments)) + } + fn parse_statement(&mut self) -> Result { if let Some(token) = self.tokens.peek().cloned() { match &token.token { Token::KeywordStore => self.parse_variable_declaration(), Token::KeywordCreate => { - // Check if this is "create pattern" or regular "create" + // Check if it's "create container", "create interface", "create new", "create pattern", or regular "create" let mut tokens_clone = self.tokens.clone(); tokens_clone.next(); // Skip "create" if let Some(next_token) = tokens_clone.next() { - if next_token.token == Token::KeywordPattern { - self.parse_create_pattern_statement() - } else { - self.parse_variable_declaration() + match &next_token.token { + Token::KeywordContainer => self.parse_container_definition(), + Token::KeywordInterface => self.parse_interface_definition(), + Token::KeywordNew => self.parse_container_instantiation(), + Token::KeywordPattern => self.parse_create_pattern_statement(), + _ => self.parse_variable_declaration(), // Default to variable declaration } } else { - self.parse_variable_declaration() + self.parse_variable_declaration() // Default to variable declaration } } Token::KeywordDisplay => self.parse_display_statement(), @@ -308,6 +1049,10 @@ impl<'a> Parser<'a> { Token::KeywordRepeat => self.parse_repeat_statement(), Token::KeywordExit => self.parse_exit_statement(), Token::KeywordPush => self.parse_push_statement(), + Token::KeywordEvent => self.parse_event_definition(), + Token::KeywordTrigger => self.parse_event_trigger(), + Token::KeywordOn => self.parse_event_handler(), + Token::KeywordParent => self.parse_parent_method_call(), Token::KeywordBreak => { let token_pos = self.tokens.next().unwrap(); Ok(Statement::BreakStatement { @@ -451,10 +1196,7 @@ impl<'a> Parser<'a> { } } else { return Err(ParseError::new( - format!( - "Expected 'as' after variable name '{}', but found end of input", - name - ), + format!("Expected 'as' after variable name '{name}', but found end of input"), token_pos.line, token_pos.column, )); @@ -573,7 +1315,7 @@ impl<'a> Parser<'a> { } } else { Err(ParseError::new( - format!("{}: unexpected end of input", error_message), + format!("{error_message}: unexpected end of input"), 0, 0, )) @@ -605,6 +1347,10 @@ impl<'a> Parser<'a> { } let op = match token { + Token::Plus => { + self.tokens.next(); // Consume "+" + Some((Operator::Plus, 1)) + } Token::KeywordPlus => { self.tokens.next(); // Consume "plus" Some((Operator::Plus, 1)) @@ -629,6 +1375,10 @@ impl<'a> Parser<'a> { self.tokens.next(); // Consume "by" Some((Operator::Divide, 2)) } + Token::Equals => { + self.tokens.next(); // Consume "=" + Some((Operator::Equals, 0)) + } Token::KeywordIs => { self.tokens.next(); // Consume "is" @@ -1135,8 +1885,91 @@ impl<'a> Parser<'a> { Token::Identifier(name) => { self.tokens.next(); + // Check for property access (dot notation) if let Some(next_token) = self.tokens.peek().cloned() { - if let Token::Identifier(id) = &next_token.token { + if next_token.token == Token::Dot { + self.tokens.next(); // Consume '.' + + if let Some(property_token) = self.tokens.peek().cloned() { + if let Token::Identifier(property_name) = &property_token.token { + self.tokens.next(); // Consume property name + + // Check for method call with parentheses + if let Some(paren_token) = self.tokens.peek().cloned() { + if paren_token.token == Token::LeftParen { + self.tokens.next(); // Consume '(' + + let mut arguments = Vec::new(); + + if let Some(next_token) = self.tokens.peek() { + if next_token.token != Token::RightParen { + let expr = self.parse_expression()?; + arguments.push(Argument { + name: None, + value: expr, + }); + + while let Some(comma_token) = self.tokens.peek() + { + if comma_token.token == Token::Comma { + self.tokens.next(); // Consume ',' + let expr = self.parse_expression()?; + arguments.push(Argument { + name: None, + value: expr, + }); + } else { + break; + } + } + } + } + + self.expect_token( + Token::RightParen, + "Expected ')' after method arguments", + )?; + + return Ok(Expression::MethodCall { + object: Box::new(Expression::Variable( + name.clone(), + token.line, + token.column, + )), + method: property_name.clone(), + arguments, + line: token.line, + column: token.column, + }); + } + } + + // Property access without method call + return Ok(Expression::PropertyAccess { + object: Box::new(Expression::Variable( + name.clone(), + token.line, + token.column, + )), + property: property_name.clone(), + line: token.line, + column: token.column, + }); + } else { + return Err(ParseError::new( + "Expected property name after '.'".to_string(), + property_token.line, + property_token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected property name after '.'".to_string(), + token.line, + token.column, + )); + } + } else if let Token::Identifier(id) = &next_token.token { if id.to_lowercase() == "with" { self.tokens.next(); // Consume "with" @@ -1455,6 +2288,51 @@ impl<'a> Parser<'a> { column: token.column, }; } + // Handle static member access: "Container.staticMember" + Token::Identifier(id) if id == "." => { + self.tokens.next(); // Consume "." + + if let Some(member_token) = self.tokens.peek().cloned() { + if let Token::Identifier(member) = &member_token.token { + self.tokens.next(); // Consume member name + + // Extract container name from expression + let container = if let Expression::Variable(name, _, _) = &expr + { + name.clone() + } else { + return Err(ParseError::new( + "Static member access requires a container name" + .to_string(), + token.line, + token.column, + )); + }; + + expr = Expression::StaticMemberAccess { + container, + member: member.clone(), + line: token.line, + column: token.column, + }; + } else { + return Err(ParseError::new( + format!( + "Expected identifier after '.', found {:?}", + member_token.token + ), + member_token.line, + member_token.column, + )); + } + } else { + return Err(ParseError::new( + "Unexpected end of input after '.'".to_string(), + token.line, + token.column, + )); + } + } _ => break, } } @@ -1559,6 +2437,25 @@ impl<'a> Parser<'a> { line, column, }), + Expression::StaticMemberAccess { line, column, .. } => { + Ok(Statement::DisplayStatement { + value: expr, + line, + column, + }) + } + Expression::MethodCall { line, column, .. } => Ok(Statement::DisplayStatement { + value: expr, + line, + column, + }), + Expression::PropertyAccess { line, column, .. } => { + Ok(Statement::DisplayStatement { + value: expr, + line, + column, + }) + } }; }; @@ -2019,15 +2916,18 @@ impl<'a> Parser<'a> { while let Some(token) = self.tokens.peek().cloned() { exec_trace!("Checking token for parameter: {:?}", token.token); - let param_name = if let Token::Identifier(id) = &token.token { - exec_trace!("Found parameter: {}", id); - self.tokens.next(); + let (param_name, param_line, param_column) = + if let Token::Identifier(id) = &token.token { + exec_trace!("Found parameter: {}", id); + let line = token.line; + let column = token.column; + self.tokens.next(); - id.clone() - } else { - exec_trace!("Not an identifier, breaking parameter parsing"); - break; - }; + (id.clone(), line, column) + } else { + exec_trace!("Not an identifier, breaking parameter parsing"); + break; + }; let param_type = if let Some(token) = self.tokens.peek() { if matches!(token.token, Token::KeywordAs) { @@ -2090,6 +2990,8 @@ impl<'a> Parser<'a> { name: param_name, param_type, default_value, + line: param_line, + column: param_column, }); if let Some(token) = self.tokens.peek().cloned() { @@ -3088,6 +3990,75 @@ impl<'a> Parser<'a> { Ok(stmt) } + fn parse_container_action_definition(&mut self) -> Result { + self.tokens.next(); // Consume "action" + + let name = if let Some(token) = self.tokens.peek() { + if let Token::Identifier(id) = &token.token { + self.tokens.next(); + id.clone() + } else { + return Err(ParseError::new( + format!( + "Expected identifier after 'action', found {:?}", + token.token + ), + token.line, + token.column, + )); + } + } else { + return Err(ParseError::new( + "Expected identifier after 'action'".to_string(), + 0, + 0, + )); + }; + + let mut parameters = Vec::new(); + + // Check for parameters + if let Some(token) = self.tokens.peek().cloned() { + if matches!(token.token, Token::KeywordNeeds) { + self.tokens.next(); // Consume "needs" + parameters = self.parse_parameter_list()?; + } + } + + // For now, container actions don't support explicit return types + let return_type = None; + + self.expect_token(Token::Colon, "Expected ':' after action declaration")?; + + let mut body = Vec::new(); + + // Parse action body until 'end' + loop { + if let Some(token) = self.tokens.peek() { + if token.token == Token::KeywordEnd { + self.tokens.next(); // Consume 'end' + break; + } + body.push(self.parse_statement()?); + } else { + return Err(ParseError::new( + "Unexpected end of input in action body".to_string(), + 0, + 0, + )); + } + } + + Ok(Statement::ActionDefinition { + name, + parameters, + body, + return_type, + line: 0, + column: 0, + }) + } + fn parse_create_pattern_statement(&mut self) -> Result { let create_token = self.tokens.next().unwrap(); // Consume "create" self.expect_token(Token::KeywordPattern, "Expected 'pattern' after 'create'")?; @@ -3232,7 +4203,7 @@ impl<'a> Parser<'a> { if *i < tokens.len() && tokens[*i].token == Token::KeywordMore { *i += 1; let inner = Self::parse_quantified_content(tokens, i)?; - Ok(format!("rep(1,inf,{})", inner)) + Ok(format!("rep(1,inf,{inner})")) } else { Err(ParseError::new( "Expected 'more' after 'one or'".to_string(), @@ -3252,7 +4223,7 @@ impl<'a> Parser<'a> { Token::KeywordOptional => { *i += 1; let inner = Self::parse_quantified_content(tokens, i)?; - Ok(format!("rep(0,1,{})", inner)) + Ok(format!("rep(0,1,{inner})")) } Token::KeywordBetween => { @@ -3267,7 +4238,7 @@ impl<'a> Parser<'a> { let max_val = *max; *i += 1; let inner = Self::parse_quantified_content(tokens, i)?; - Ok(format!("rep({},{},{})", min_val, max_val, inner)) + Ok(format!("rep({min_val},{max_val},{inner})")) } else { Err(ParseError::new( "Expected number after 'and'".to_string(), @@ -3324,7 +4295,7 @@ impl<'a> Parser<'a> { let capture_name = name.clone(); *i += 1; let inner_ir = Self::compile_pattern_to_ir(&capture_tokens)?; - Ok(format!("cap(\"{}\",{})", capture_name, inner_ir)) + Ok(format!("cap(\"{capture_name}\",{inner_ir})")) } else { Err(ParseError::new( "Expected identifier after 'as'".to_string(), diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 1bf0dd6b..789274af 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -20,10 +20,7 @@ fn parses_concatenation_correctly() { "Left side should be variable 'currentLog'" ); } else { - panic!( - "Left side of concatenation should be a Variable, not {:?}", - left - ); + panic!("Left side of concatenation should be a Variable, not {left:?}"); } // Right side of the outer concatenation should be another concatenation @@ -40,17 +37,14 @@ fn parses_concatenation_correctly() { "Left side should be variable 'message_text'" ); } else { - panic!("Inner left side should be a Variable, not {:?}", inner_left); + panic!("Inner left side should be a Variable, not {inner_left:?}"); } // Inner right should be a string literal if let Expression::Literal(Literal::String(s), ..) = *inner_right { assert_eq!(s, "\\n", "Right side should be string '\\n'"); } else { - panic!( - "Inner right side should be a String literal, not {:?}", - inner_right - ); + panic!("Inner right side should be a String literal, not {inner_right:?}"); } } else if let Expression::Variable(var_name, ..) = *right { // For simple concatenation, right side could be just the variable @@ -59,16 +53,13 @@ fn parses_concatenation_correctly() { "Right side should be variable 'message_text'" ); } else { - panic!( - "Right side should be a Variable or Concatenation, not {:?}", - right - ); + panic!("Right side should be a Variable or Concatenation, not {right:?}"); } } else { - panic!("Expected Concatenation expression, got: {:?}", value); + panic!("Expected Concatenation expression, got: {value:?}"); } } else { - panic!("Expected VariableDeclaration, got: {:?}", result); + panic!("Expected VariableDeclaration, got: {result:?}"); } } @@ -222,12 +213,12 @@ fn test_parse_wait_for_open_file() { println!("Testing open file statement:"); for (i, token) in tokens.iter().enumerate() { - println!("{}: {:?}", i, token); + println!("{i}: {token:?}"); } let result = parser.parse_statement(); if let Err(ref e) = result { - println!("Parse error for open file: {:?}", e); + println!("Parse error for open file: {e:?}"); } else { println!("Successfully parsed open file statement"); } @@ -242,12 +233,12 @@ fn test_parse_wait_for_open_file() { println!("\nTesting new open file syntax:"); for (i, token) in tokens.iter().enumerate() { - println!("{}: {:?}", i, token); + println!("{i}: {token:?}"); } let result = parser.parse_statement(); if let Err(ref e) = result { - println!("Parse error for new open file syntax: {:?}", e); + println!("Parse error for new open file syntax: {e:?}"); } else { println!("Successfully parsed new open file syntax"); } @@ -277,12 +268,12 @@ fn test_parse_wait_for_open_file() { println!("\nTesting wait for statement:"); for (i, token) in tokens.iter().enumerate() { - println!("{}: {:?}", i, token); + println!("{i}: {token:?}"); } let result = parser.parse_statement(); if let Err(ref e) = result { - println!("Parse error for wait for: {:?}", e); + println!("Parse error for wait for: {e:?}"); } else { println!("Successfully parsed wait for statement"); } diff --git a/src/repl.rs b/src/repl.rs index deef0fec..fe74448e 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -106,13 +106,12 @@ impl ReplState { ".clear" => { print!("\x1B[2J\x1B[1;1H"); if let Err(e) = io::stdout().flush() { - eprintln!("Flush failed: {}", e); + eprintln!("Flush failed: {e}"); } Ok(CommandResult::ClearedScreen) } _ => Ok(CommandResult::Unknown(format!( - "Unknown command: {}", - command + "Unknown command: {command}" ))), } } @@ -214,7 +213,7 @@ impl ReplState { &reporter.files, &diagnostic.to_codespan_diagnostic(file_id), ) { - error_messages.push(format!("Type error: {}", error)); + error_messages.push(format!("Type error: {error}")); continue; } @@ -236,7 +235,7 @@ impl ReplState { match self.interpreter.interpret(&expr_program).await { Ok(value) => { - result_output = Some(format!("{:?}", value)); + result_output = Some(format!("{value:?}")); } Err(errors) => { let mut error_messages = Vec::new(); @@ -254,7 +253,7 @@ impl ReplState { &reporter.files, &diagnostic.to_codespan_diagnostic(file_id), ) { - error_messages.push(format!("Runtime error: {}", error)); + error_messages.push(format!("Runtime error: {error}")); continue; } @@ -284,7 +283,7 @@ impl ReplState { &reporter.files, &diagnostic.to_codespan_diagnostic(file_id), ) { - error_messages.push(format!("Runtime error: {}", error)); + error_messages.push(format!("Runtime error: {error}")); continue; } @@ -315,7 +314,7 @@ impl ReplState { &reporter.files, &diagnostic.to_codespan_diagnostic(file_id), ) { - error_messages.push(format!("Runtime error: {}", error)); + error_messages.push(format!("Runtime error: {error}")); continue; } @@ -355,9 +354,9 @@ pub async fn run_repl() -> RustylineResult<()> { rl.add_history_entry(&line)?; match repl_state.process_line(&line).await { - Ok(Some(output)) => println!("{}", output), + Ok(Some(output)) => println!("{output}"), Ok(None) => {} // No output needed - Err(error) => println!("Error: {}", error), + Err(error) => println!("Error: {error}"), } } Err(ReadlineError::Interrupted) => { @@ -369,7 +368,7 @@ pub async fn run_repl() -> RustylineResult<()> { break; } Err(err) => { - println!("Error: {:?}", err); + println!("Error: {err:?}"); break; } } diff --git a/src/stdlib/core.rs b/src/stdlib/core.rs index 53c46663..43ce56df 100644 --- a/src/stdlib/core.rs +++ b/src/stdlib/core.rs @@ -8,7 +8,7 @@ pub fn native_print(args: Vec) -> Result { if i > 0 { print!(" "); } - print!("{}", arg); + print!("{arg}"); } println!(); Ok(Value::Null) diff --git a/src/stdlib/legacy_pattern.rs b/src/stdlib/legacy_pattern.rs index ace1043b..40a56ede 100644 --- a/src/stdlib/legacy_pattern.rs +++ b/src/stdlib/legacy_pattern.rs @@ -68,7 +68,7 @@ impl Pattern { capture_names.push(placeholder_name.to_string()); - regex_str.push_str(&format!("(?P<{}>.*?)", placeholder_name)); + regex_str.push_str(&format!("(?P<{placeholder_name}>.*?)")); parts.push(PatternPart::Placeholder(placeholder_name.to_string())); current_pos += end_pos + 1; @@ -169,7 +169,7 @@ impl Pattern { current_pos = new_pos; parts.push(PatternPart::Optional(Box::new(part))); - regex_str.push_str(&format!("(?:{})?", part_regex)); + regex_str.push_str(&format!("(?:{part_regex})?")); continue; } @@ -186,7 +186,7 @@ impl Pattern { current_pos = new_pos; parts.push(PatternPart::OneOrMore(Box::new(part))); - regex_str.push_str(&format!("(?:{})+", part_regex)); + regex_str.push_str(&format!("(?:{part_regex})+")); continue; } @@ -225,7 +225,7 @@ impl Pattern { count, part: Box::new(part), }); - regex_str.push_str(&format!("(?:{}){{{}}}", part_regex, count)); + regex_str.push_str(&format!("(?:{part_regex}){{{count}}}")); continue; } @@ -289,7 +289,7 @@ impl Pattern { max, part: Box::new(part), }); - regex_str.push_str(&format!("(?:{}){{{},{}}}", part_regex, min, max)); + regex_str.push_str(&format!("(?:{part_regex}){{{min},{max}}}")); continue; } @@ -306,7 +306,7 @@ impl Pattern { current_pos = new_pos; parts.push(PatternPart::BeginsWith(Box::new(part))); - regex_str = format!("^{}{}", part_regex, regex_str); + regex_str = format!("^{part_regex}{regex_str}"); continue; } @@ -323,7 +323,7 @@ impl Pattern { current_pos = new_pos; parts.push(PatternPart::EndsWith(Box::new(part))); - regex_str.push_str(&format!("{}$", part_regex)); + regex_str.push_str(&format!("{part_regex}$")); continue; } @@ -342,10 +342,10 @@ impl Pattern { if let Some(PatternPart::Alternation(alts)) = parts.last_mut() { alts.push(part); - regex_str.push_str(&format!("|{}", part_regex)); + regex_str.push_str(&format!("|{part_regex}")); } else if let Some(prev_part) = parts.pop() { let prev_regex = regex_str.clone(); - regex_str = format!("(?:{}|{})", prev_regex, part_regex); + regex_str = format!("(?:{prev_regex}|{part_regex})"); parts.push(PatternPart::Alternation(vec![prev_part, part])); } else { @@ -368,7 +368,7 @@ impl Pattern { current_pos += 1; } - let regex = Regex::new(®ex_str).map_err(|e| format!("Invalid regex: {}", e))?; + let regex = Regex::new(®ex_str).map_err(|e| format!("Invalid regex: {e}"))?; Ok(Pattern { parts, @@ -620,7 +620,7 @@ pub fn native_pattern_matches(args: Vec) -> Result { Ok(Value::Bool(result)) } Err(err) => Err(RuntimeError::new( - format!("Error parsing pattern: {}", err), + format!("Error parsing pattern: {err}"), line, column, )), @@ -673,7 +673,7 @@ pub fn native_pattern_find(args: Vec) -> Result { } } Err(err) => Err(RuntimeError::new( - format!("Error parsing pattern: {}", err), + format!("Error parsing pattern: {err}"), line, column, )), @@ -730,7 +730,7 @@ pub fn native_pattern_replace(args: Vec) -> Result { Ok(Value::Text(Rc::from(result.as_str()))) } Err(err) => Err(RuntimeError::new( - format!("Error parsing pattern: {}", err), + format!("Error parsing pattern: {err}"), line, column, )), @@ -781,7 +781,7 @@ pub fn native_pattern_split(args: Vec) -> Result { Ok(Value::List(Rc::new(RefCell::new(values)))) } Err(err) => Err(RuntimeError::new( - format!("Error parsing pattern: {}", err), + format!("Error parsing pattern: {err}"), line, column, )), diff --git a/src/stdlib/list.rs b/src/stdlib/list.rs index 3cebb64f..b3ec2167 100644 --- a/src/stdlib/list.rs +++ b/src/stdlib/list.rs @@ -92,7 +92,7 @@ pub fn native_contains(args: Vec) -> Result { let item = &args[1]; for value in list.borrow().iter() { - if format!("{:?}", value) == format!("{:?}", item) { + if format!("{value:?}") == format!("{item:?}") { return Ok(Value::Bool(true)); } } @@ -113,7 +113,7 @@ pub fn native_indexof(args: Vec) -> Result { let item = &args[1]; for (i, value) in list.borrow().iter().enumerate() { - if format!("{:?}", value) == format!("{:?}", item) { + if format!("{value:?}") == format!("{item:?}") { return Ok(Value::Number(i as f64)); } } diff --git a/src/stdlib/math.rs b/src/stdlib/math.rs index 8e4437f4..ca12d404 100644 --- a/src/stdlib/math.rs +++ b/src/stdlib/math.rs @@ -100,10 +100,7 @@ pub fn native_clamp(args: Vec) -> Result { if min > max { return Err(RuntimeError::new( - format!( - "clamp min ({}) must be less than or equal to max ({})", - min, max - ), + format!("clamp min ({min}) must be less than or equal to max ({max})"), 0, 0, )); diff --git a/src/stdlib/pattern.rs b/src/stdlib/pattern.rs index 5e73082a..a87ea847 100644 --- a/src/stdlib/pattern.rs +++ b/src/stdlib/pattern.rs @@ -19,8 +19,8 @@ pub enum PatternError { impl std::fmt::Display for PatternError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - PatternError::ParseError(msg) => write!(f, "Pattern parse error: {}", msg), - PatternError::RuntimeError(msg) => write!(f, "Pattern runtime error: {}", msg), + PatternError::ParseError(msg) => write!(f, "Pattern parse error: {msg}"), + PatternError::RuntimeError(msg) => write!(f, "Pattern runtime error: {msg}"), PatternError::StepLimitExceeded => write!(f, "Pattern execution step limit exceeded"), PatternError::RecursionLimitExceeded => write!(f, "Pattern recursion limit exceeded"), } @@ -126,8 +126,7 @@ fn parse_ir_node(ir: &str) -> Result { Ok(PatternNode::Literal(literal)) } else { Err(PatternError::ParseError(format!( - "Invalid literal format: {}", - ir + "Invalid literal format: {ir}" ))) } } else if ir.starts_with("class(") && ir.ends_with(')') { @@ -138,8 +137,7 @@ fn parse_ir_node(ir: &str) -> Result { "whitespace" => Ok(PatternNode::CharClass(CharClass::Whitespace)), "any" => Ok(PatternNode::CharClass(CharClass::Any)), _ => Err(PatternError::ParseError(format!( - "Unknown character class: {}", - class_name + "Unknown character class: {class_name}" ))), } } else if ir.starts_with("seq(") && ir.ends_with(')') { @@ -186,8 +184,7 @@ fn parse_ir_node(ir: &str) -> Result { if min > max && max != u32::MAX { return Err(PatternError::ParseError(format!( - "Invalid range: min {} > max {}", - min, max + "Invalid range: min {min} > max {max}" ))); } @@ -227,8 +224,7 @@ fn parse_ir_node(ir: &str) -> Result { "start" => Ok(PatternNode::Anchor(AnchorType::Start)), "end" => Ok(PatternNode::Anchor(AnchorType::End)), _ => Err(PatternError::ParseError(format!( - "Unknown anchor type: {}", - anchor_type + "Unknown anchor type: {anchor_type}" ))), } } else { @@ -458,7 +454,7 @@ fn match_at_position( match_count >= *min } PatternNode::Capture { name, child } => { - recursion_stack.push(format!("capture:{}", name)); + recursion_stack.push(format!("capture:{name}")); let start_pos = pos; let result = match_at_position(child, text, pos, captures, steps, recursion_stack); @@ -588,7 +584,7 @@ pub fn native_pattern_matches( Ok(Some(_)) => Ok(Value::Bool(true)), Ok(None) => Ok(Value::Bool(false)), Err(e) => Err(RuntimeError::new( - format!("Pattern execution error: {}", e), + format!("Pattern execution error: {e}"), line, column, )), @@ -641,7 +637,7 @@ pub fn native_pattern_find( } Ok(None) => Ok(Value::Null), Err(e) => Err(RuntimeError::new( - format!("Pattern execution error: {}", e), + format!("Pattern execution error: {e}"), line, column, )), @@ -702,7 +698,7 @@ pub fn native_pattern_replace( } Ok(None) => Ok(Value::Text(Rc::from(text))), Err(e) => Err(RuntimeError::new( - format!("Pattern execution error: {}", e), + format!("Pattern execution error: {e}"), line, column, )), @@ -771,7 +767,7 @@ pub fn native_pattern_split( Ok(None) => break, Err(e) => { return Err(RuntimeError::new( - format!("Pattern execution error: {}", e), + format!("Pattern execution error: {e}"), line, column, )); diff --git a/src/stdlib/pattern_test.rs b/src/stdlib/pattern_test.rs index be4906c7..24713c06 100644 --- a/src/stdlib/pattern_test.rs +++ b/src/stdlib/pattern_test.rs @@ -468,7 +468,7 @@ mod tests { let mut pattern_parts = Vec::new(); for i in 0..20 { - pattern_parts.push(format!("rep(0,1,lit(\"{}\"))", i)); + pattern_parts.push(format!("rep(0,1,lit(\"{i}\"))")); } let pattern_ir = format!("seq({})", pattern_parts.join(",")); let pattern = parse_ir(&pattern_ir).unwrap(); diff --git a/src/stdlib/time.rs b/src/stdlib/time.rs index 17e84956..60a35852 100644 --- a/src/stdlib/time.rs +++ b/src/stdlib/time.rs @@ -213,7 +213,7 @@ pub fn native_parse_date(args: Vec) -> Result { match NaiveDate::parse_from_str(&date_str, &format_string) { Ok(date) => Ok(Value::Date(Rc::new(date))), Err(e) => Err(RuntimeError::new( - format!("Failed to parse date: {}", e), + format!("Failed to parse date: {e}"), 0, 0, )), @@ -261,7 +261,7 @@ pub fn native_parse_time(args: Vec) -> Result { match NaiveTime::parse_from_str(&time_str, &format_string) { Ok(time) => Ok(Value::Time(Rc::new(time))), Err(e) => Err(RuntimeError::new( - format!("Failed to parse time: {}", e), + format!("Failed to parse time: {e}"), 0, 0, )), @@ -326,7 +326,7 @@ pub fn native_create_time(args: Vec) -> Result { if hours >= 24 { return Err(RuntimeError::new( - format!("Hours must be between 0 and 23, got {}", hours), + format!("Hours must be between 0 and 23, got {hours}"), 0, 0, )); @@ -334,7 +334,7 @@ pub fn native_create_time(args: Vec) -> Result { if minutes >= 60 { return Err(RuntimeError::new( - format!("Minutes must be between 0 and 59, got {}", minutes), + format!("Minutes must be between 0 and 59, got {minutes}"), 0, 0, )); @@ -342,7 +342,7 @@ pub fn native_create_time(args: Vec) -> Result { if seconds >= 60 { return Err(RuntimeError::new( - format!("Seconds must be between 0 and 59, got {}", seconds), + format!("Seconds must be between 0 and 59, got {seconds}"), 0, 0, )); @@ -352,8 +352,7 @@ pub fn native_create_time(args: Vec) -> Result { Some(time) => Ok(Value::Time(Rc::new(time))), None => Err(RuntimeError::new( format!( - "Failed to create time with hours: {}, minutes: {}, seconds: {}", - hours, minutes, seconds + "Failed to create time with hours: {hours}, minutes: {minutes}, seconds: {seconds}" ), 0, 0, @@ -415,7 +414,7 @@ pub fn native_create_date(args: Vec) -> Result { if !(1..=12).contains(&month) { return Err(RuntimeError::new( - format!("Month must be between 1 and 12, got {}", month), + format!("Month must be between 1 and 12, got {month}"), 0, 0, )); @@ -423,7 +422,7 @@ pub fn native_create_date(args: Vec) -> Result { if !(1..=31).contains(&day) { return Err(RuntimeError::new( - format!("Day must be between 1 and 31, got {}", day), + format!("Day must be between 1 and 31, got {day}"), 0, 0, )); @@ -432,10 +431,7 @@ pub fn native_create_date(args: Vec) -> Result { match NaiveDate::from_ymd_opt(year, month, day) { Some(date) => Ok(Value::Date(Rc::new(date))), None => Err(RuntimeError::new( - format!( - "Failed to create date with year: {}, month: {}, day: {}", - year, month, day - ), + format!("Failed to create date with year: {year}, month: {month}, day: {day}"), 0, 0, )), @@ -482,7 +478,7 @@ pub fn native_add_days(args: Vec) -> Result { let new_date = date .checked_add_signed(chrono::Duration::days(days)) - .ok_or_else(|| RuntimeError::new(format!("Failed to add {} days to date", days), 0, 0))?; + .ok_or_else(|| RuntimeError::new(format!("Failed to add {days} days to date"), 0, 0))?; Ok(Value::Date(Rc::new(new_date))) } diff --git a/src/typechecker/mod.rs b/src/typechecker/mod.rs index e83be530..e1c0bcda 100644 --- a/src/typechecker/mod.rs +++ b/src/typechecker/mod.rs @@ -38,11 +38,11 @@ impl fmt::Display for TypeError { if let Some(expected) = &self.expected { if let Some(found) = &self.found { - message.push_str(&format!(" - Expected {} but found {}", expected, found)); + message.push_str(&format!(" - Expected {expected} but found {found}")); } } - write!(f, "{}", message) + write!(f, "{message}") } } @@ -56,9 +56,9 @@ impl fmt::Display for Type { Type::Boolean => write!(f, "Boolean"), Type::Nothing => write!(f, "Nothing"), Type::Pattern => write!(f, "Pattern"), - Type::Custom(name) => write!(f, "{}", name), - Type::List(item_type) => write!(f, "List of {}", item_type), - Type::Map(key_type, value_type) => write!(f, "Map from {} to {}", key_type, value_type), + Type::Custom(name) => write!(f, "{name}"), + Type::List(item_type) => write!(f, "List of {item_type}"), + Type::Map(key_type, value_type) => write!(f, "Map from {key_type} to {value_type}"), Type::Function { parameters, return_type, @@ -68,14 +68,17 @@ impl fmt::Display for Type { if i > 0 { write!(f, ", ")?; } - write!(f, "{}", param)?; + write!(f, "{param}")?; } - write!(f, ") -> {}", return_type) + write!(f, ") -> {return_type}") } Type::Unknown => write!(f, "Unknown"), Type::Error => write!(f, "Error"), - Type::Async(t) => write!(f, "Async<{}>", t), + Type::Async(t) => write!(f, "Async<{t}>"), Type::Any => write!(f, "Any"), + Type::Container(name) => write!(f, "Container<{name}>"), + Type::ContainerInstance(name) => write!(f, "Instance<{name}>"), + Type::Interface(name) => write!(f, "Interface<{name}>"), } } } @@ -147,19 +150,19 @@ impl TypeChecker { Statement::PushStatement { list, value, - line, - column, + line: _line, + column: _column, } => { let list_type = self.infer_expression_type(list); match list_type { Type::List(_) | Type::Unknown => {} _ => { self.errors.push(TypeError::new( - format!("Expected list type for push operation, got {:?}", list_type), + format!("Expected list type for push operation, got {list_type:?}"), Some(Type::List(Box::new(Type::Any))), Some(list_type.clone()), - *line, - *column, + *_line, + *_column, )); } } @@ -168,20 +171,19 @@ impl TypeChecker { Statement::RepeatWhileLoop { condition, body, - line, - column, + line: _line, + column: _column, } => { let condition_type = self.infer_expression_type(condition); if condition_type != Type::Boolean && condition_type != Type::Unknown { self.errors.push(TypeError::new( format!( - "Expected boolean condition in repeat-while loop, got {:?}", - condition_type + "Expected boolean condition in repeat-while loop, got {condition_type:?}" ), Some(Type::Boolean), Some(condition_type.clone()), - *line, - *column, + *_line, + *_column, )); } @@ -226,8 +228,8 @@ impl TypeChecker { Statement::HttpGetStatement { url, variable_name, - line, - column, + line: _line, + column: _column, } => { let url_type = self.infer_expression_type(url); if url_type != Type::Text && url_type != Type::Unknown && url_type != Type::Error { @@ -235,8 +237,8 @@ impl TypeChecker { "URL must be a text string".to_string(), Some(Type::Text), Some(url_type), - *line, - *column, + *_line, + *_column, ); } @@ -250,8 +252,8 @@ impl TypeChecker { url, data, variable_name, - line, - column, + line: _line, + column: _column, } => { let url_type = self.infer_expression_type(url); if url_type != Type::Text && url_type != Type::Unknown && url_type != Type::Error { @@ -259,8 +261,8 @@ impl TypeChecker { "URL must be a text string".to_string(), Some(Type::Text), Some(url_type), - *line, - *column, + *_line, + *_column, ); } @@ -275,8 +277,8 @@ impl TypeChecker { Statement::VariableDeclaration { name, value, - line, - column, + line: _line, + column: _column, } => { let inferred_type = self.infer_expression_type(value); @@ -288,11 +290,11 @@ impl TypeChecker { if inferred_type == Type::Unknown { self.type_error( - format!("Could not infer type for variable '{}'", name), + format!("Could not infer type for variable '{name}'"), None, None, - *line, - *column, + *_line, + *_column, ); } @@ -310,14 +312,11 @@ impl TypeChecker { if need_type_error { self.type_error( - format!( - "Cannot initialize variable '{}' with incompatible type", - name - ), + format!("Cannot initialize variable '{name}' with incompatible type"), symbol_type_option.clone(), Some(inferred_type.clone()), - *line, - *column, + *_line, + *_column, ); } @@ -342,8 +341,7 @@ impl TypeChecker { if !self.are_types_compatible(variable_type, &inferred_type) { self.type_error( format!( - "Cannot assign value of incompatible type to variable '{}'", - name + "Cannot assign value of incompatible type to variable '{name}'" ), Some(variable_type.clone()), Some(inferred_type), @@ -363,8 +361,8 @@ impl TypeChecker { parameters, body, return_type, - line, - column, + line: _line, + column: _column, } => { let param_types = parameters .iter() @@ -385,15 +383,15 @@ impl TypeChecker { } if let Some(ret_type) = return_type { - self.check_return_statements(body, ret_type, *line, *column); + self.check_return_statements(body, ret_type, *_line, *_column); } } Statement::IfStatement { condition, then_block, else_block, - line, - column, + line: _line, + column: _column, } => { let condition_type = self.infer_expression_type(condition); if condition_type != Type::Boolean @@ -404,8 +402,8 @@ impl TypeChecker { "Condition must be a boolean expression".to_string(), Some(Type::Boolean), Some(condition_type), - *line, - *column, + *_line, + *_column, ); } @@ -423,8 +421,8 @@ impl TypeChecker { condition, then_stmt, else_stmt, - line, - column, + line: _line, + column: _column, } => { let condition_type = self.infer_expression_type(condition); if condition_type != Type::Boolean @@ -435,8 +433,8 @@ impl TypeChecker { "Condition must be a boolean expression".to_string(), Some(Type::Boolean), Some(condition_type), - *line, - *column, + *_line, + *_column, ); } @@ -450,8 +448,8 @@ impl TypeChecker { item_name, collection, body, - line, - column, + line: _line, + column: _column, .. } => { let collection_type = self.infer_expression_type(collection); @@ -472,8 +470,8 @@ impl TypeChecker { "Collection in for-each loop must be a list or map".to_string(), Some(Type::List(Box::new(Type::Unknown))), Some(collection_type), - *line, - *column, + *_line, + *_column, ); } } @@ -487,8 +485,8 @@ impl TypeChecker { end, step, body, - line, - column, + line: _line, + column: _column, .. } => { let start_type = self.infer_expression_type(start); @@ -500,8 +498,8 @@ impl TypeChecker { "Start value in count loop must be a number".to_string(), Some(Type::Number), Some(start_type), - *line, - *column, + *_line, + *_column, ); } @@ -512,8 +510,8 @@ impl TypeChecker { "End value in count loop must be a number".to_string(), Some(Type::Number), Some(end_type), - *line, - *column, + *_line, + *_column, ); } @@ -527,8 +525,8 @@ impl TypeChecker { "Step value in count loop must be a number".to_string(), Some(Type::Number), Some(step_type), - *line, - *column, + *_line, + *_column, ); } } @@ -540,8 +538,8 @@ impl TypeChecker { Statement::WhileLoop { condition, body, - line, - column, + line: _line, + column: _column, } => { let condition_type = self.infer_expression_type(condition); if condition_type != Type::Boolean @@ -552,8 +550,8 @@ impl TypeChecker { "Condition in while loop must be a boolean expression".to_string(), Some(Type::Boolean), Some(condition_type), - *line, - *column, + *_line, + *_column, ); } @@ -564,8 +562,8 @@ impl TypeChecker { Statement::RepeatUntilLoop { condition, body, - line, - column, + line: _line, + column: _column, } => { let condition_type = self.infer_expression_type(condition); if condition_type != Type::Boolean @@ -576,8 +574,8 @@ impl TypeChecker { "Condition in repeat-until loop must be a boolean expression".to_string(), Some(Type::Boolean), Some(condition_type), - *line, - *column, + *_line, + *_column, ); } @@ -609,8 +607,8 @@ impl TypeChecker { Statement::OpenFileStatement { path, variable_name, - line, - column, + line: _line, + column: _column, } => { let path_type = self.infer_expression_type(path); if path_type != Type::Text && path_type != Type::Unknown && path_type != Type::Error @@ -619,8 +617,8 @@ impl TypeChecker { "File path must be a text string".to_string(), Some(Type::Text), Some(path_type), - *line, - *column, + *_line, + *_column, ); } @@ -631,8 +629,8 @@ impl TypeChecker { Statement::ReadFileStatement { path, variable_name, - line, - column, + line: _line, + column: _column, } => { let file_type = self.infer_expression_type(path); if file_type != Type::Custom("File".to_string()) @@ -643,8 +641,8 @@ impl TypeChecker { "Expected a File object".to_string(), Some(Type::Custom("File".to_string())), Some(file_type), - *line, - *column, + *_line, + *_column, ); } @@ -656,8 +654,8 @@ impl TypeChecker { file, content, mode: _, - line, - column, + line: _line, + column: _column, } => { let file_type = self.infer_expression_type(file); if file_type != Type::Custom("File".to_string()) @@ -668,8 +666,8 @@ impl TypeChecker { "Expected a File object".to_string(), Some(Type::Custom("File".to_string())), Some(file_type), - *line, - *column, + *_line, + *_column, ); } @@ -682,12 +680,16 @@ impl TypeChecker { "File content must be a text string".to_string(), Some(Type::Text), Some(content_type), - *line, - *column, + *_line, + *_column, ); } } - Statement::CloseFileStatement { file, line, column } => { + Statement::CloseFileStatement { + file, + line: _line, + column: _column, + } => { let file_type = self.infer_expression_type(file); if file_type != Type::Custom("File".to_string()) && file_type != Type::Unknown @@ -697,11 +699,170 @@ impl TypeChecker { "Expected a File object".to_string(), Some(Type::Custom("File".to_string())), Some(file_type), + *_line, + *_column, + ); + } + } + // Container-related statements + Statement::ContainerDefinition { + name: _name, + extends, + implements, + properties, + methods, + events: _events, + static_properties: _static_properties, + static_methods: _static_methods, + line, + column, + } => { + if let Some(parent_name) = extends { + if let Some(parent_symbol) = self.analyzer.get_symbol(parent_name) { + if parent_symbol.symbol_type != Some(Type::Container(parent_name.clone())) { + self.type_error( + format!("'{parent_name}' is not a container type"), + Some(Type::Container(parent_name.clone())), + parent_symbol.symbol_type.clone(), + *line, + *column, + ); + } + } else { + self.type_error( + format!("Parent container '{parent_name}' not found"), + Some(Type::Container(parent_name.clone())), + None, + *line, + *column, + ); + } + } + + for interface_name in implements { + if let Some(interface_symbol) = self.analyzer.get_symbol(interface_name) { + if interface_symbol.symbol_type + != Some(Type::Interface(interface_name.clone())) + { + self.type_error( + format!("'{interface_name}' is not an interface type"), + Some(Type::Interface(interface_name.clone())), + interface_symbol.symbol_type.clone(), + *line, + *column, + ); + } + } else { + self.type_error( + format!("Interface '{interface_name}' not found"), + Some(Type::Interface(interface_name.clone())), + None, + *line, + *column, + ); + } + } + + for property in properties { + if let Some(default_expr) = &property.default_value { + let default_type = self.infer_expression_type(default_expr); + if let Some(declared_type) = &property.property_type { + if !self.are_types_compatible(&default_type, declared_type) { + self.type_error( + format!( + "Default value type {default_type:?} incompatible with declared type {declared_type:?}" + ), + Some(declared_type.clone()), + Some(default_type), + property.line, + property.column, + ); + } + } + } + } + + for method in methods { + if let Statement::ActionDefinition { body, .. } = method { + for stmt in body { + self.check_statement_types(stmt); + } + } + } + + // Container type registration would be handled by analyzer + } + Statement::ContainerInstantiation { + container_type, + instance_name: _instance_name, + arguments: _arguments, + property_initializers, + line, + column, + } => { + if let Some(container_symbol) = self.analyzer.get_symbol(container_type) { + if container_symbol.symbol_type != Some(Type::Container(container_type.clone())) + { + self.type_error( + format!("'{container_type}' is not a container type"), + Some(Type::Container(container_type.clone())), + container_symbol.symbol_type.clone(), + *line, + *column, + ); + } + } else { + self.type_error( + format!("Container type '{container_type}' not found"), + Some(Type::Container(container_type.clone())), + None, *line, *column, ); } + + for initializer in property_initializers { + let _init_type = self.infer_expression_type(&initializer.value); + } + } + Statement::InterfaceDefinition { + name: _name, + extends: _extends, + required_actions: _required_actions, + line: _line, + column: _column, + } => { + // Interface type registration would be handled by analyzer } + Statement::EventDefinition { + name: _name, + parameters: _parameters, + line: _line, + column: _column, + } => {} + Statement::EventTrigger { + name: _name, + arguments: _arguments, + line: _line, + column: _column, + } => {} + Statement::EventHandler { + event_name: _event_name, + event_source: _event_source, + handler_body, + line: _line, + column: _column, + } => { + for stmt in handler_body { + self.check_statement_types(stmt); + } + } + Statement::ParentMethodCall { + method_name: _method_name, + arguments: _arguments, + line: _line, + column: _column, + } => {} } } @@ -716,17 +877,17 @@ impl TypeChecker { Literal::Pattern(_) => Type::Pattern, Literal::List(_) => Type::List(Box::new(Type::Any)), }, - Expression::Variable(name, line, column) => { + Expression::Variable(name, _line, _column) => { if let Some(symbol) = self.analyzer.get_symbol(name) { if let Some(var_type) = &symbol.symbol_type { var_type.clone() } else { self.type_error( - format!("Cannot determine type of variable '{}'", name), + format!("Cannot determine type of variable '{name}'"), None, None, - *line, - *column, + *_line, + *_column, ); Type::Unknown } @@ -745,11 +906,11 @@ impl TypeChecker { } else { // Add an error for undefined variable self.type_error( - format!("Variable '{}' is not defined", name), + format!("Variable '{name}' is not defined"), None, None, - *line, - *column, + *_line, + *_column, ); Type::Error } @@ -785,8 +946,7 @@ impl TypeChecker { } else { self.type_error( format!( - "Cannot perform {:?} operation on {} and {}", - operator, left_type, right_type + "Cannot perform {operator:?} operation on {left_type} and {right_type}" ), Some(Type::Number), Some(if left_type != Type::Number { @@ -805,10 +965,7 @@ impl TypeChecker { && !self.are_types_compatible(&right_type, &left_type) { self.type_error( - format!( - "Cannot compare {} and {} for equality", - left_type, right_type - ), + format!("Cannot compare {left_type} and {right_type} for equality"), Some(left_type.clone()), Some(right_type), *line, @@ -830,8 +987,7 @@ impl TypeChecker { } else { self.type_error( format!( - "Cannot compare {} and {} with {:?}", - left_type, right_type, operator + "Cannot compare {left_type} and {right_type} with {operator:?}" ), Some(if left_type == Type::Number || left_type == Type::Text { left_type.clone() @@ -851,8 +1007,7 @@ impl TypeChecker { } else { self.type_error( format!( - "Cannot perform logical {:?} on {} and {}", - operator, left_type, right_type + "Cannot perform logical {operator:?} on {left_type} and {right_type}" ), Some(Type::Boolean), Some(if left_type != Type::Boolean { @@ -871,8 +1026,7 @@ impl TypeChecker { if !self.are_types_compatible(item_type, &right_type) { self.type_error( format!( - "Cannot check if {} contains {}, list items are {}", - left_type, right_type, item_type + "Cannot check if {left_type} contains {right_type}, list items are {item_type}" ), Some(*item_type.clone()), Some(right_type), @@ -888,8 +1042,7 @@ impl TypeChecker { if !self.are_types_compatible(key_type, &right_type) { self.type_error( format!( - "Cannot check if {} contains {}, map keys are {}", - left_type, right_type, key_type + "Cannot check if {left_type} contains {right_type}, map keys are {key_type}" ), Some(*key_type.clone()), Some(right_type), @@ -904,10 +1057,7 @@ impl TypeChecker { Type::Text => { if right_type != Type::Text { self.type_error( - format!( - "Cannot check if {} contains {}", - left_type, right_type - ), + format!("Cannot check if {left_type} contains {right_type}"), Some(Type::Text), Some(right_type), *line, @@ -920,7 +1070,7 @@ impl TypeChecker { } _ => { self.type_error( - format!("Cannot check if {} contains {}", left_type, right_type), + format!("Cannot check if {left_type} contains {right_type}"), Some(Type::List(Box::new(Type::Unknown))), Some(left_type), *line, @@ -949,7 +1099,7 @@ impl TypeChecker { Type::Boolean } else { self.type_error( - format!("Cannot apply 'not' to {}", expr_type), + format!("Cannot apply 'not' to {expr_type}"), Some(Type::Boolean), Some(expr_type), *line, @@ -963,7 +1113,7 @@ impl TypeChecker { Type::Number } else { self.type_error( - format!("Cannot negate {}", expr_type), + format!("Cannot negate {expr_type}"), Some(Type::Number), Some(expr_type), *line, @@ -1033,7 +1183,7 @@ impl TypeChecker { Type::Unknown | Type::Error => Type::Unknown, _ => { self.type_error( - format!("Cannot call {}, not a function", function_type), + format!("Cannot call {function_type}, not a function"), Some(Type::Function { parameters: vec![], return_type: Box::new(Type::Unknown), @@ -1049,8 +1199,8 @@ impl TypeChecker { Expression::MemberAccess { object, property, - line, - column, + line: _line, + column: _column, } => { let object_type = self.infer_expression_type(object); @@ -1063,11 +1213,11 @@ impl TypeChecker { Type::Unknown => Type::Unknown, _ => { self.type_error( - format!("Cannot access property '{}' on {}", property, object_type), + format!("Cannot access property '{property}' on {object_type}"), Some(Type::Custom("Object".to_string())), Some(object_type), - *line, - *column, + *_line, + *_column, ); Type::Error } @@ -1090,7 +1240,7 @@ impl TypeChecker { Type::List(item_type) => { if index_type != Type::Number { self.type_error( - format!("List index must be a number, got {}", index_type), + format!("List index must be a number, got {index_type}"), Some(Type::Number), Some(index_type), *line, @@ -1104,7 +1254,7 @@ impl TypeChecker { Type::Map(key_type, value_type) => { if !self.are_types_compatible(&key_type, &index_type) { self.type_error( - format!("Map key must be {}, got {}", key_type, index_type), + format!("Map key must be {key_type}, got {index_type}"), Some(*key_type.clone()), Some(index_type), *line, @@ -1118,7 +1268,7 @@ impl TypeChecker { Type::Text => { if index_type != Type::Number { self.type_error( - format!("Text index must be a number, got {}", index_type), + format!("Text index must be a number, got {index_type}"), Some(Type::Number), Some(index_type), *line, @@ -1132,7 +1282,7 @@ impl TypeChecker { Type::Unknown => Type::Unknown, _ => { self.type_error( - format!("Cannot index into {}", collection_type), + format!("Cannot index into {collection_type}"), Some(Type::List(Box::new(Type::Unknown))), Some(collection_type), *line, @@ -1145,8 +1295,8 @@ impl TypeChecker { Expression::Concatenation { left, right, - line, - column, + line: _line, + column: _column, } => { let left_type = self.infer_expression_type(left); let right_type = self.infer_expression_type(right); @@ -1161,15 +1311,15 @@ impl TypeChecker { Type::Text } else { self.type_error( - format!("Cannot concatenate {} and {}", left_type, right_type), + format!("Cannot concatenate {left_type} and {right_type}"), Some(Type::Text), Some(if left_type != Type::Text && left_type != Type::Number { left_type } else { right_type }), - *line, - *column, + *_line, + *_column, ); Type::Error } @@ -1180,7 +1330,7 @@ impl TypeChecker { if text_type != Type::Text { self.type_error( - format!("Expected Text for pattern matching, got {}", text_type), + format!("Expected Text for pattern matching, got {text_type}"), Some(Type::Text), Some(text_type), 0, @@ -1190,10 +1340,7 @@ impl TypeChecker { if pattern_type != Type::Pattern && pattern_type != Type::Text { self.type_error( - format!( - "Expected Pattern for pattern matching, got {}", - pattern_type - ), + format!("Expected Pattern for pattern matching, got {pattern_type}"), Some(Type::Pattern), Some(pattern_type), 0, @@ -1209,7 +1356,7 @@ impl TypeChecker { if text_type != Type::Text { self.type_error( - format!("Expected Text for pattern finding, got {}", text_type), + format!("Expected Text for pattern finding, got {text_type}"), Some(Type::Text), Some(text_type), 0, @@ -1219,7 +1366,7 @@ impl TypeChecker { if pattern_type != Type::Pattern && pattern_type != Type::Text { self.type_error( - format!("Expected Pattern for pattern finding, got {}", pattern_type), + format!("Expected Pattern for pattern finding, got {pattern_type}"), Some(Type::Pattern), Some(pattern_type), 0, @@ -1241,7 +1388,7 @@ impl TypeChecker { if text_type != Type::Text { self.type_error( - format!("Expected Text for pattern replacement, got {}", text_type), + format!("Expected Text for pattern replacement, got {text_type}"), Some(Type::Text), Some(text_type), 0, @@ -1251,10 +1398,7 @@ impl TypeChecker { if pattern_type != Type::Pattern && pattern_type != Type::Text { self.type_error( - format!( - "Expected Pattern for pattern replacement, got {}", - pattern_type - ), + format!("Expected Pattern for pattern replacement, got {pattern_type}"), Some(Type::Pattern), Some(pattern_type), 0, @@ -1264,7 +1408,7 @@ impl TypeChecker { if replacement_type != Type::Text { self.type_error( - format!("Expected Text for replacement, got {}", replacement_type), + format!("Expected Text for replacement, got {replacement_type}"), Some(Type::Text), Some(replacement_type), 0, @@ -1280,7 +1424,7 @@ impl TypeChecker { if text_type != Type::Text { self.type_error( - format!("Expected Text for pattern splitting, got {}", text_type), + format!("Expected Text for pattern splitting, got {text_type}"), Some(Type::Text), Some(text_type), 0, @@ -1290,10 +1434,7 @@ impl TypeChecker { if pattern_type != Type::Pattern && pattern_type != Type::Text { self.type_error( - format!( - "Expected Pattern for pattern splitting, got {}", - pattern_type - ), + format!("Expected Pattern for pattern splitting, got {pattern_type}"), Some(Type::Pattern), Some(pattern_type), 0, @@ -1314,7 +1455,7 @@ impl TypeChecker { Type::Async(inner_type) => *inner_type, _ => { self.type_error( - format!("Cannot await non-async value of type {}", expr_type), + format!("Cannot await non-async value of type {expr_type}"), Some(Type::Async(Box::new(Type::Unknown))), Some(expr_type), *line, @@ -1327,8 +1468,8 @@ impl TypeChecker { Expression::ActionCall { name, arguments, - line, - column, + line: _line, + column: _column, } => { let symbol_opt = self.analyzer.get_symbol(name); @@ -1342,11 +1483,11 @@ impl TypeChecker { return Type::Unknown; } else { self.type_error( - format!("Undefined action '{}'", name), + format!("Undefined action '{name}'"), None, None, - *line, - *column, + *_line, + *_column, ); return Type::Error; } @@ -1356,11 +1497,11 @@ impl TypeChecker { if symbol.symbol_type.is_none() { self.type_error( - format!("Cannot determine type of action '{}'", name), + format!("Cannot determine type of action '{name}'"), None, None, - *line, - *column, + *_line, + *_column, ); return Type::Unknown; } @@ -1382,8 +1523,8 @@ impl TypeChecker { ), None, None, - *line, - *column, + *_line, + *_column, ); return Type::Error; } @@ -1407,8 +1548,8 @@ impl TypeChecker { ), Some(param_type.clone()), Some(arg_type.clone()), - *line, - *column, + *_line, + *_column, ); return Type::Error; } @@ -1418,14 +1559,101 @@ impl TypeChecker { } _ => { self.type_error( - format!("'{}' is not an action", name), + format!("'{name}' is not an action"), Some(Type::Function { parameters: vec![], return_type: Box::new(Type::Unknown), }), Some(symbol_type), - *line, - *column, + *_line, + *_column, + ); + Type::Error + } + } + } + Expression::StaticMemberAccess { + container: _container, + member: _member, + line: _line, + column: _column, + } => { + // Check if the container exists + let _container_type = Type::Container(_container.clone()); + + // Look up the static member in the container + // For now, return Unknown type since we need to implement container symbol table + // TODO: Implement proper static member type lookup + + // This is a placeholder implementation + // In a full implementation, we would: + // 1. Check if the container exists in the symbol table + // 2. Check if the member exists as a static member in the container + // 3. Return the appropriate type based on the member's definition + + Type::Unknown + } + Expression::MethodCall { + object, + method: _method, + arguments, + line: _line, + column: _column, + } => { + // First, determine the type of the object + let object_type = self.infer_expression_type(object); + + // Check if the object is a container instance + match object_type { + Type::ContainerInstance(_container_name) => { + // Look up the method in the container + // For now, return Unknown type since we need to implement container method lookup + // TODO: Implement proper method type lookup + + // This is a placeholder implementation + // In a full implementation, we would: + // 1. Check if the container exists in the symbol table + // 2. Check if the method exists in the container + // 3. Check if the arguments match the method's parameters + // 4. Return the method's return type + + // Check argument types + for arg in arguments { + self.infer_expression_type(&arg.value); + } + + Type::Unknown + } + _ => { + self.type_error( + format!( + "Cannot call method '{_method}' on non-container type {object_type}" + ), + Some(Type::ContainerInstance(String::from("Unknown"))), + Some(object_type), + *_line, + *_column, + ); + Type::Error + } + } + } + Expression::PropertyAccess { + object, property, .. + } => { + let object_type = self.infer_expression_type(object); + match object_type { + Type::ContainerInstance(_container_name) => { + // For now, return Unknown type for property access + Type::Unknown + } + _ => { + self.type_error( + format!("Cannot access property '{property}' on non-container type"), + Some(Type::ContainerInstance("Unknown".to_string())), + Some(object_type), + 0, + 0, ); Type::Error } @@ -1702,6 +1930,8 @@ mod tests { name: "name".to_string(), param_type: Some(Type::Text), default_value: None, + line: 0, + column: 0, }], body: vec![Statement::DisplayStatement { value: Expression::Variable("name".to_string(), 2, 5), diff --git a/src/wfl_config/checker.rs b/src/wfl_config/checker.rs index ab88b249..18c88709 100644 --- a/src/wfl_config/checker.rs +++ b/src/wfl_config/checker.rs @@ -298,7 +298,7 @@ impl ConfigChecker { file_path: file_path.to_path_buf(), kind: ConfigIssueKind::UnknownKey, issue_type: ConfigIssueType::Warning, - message: format!("Unknown configuration key: {}", key), + message: format!("Unknown configuration key: {key}"), setting_name: Some(key.to_string()), line_number: Some(line_number + 1), fix_message: Some("Remove the line or correct the key name".to_string()), @@ -316,15 +316,14 @@ impl ConfigChecker { kind: ConfigIssueKind::InvalidType, issue_type: ConfigIssueType::Error, message: format!( - "Invalid type for {}: expected integer, got '{}'", - key, value + "Invalid type for {key}: expected integer, got '{value}'" ), setting_name: Some(key.to_string()), line_number: Some(line_number + 1), fix_message: setting .default_value .as_ref() - .map(|default| format!("Set to default value: {}", default)), + .map(|default| format!("Set to default value: {default}")), }); } } @@ -335,15 +334,14 @@ impl ConfigChecker { kind: ConfigIssueKind::InvalidType, issue_type: ConfigIssueType::Error, message: format!( - "Invalid type for {}: expected boolean (true/false), got '{}'", - key, value + "Invalid type for {key}: expected boolean (true/false), got '{value}'" ), setting_name: Some(key.to_string()), line_number: Some(line_number + 1), fix_message: setting .default_value .as_ref() - .map(|default| format!("Set to default value: {}", default)), + .map(|default| format!("Set to default value: {default}")), }); } } @@ -355,13 +353,12 @@ impl ConfigChecker { kind: ConfigIssueKind::InvalidValue, issue_type: ConfigIssueType::Error, message: format!( - "Invalid value for {}: expected one of {:?}, got '{}'", - key, valid_values, value + "Invalid value for {key}: expected one of {valid_values:?}, got '{value}'" ), setting_name: Some(key.to_string()), line_number: Some(line_number + 1), fix_message: setting.default_value.as_ref().map(|default| { - format!("Set to default value: {}", default) + format!("Set to default value: {default}") }), }); } @@ -375,13 +372,12 @@ impl ConfigChecker { kind: ConfigIssueKind::InvalidValue, issue_type: ConfigIssueType::Error, message: format!( - "Invalid value for {}: expected one of {:?}, got '{}'", - key, valid_values, value + "Invalid value for {key}: expected one of {valid_values:?}, got '{value}'" ), setting_name: Some(key.to_string()), line_number: Some(line_number + 1), fix_message: setting.default_value.as_ref().map(|default| { - format!("Set to default value: {}", default) + format!("Set to default value: {default}") }), }); } @@ -420,12 +416,13 @@ impl ConfigChecker { file_path: file_path.to_path_buf(), kind: ConfigIssueKind::MissingSetting, issue_type: ConfigIssueType::Error, - message: format!("Missing required setting: {}", key), + message: format!("Missing required setting: {key}"), setting_name: Some(key.to_string()), line_number: None, - fix_message: setting.default_value.as_ref().map(|default| { - format!("Add '{}' with default value: {}", key, default) - }), + fix_message: setting + .default_value + .as_ref() + .map(|default| format!("Add '{key}' with default value: {default}")), }); } } @@ -478,7 +475,7 @@ impl ConfigChecker { if line_number <= lines.len() { lines[line_number - 1] = format!("# {} (unknown key)", lines[line_number - 1]); - println!("✅ Commented out unknown key at line {}", line_number); + println!("✅ Commented out unknown key at line {line_number}"); } } } @@ -490,10 +487,9 @@ impl ConfigChecker { if let Some(setting) = self.expected_settings.get(setting_name) { if let Some(default_value) = &setting.default_value { lines[line_number - 1] = - format!("{} = {}", setting_name, default_value); + format!("{setting_name} = {default_value}"); println!( - "✅ Fixed value for '{}' at line {}", - setting_name, line_number + "✅ Fixed value for '{setting_name}' at line {line_number}" ); } } @@ -507,9 +503,9 @@ impl ConfigChecker { if !added_settings.contains_key(setting_name) { lines.push(String::new()); lines.push(format!("# {}", setting.description)); - lines.push(format!("{} = {}", setting_name, default_value)); + lines.push(format!("{setting_name} = {default_value}")); added_settings.insert(setting_name.clone(), true); - println!("✅ Added missing setting: {}", setting_name); + println!("✅ Added missing setting: {setting_name}"); } } } @@ -558,19 +554,16 @@ impl ConfigChecker { if let Some(fix_message) = &issue.fix_message { if fix_mode { - println!(" Fix: {}", fix_message); + println!(" Fix: {fix_message}"); } else { - println!(" Suggested fix: {}", fix_message); + println!(" Suggested fix: {fix_message}"); } } println!(); } - println!( - "{} errors, {} warnings found in configuration files", - error_count, warning_count - ); + println!("{error_count} errors, {warning_count} warnings found in configuration files"); if !fix_mode && error_count > 0 { println!("\n🛠️ Run 'wfl --configFix' to automatically fix these issues"); @@ -640,7 +633,7 @@ max_line_length = 80 fs::write(&config_path, config_content).unwrap(); let issues = checker.check_config_file(&config_path).unwrap(); - assert!(issues.is_empty(), "Expected no issues, got: {:?}", issues); + assert!(issues.is_empty(), "Expected no issues, got: {issues:?}"); } #[test] @@ -706,8 +699,7 @@ unknown_setting = value assert!( issues.is_empty(), - "Expected no issues after fix, got: {:?}", - issues + "Expected no issues after fix, got: {issues:?}" ); } @@ -732,15 +724,13 @@ timeout_seconds = potato assert!( issues_after.is_empty(), - "Expected no issues after fix, got: {:?}", - issues_after + "Expected no issues after fix, got: {issues_after:?}" ); let content = fs::read_to_string(&config_path).unwrap(); assert!( content.contains("timeout_seconds = 60"), - "File content after fix: {}", - content + "File content after fix: {content}" ); } } diff --git a/tests/action_tests.rs b/tests/action_tests.rs index 15c87464..5e6d2d3c 100644 --- a/tests/action_tests.rs +++ b/tests/action_tests.rs @@ -55,7 +55,7 @@ fn test_action_call_parses() { _ => panic!("Expected string literal, got {:?}", arguments[0].value), } } - _ => panic!("Expected ActionCall, got {:?}", expression), + _ => panic!("Expected ActionCall, got {expression:?}"), }, _ => panic!( "Expected ExpressionStatement, got {:?}", @@ -97,13 +97,13 @@ async fn test_action_call_executes() { let mut interpreter = Interpreter::new(); let result = interpreter.interpret(&program).await; - assert!(result.is_ok(), "Failed to execute program: {:?}", result); + assert!(result.is_ok(), "Failed to execute program: {result:?}"); let env = interpreter.global_env(); let result_value = env.borrow().get("result").expect("Result not found"); match result_value { Value::Number(n) => assert_eq!(n, 42.0), - _ => panic!("Expected number, got {:?}", result_value), + _ => panic!("Expected number, got {result_value:?}"), } } diff --git a/tests/cli_tests.rs b/tests/cli_tests.rs index 3642b395..a0f2cec1 100644 --- a/tests/cli_tests.rs +++ b/tests/cli_tests.rs @@ -31,10 +31,7 @@ display myVariable // Run the binary with --lint file_path --fix --diff let file_path_str = file_path.to_str().unwrap(); - println!( - "Running: {:?} --lint {} --fix {} --diff", - binary_path, file_path_str, file_path_str - ); + println!("Running: {binary_path:?} --lint {file_path_str} --fix {file_path_str} --diff"); let output = Command::new(binary_path) .args(["--lint", file_path_str, "--fix", file_path_str, "--diff"]) @@ -42,7 +39,7 @@ display myVariable .expect("Failed to execute command"); // Check that the command succeeded - assert!(output.status.success(), "Command failed: {:?}", output); + assert!(output.status.success(), "Command failed: {output:?}"); // Convert output to string let output_str = String::from_utf8_lossy(&output.stdout); @@ -50,12 +47,10 @@ display myVariable // Check that the diff contains the expected replacement assert!( output_str.contains("-store myVariable as 42"), - "Diff doesn't contain the original line: {}", - output_str + "Diff doesn't contain the original line: {output_str}" ); assert!( output_str.contains("+store my_variable as 42"), - "Diff doesn't contain the fixed line: {}", - output_str + "Diff doesn't contain the fixed line: {output_str}" ); } diff --git a/tests/control_flow.rs b/tests/control_flow.rs index 261a903f..c707820c 100644 --- a/tests/control_flow.rs +++ b/tests/control_flow.rs @@ -6,15 +6,13 @@ use wfl::parser::Parser; async fn execute_wfl(code: &str) -> Result { let tokens = lex_wfl_with_positions(code); let mut parser = Parser::new(&tokens); - let program = parser - .parse() - .map_err(|e| format!("Parse error: {:?}", e))?; + let program = parser.parse().map_err(|e| format!("Parse error: {e:?}"))?; let mut interpreter = Interpreter::default(); interpreter .interpret(&program) .await - .map_err(|e| format!("Runtime error: {:?}", e)) + .map_err(|e| format!("Runtime error: {e:?}")) } #[tokio::test] diff --git a/tests/interpreter/container_tests.rs b/tests/interpreter/container_tests.rs new file mode 100644 index 00000000..dc905a92 --- /dev/null +++ b/tests/interpreter/container_tests.rs @@ -0,0 +1,231 @@ +use wfl::interpreter::Interpreter; +use wfl::parser::Parser; +use wfl::lexer::Lexer; +use wfl::interpreter::value::Value; +use tokio; + +#[tokio::test] +async fn test_container_instantiation() { + let input = r#" +create container Person: + property name: Text + property age: Number +end + +create new Person as alice: + name = "Alice" + age = 28 +"#; + + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + let mut interpreter = Interpreter::new(); + let result = interpreter.interpret(&program).await; + + assert!(result.is_ok(), "Container instantiation should succeed"); +} + +#[tokio::test] +async fn test_container_method_call() { + let input = r#" +create container Person: + property name: Text + property age: Number + + action greet: + display "Hello, I am " + this.name + " and I am " + this.age + "." + end +end + +create new Person as alice: + name = "Alice" + age = 28 + +alice.greet() +"#; + + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + let mut interpreter = Interpreter::new(); + let result = interpreter.interpret(&program).await; + + assert!(result.is_ok(), "Container method call should succeed"); +} + +#[tokio::test] +async fn test_container_property_access() { + let input = r#" +create container Person: + property name: Text + property age: Number +end + +create new Person as alice: + name = "Alice" + age = 28 + +display alice.name +display alice.age +"#; + + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + let mut interpreter = Interpreter::new(); + let result = interpreter.interpret(&program).await; + + assert!(result.is_ok(), "Container property access should succeed"); +} + +#[tokio::test] +async fn test_container_inheritance() { + let input = r#" +create container Animal: + property species: Text + + action speak: + display "Animal sound" + end +end + +create container Dog extends Animal: + property breed: Text + + action speak: + display "Woof!" + end +end + +create new Dog as buddy: + species = "Canine" + breed = "Golden Retriever" + +buddy.speak() +display buddy.species +"#; + + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + let mut interpreter = Interpreter::new(); + let result = interpreter.interpret(&program).await; + + assert!(result.is_ok(), "Container inheritance should work"); +} + +#[tokio::test] +async fn test_undefined_method_call_failure() { + let input = r#" +create container Person: + property name: Text +end + +create new Person as alice: + name = "Alice" + +alice.undefined_method() +"#; + + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + let mut interpreter = Interpreter::new(); + let result = interpreter.interpret(&program).await; + + assert!(result.is_err(), "Calling undefined method should fail"); +} + +#[tokio::test] +async fn test_undefined_property_access_failure() { + let input = r#" +create container Person: + property name: Text +end + +create new Person as alice: + name = "Alice" + +display alice.undefined_property +"#; + + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + let mut interpreter = Interpreter::new(); + let result = interpreter.interpret(&program).await; + + assert!(result.is_err(), "Accessing undefined property should fail"); +} + +#[tokio::test] +async fn test_static_member_access() { + let input = r#" +create container Math: + static property PI: Number = 3.14159 + + static action square needs value: Number: Number + return value * value + end +end + +display Math.PI +store Math.square(5) in result +display result +"#; + + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + let mut interpreter = Interpreter::new(); + let result = interpreter.interpret(&program).await; + + assert!(result.is_ok(), "Static member access should work"); +} + +#[tokio::test] +async fn test_interface_implementation() { + let input = r#" +create interface Drawable: + action draw: +end + +create container Circle implements Drawable: + property radius: Number + + action draw: + display "Drawing a circle with radius " + this.radius + end +end + +create new Circle as c: + radius = 5 + +c.draw() +"#; + + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + let mut interpreter = Interpreter::new(); + let result = interpreter.interpret(&program).await; + + assert!(result.is_ok(), "Interface implementation should work"); +} diff --git a/tests/parser/container_err.rs b/tests/parser/container_err.rs new file mode 100644 index 00000000..4a551f0e --- /dev/null +++ b/tests/parser/container_err.rs @@ -0,0 +1,103 @@ +use wfl::parser::Parser; +use wfl::lexer::Lexer; + +#[test] +fn test_missing_colon_after_container_name() { + let input = r#" +create container Person +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let result = parser.parse(); + + assert!(result.is_err(), "Expected parse error for missing colon"); +} + +#[test] +fn test_missing_end_keyword() { + let input = r#" +create container Person: + property name: Text +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let result = parser.parse(); + + assert!(result.is_err(), "Expected parse error for missing end keyword"); +} + +#[test] +fn test_invalid_property_syntax() { + let input = r#" +create container Person: + property name +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let result = parser.parse(); + + assert!(result.is_err(), "Expected parse error for invalid property syntax"); +} + +#[test] +fn test_undefined_parent_class() { + let input = r#" +create container Dog extends UndefinedAnimal: +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let result = parser.parse(); + + assert!(result.is_ok(), "Parser should succeed, typechecker should catch error"); +} + +#[test] +fn test_invalid_method_syntax() { + let input = r#" +create container Person: + action greet + display "Hello" + end +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let result = parser.parse(); + + assert!(result.is_err(), "Expected parse error for missing colon after action"); +} + +#[test] +fn test_invalid_instantiation_syntax() { + let input = r#" +create new Person alice: +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let result = parser.parse(); + + assert!(result.is_err(), "Expected parse error for missing 'as' keyword"); +} + +#[test] +fn test_empty_container_body() { + let input = r#" +create container Person: +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let result = parser.parse(); + + assert!(result.is_ok(), "Empty container body should be valid"); +} diff --git a/tests/parser/container_ok.rs b/tests/parser/container_ok.rs new file mode 100644 index 00000000..da2d6d31 --- /dev/null +++ b/tests/parser/container_ok.rs @@ -0,0 +1,117 @@ +use wfl::parser::Parser; +use wfl::lexer::Lexer; + +#[test] +fn test_basic_container_definition() { + let input = r#" +create container Person: +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + assert_eq!(program.statements.len(), 1); +} + +#[test] +fn test_container_with_properties() { + let input = r#" +create container Person: + property name: Text + property age: Number +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + assert_eq!(program.statements.len(), 1); +} + +#[test] +fn test_container_with_methods() { + let input = r#" +create container Person: + action greet: + display "Hello" + end +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + assert_eq!(program.statements.len(), 1); +} + +#[test] +fn test_container_instantiation() { + let input = r#" +create container Person: +end + +create new Person as alice: +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + assert_eq!(program.statements.len(), 2); +} + +#[test] +fn test_container_with_inheritance() { + let input = r#" +create container Animal: +end + +create container Dog extends Animal: +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + assert_eq!(program.statements.len(), 2); +} + +#[test] +fn test_container_with_interface_implementation() { + let input = r#" +create interface Drawable: +end + +create container Shape implements Drawable: +end +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + assert_eq!(program.statements.len(), 2); +} + +#[test] +fn test_container_with_property_initializers() { + let input = r#" +create container Person: + property name: Text +end + +create new Person as alice: + name = "Alice" +"#; + let mut lexer = Lexer::new(input); + let tokens = lexer.tokenize().expect("Failed to tokenize"); + let mut parser = Parser::new(tokens); + let program = parser.parse().expect("Failed to parse program"); + + assert_eq!(program.statements.len(), 2); +} diff --git a/tests/step_mode.rs b/tests/step_mode.rs index 12649072..39c88ec6 100644 --- a/tests/step_mode.rs +++ b/tests/step_mode.rs @@ -27,8 +27,7 @@ display x let output_str = String::from_utf8_lossy(&output_no_step.stdout); assert!( !output_str.contains("continue (y/n)?"), - "Output shouldn't contain step mode prompts: {}", - output_str + "Output shouldn't contain step mode prompts: {output_str}" ); } @@ -65,15 +64,13 @@ store y as 100 let output_str = String::from_utf8_lossy(&output.stdout); assert!( output_str.contains("continue (y/n)?"), - "Output should contain step mode prompts: {}", - output_str + "Output should contain step mode prompts: {output_str}" ); let prompt_count = output_str.matches("continue (y/n)?").count(); assert!( prompt_count >= 1, - "Expected at least 1 prompt, got {}", - prompt_count + "Expected at least 1 prompt, got {prompt_count}" ); } @@ -122,18 +119,15 @@ main let output_str = String::from_utf8_lossy(&output.stdout); assert!( output_str.contains("Boot phase: Configuration loaded"), - "Output should show boot phase: {}", - output_str + "Output should show boot phase: {output_str}" ); assert!( output_str.contains("continue (y/n)?"), - "Output should contain prompts: {}", - output_str + "Output should contain prompts: {output_str}" ); assert!( output_str.contains("Program has 4 statements"), - "Output should show program statement count: {}", - output_str + "Output should show program statement count: {output_str}" ); } @@ -171,13 +165,11 @@ end count let output_str = String::from_utf8_lossy(&output.stdout); assert!( output_str.contains("loopcounter"), - "Output should show loopcounter variable: {}", - output_str + "Output should show loopcounter variable: {output_str}" ); assert!( output_str.contains("Count: 1"), - "Output should show Count: 1: {}", - output_str + "Output should show Count: 1: {output_str}" ); } @@ -214,7 +206,6 @@ display x let prompt_count = output_str.matches("continue (y/n)?").count(); assert!( prompt_count >= 1, - "Expected at least one prompt, got {}", - prompt_count + "Expected at least one prompt, got {prompt_count}" ); }