Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4792dc9
Implement hybrid module system with include and export statements
claude[bot] Jan 31, 2026
d94e5ab
Merge branch 'main' into claude/issue-245-20260131-0854
logbie Jan 31, 2026
0672585
fix: Improve module system validation and restrictions
claude[bot] Jan 31, 2026
ab6a499
Merge branch 'main' into claude/issue-245-20260131-0854
logbie Jan 31, 2026
9cd99aa
Merge branch 'main' into claude/issue-245-20260131-0854
logbie Jan 31, 2026
2588bc5
Allow included files to modify parent variables
logbie Jan 31, 2026
49fe638
Disallow exporting mutable variables as constants
logbie Jan 31, 2026
59e4d69
Refactor tests to use temporary files
logbie Jan 31, 2026
b9326cf
Update tests to expect successful export parsing
logbie Jan 31, 2026
d139706
Refactor include statement tests for better isolation
logbie Jan 31, 2026
60e8f79
Refactor include tests to focus on interpretation
logbie Jan 31, 2026
08fde7c
Refactors tests for async interpreter and syntax updates
logbie Jan 31, 2026
7c42bdd
Refactor tests to use panic for clearer failures
logbie Jan 31, 2026
c8e4f54
Fix borrow checker error in export type checking
logbie Jan 31, 2026
8577398
Preserve variable mutability in included files
logbie Jan 31, 2026
b472a7f
Restrict exports to definitions in the local scope
logbie Jan 31, 2026
7a78a20
test: Set source file path in interpreter tests
logbie Jan 31, 2026
4084658
Enables test for mutable variables in includes
logbie Jan 31, 2026
273f4d5
Merge branch 'main' into claude/issue-245-20260131-0854
logbie Feb 4, 2026
6650c6b
Merge branch 'main' into claude/issue-245-20260131-0854
logbie Feb 4, 2026
a102d87
fix: Address minor issues in module system implementation
claude[bot] Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"Bash(gh pr view:*)",
"Bash(git merge:*)",
"Bash(git push:*)",
"Bash(gh pr checks:*)"
"Bash(gh pr checkout:*)",
"Bash(git pull:*)"
],
Expand Down
173 changes: 129 additions & 44 deletions Docs/04-advanced-features/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,85 @@ WFL's module system allows you to organize code across multiple files, enabling

## Basic Module Loading

WFL provides two ways to include code from other files:

1. **Load Module** - Isolated execution (existing behavior)
2. **Include** - Parent scope execution (NEW in V2)

## Load Module Statement

Load code from another WFL file using the `load module from` statement:

```wfl
load module from "utilities.wfl"
```

This reads, parses, and executes the specified file. The module runs in its own scope but can access variables from the parent file.
This reads, parses, and executes the specified file. The module runs in its own isolated scope but can access variables from the parent file.

### Simple Example
## Include Statement

Include code from another WFL file using the `include from` statement:

**helper.wfl:**
```wfl
store message as "Hello from helper module"
display message
include from "containers.wfl"
```

**main.wfl:**
This reads, parses, and executes the specified file in the parent scope, making all definitions available to the parent.

## When to Use Each Approach

### Use `load module from` for:
- **Initialization and setup** - Module side effects
- **Utility scripts** - Self-contained operations
- **Protection** - When you don't want module definitions affecting parent scope

### Use `include from` for:
- **Shared libraries** - Container definitions, common actions
- **Configuration** - Constants and shared variables
- **Code organization** - Breaking large files into smaller, manageable pieces

## Comparison Example

**containers.wfl:**
```wfl
display "Loading helper..."
load module from "helper.wfl"
display "Helper loaded"
create container Person:
property name: Text
property age: Number
end

store utility_value as "shared utility"
```

**Output:**
### Using Include (Exposes Definitions)

**include_example.wfl:**
```wfl
include from "containers.wfl"

# Person container is available!
create new Person as alice:
name is "Alice"
age is 30
end

# Variables are also available
display utility_value # Works: "shared utility"
```
Loading helper...
Hello from helper module
Helper loaded

### Using Load Module (Isolated)

**load_example.wfl:**
```wfl
load module from "containers.wfl"

# Person container is NOT available
create new Person as bob: # Error: Container type 'Person' not found
name is "Bob"
age is 25
end

# Variables are also NOT available
display utility_value # Error: Variable 'utility_value' not found
```

## How Modules Work
Expand Down Expand Up @@ -459,56 +510,81 @@ end try

## Limitations

### Current Limitations (V1)
## Export Statement

1. **No Export Mechanism**
- Modules cannot expose variables/actions to parent
- All definitions stay in module scope
- Future: `export` keyword planned
WFL includes an `export` statement that documents and validates module interfaces:

2. **No Namespace Control**
```wfl
# Define items in a module
create container Person:
property name: Text
property age: Number
end

define action called greet:
display "Hello!"
end

store constant VERSION as "1.0.0"

Copilot AI Jan 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent syntax usage: the correct WFL syntax should be 'store new constant VERSION as' based on other examples in the documentation and test files.

Suggested change
store constant VERSION as "1.0.0"
store new constant VERSION as "1.0.0"

Copilot uses AI. Check for mistakes.

# Document which items are intended for external use
export container Person
export action greet
export constant VERSION
```

**Current Benefits:**
- **Documentation**: Makes module interface explicit and clear
- **Validation**: Ensures exported items actually exist at compile/lint time
- **Best Practices**: Encourages conscious design of module interfaces

**Future Enhancements:** The export statement establishes the foundation for selective module exposure and namespace control in future WFL versions.

### Current Limitations (V2)

1. **No Namespace Control**
- Cannot load module with custom name
- Future: `load module from "x.wfl" as name` planned

3. **No Selective Imports**
2. **No Selective Imports**
- Must load entire module
- Future: `load function1, function2 from "x.wfl"` planned

4. **No Module Caching**
3. **No Module Caching**
- Each `load module` re-parses and re-executes
- Multiple loads of same file execute multiple times
- Future: Optional caching planned

5. **Semantic Analysis Limitation**
- Modules are analyzed independently
- Cannot reference parent variables during analysis
- Runtime execution works, but analysis may show warnings
4. **Export Foundation Only**
- Export statements validate but don't yet enable selective exposure
- Future: Full namespace and selective import system

### Workarounds

**For shared functionality**, use side effects:
**For shared containers and functions**, use `include`:

```wfl
# Instead of exporting functions, use files

# logger.wfl
create file at "app.log" with ""
# containers.wfl
create container Person:
property name: Text
property age: Number
end

# main.wfl
load module from "logger.wfl"
# Now app.log exists and can be used
include from "containers.wfl"
# Person container is now available
```

**For configuration**, use parent-to-module flow:
**For initialization**, use `load module`:

```wfl
# main.wfl
store api_key as "secret123"
store db_host as "localhost"

load module from "app_init.wfl"
# init.wfl
create file at "app.log" with ""
display "System initialized"

# app_init.wfl reads these from parent scope
# main.wfl
load module from "init.wfl"
# Side effects occur but no definitions exposed
```

## Common Patterns
Expand Down Expand Up @@ -712,11 +788,20 @@ load module from "package:json-parser"

## Summary

- Use `load module from "path.wfl"` to include other WFL files
- Modules execute in child scope with parent read access
WFL's hybrid module system provides flexible code organization:

- **`load module from "path.wfl"`** - Isolated execution for initialization and side effects
- **`include from "path.wfl"`** - Parent scope execution for shared libraries and containers
- **`export container/action/constant NAME`** - Foundation for future namespace system
- Paths resolve relative to the including file
- Circular dependencies are automatically detected
- Modules are best for initialization and side effects
- Variables defined in modules stay local to that module
- Full error handling and type checking for all statements

### Quick Decision Guide

- **Need container definitions available?** → Use `include from`
- **Need shared actions and constants?** → Use `include from`
- **Need initialization without exposing definitions?** → Use `load module from`
- **Want to prepare for future selective exports?** → Add `export` statements

Modules enable better code organization and reusability in WFL projects. Start with simple includes and build up to more complex module hierarchies as your project grows.
The hybrid system solves the fundamental limitation while maintaining backward compatibility. Start with simple includes and build up to more complex module hierarchies as your project grows.
25 changes: 23 additions & 2 deletions src/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,11 @@ impl Analyzer {

/// Create an analyzer with parent scope variables
/// Parent variables are added as read-only (immutable) to prevent modification warnings
pub fn with_parent_variables(parent_vars: HashMap<String, Type>) -> Self {
pub fn with_parent_variables(parent_vars: HashMap<String, (Type, bool)>) -> Self {
let mut analyzer = Self::new();

// Add parent variables as read-only symbols
for (name, var_type) in parent_vars {
for (name, (var_type, _is_mutable)) in parent_vars {
let symbol = Symbol {
name: name.clone(),
kind: SymbolKind::Variable { mutable: false }, // Read-only!
Expand All @@ -337,6 +337,27 @@ impl Analyzer {
analyzer
}

/// Create an analyzer with parent variables preserving their mutability (used for includes)
pub fn with_parent_variables_mutable(parent_vars: HashMap<String, (Type, bool)>) -> Self {
let mut analyzer = Self::new();

// Add parent variables preserving their mutability from parent scope
for (name, (var_type, is_mutable)) in parent_vars {
let symbol = Symbol {
name: name.clone(),
kind: SymbolKind::Variable {
mutable: is_mutable,
}, // Preserve mutability
symbol_type: Some(var_type),
line: 0,
column: 0,
};
let _ = analyzer.current_scope.define(symbol);
}

analyzer
}

pub fn is_builtin_function(name: &str) -> bool {
crate::builtins::is_builtin_function(name)
}
Expand Down
5 changes: 5 additions & 0 deletions src/interpreter/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ impl Environment {
Err(format!("Undefined variable '{name}'"))
}

/// Get a value from the local scope only (does not check parent scopes)
pub fn get_local(&self, name: &str) -> Option<Value> {
self.values.get(name).cloned()
}

pub fn get(&self, name: &str) -> Option<Value> {
// Check local scope first
if let Some(value) = self.values.get(name) {
Expand Down
Loading