Skip to content

Commit 1a76d6f

Browse files
committed
Add module capabilities to the VM
1 parent 6d8a309 commit 1a76d6f

File tree

5 files changed

+322
-0
lines changed

5 files changed

+322
-0
lines changed

lisp/modules/greetings.lisp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
; Greetings module
2+
3+
(module greetings
4+
(export hello goodbye welcome)
5+
6+
(def hello (fn (name)
7+
(println (list "Hello, " name "!"))))
8+
9+
(def goodbye (fn (name)
10+
(println (list "Goodbye, " name "!"))))
11+
12+
(def welcome (fn (name place)
13+
(println (list "Welcome to " place ", " name "!")))))

lisp/modules/math.lisp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
; Math module
2+
3+
(module math
4+
(export square cube abs double add negate)
5+
6+
(def square (fn (x) (* x x)))
7+
8+
(def cube (fn (x) (* x x x)))
9+
10+
(def abs (fn (x) (if (< x 0) (- 0 x) x)))
11+
12+
(def double (fn (x) (* x 2)))
13+
14+
(def add (fn (a b) (+ a b)))
15+
16+
(def negate (fn (x) (- 0 x)))
17+
18+
; Private helper - not exported
19+
(def internal-constant 42))

lisp/modules/predicates.lisp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
; Predicates module
2+
3+
(module predicates
4+
(export positive? negative? zero? even? odd?)
5+
6+
(def positive? (fn (x) (> x 0)))
7+
8+
(def negative? (fn (x) (< x 0)))
9+
10+
(def zero? (fn (x) (= x 0)))
11+
12+
(def even? (fn (x) (= (mod x 2) 0)))
13+
14+
(def odd? (fn (x) (= (mod x 2) 1))))

lisp/test_modules.lisp

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
; Test module system functionality
2+
; This test demonstrates multi-file module loading and interop
3+
4+
; ============================================================================
5+
; Load standard library and modules from separate files
6+
; ============================================================================
7+
(load "lisp/stdlib.lisp")
8+
(load "lisp/modules/math.lisp")
9+
(load "lisp/modules/predicates.lisp")
10+
(load "lisp/modules/greetings.lisp")
11+
12+
; ============================================================================
13+
; Test 1: Basic qualified access
14+
; ============================================================================
15+
(println "Test 1: Qualified access (module/function)")
16+
17+
(if (= (math/square 5) 25)
18+
(println " math/square: passed")
19+
(println " math/square: FAILED"))
20+
21+
(if (= (math/cube 3) 27)
22+
(println " math/cube: passed")
23+
(println " math/cube: FAILED"))
24+
25+
(if (= (math/abs -42) 42)
26+
(println " math/abs: passed")
27+
(println " math/abs: FAILED"))
28+
29+
(if (= (math/double 7) 14)
30+
(println " math/double: passed")
31+
(println " math/double: FAILED"))
32+
33+
(if (= (math/negate 5) -5)
34+
(println " math/negate: passed")
35+
(println " math/negate: FAILED"))
36+
37+
; ============================================================================
38+
; Test 2: Selective import
39+
; ============================================================================
40+
(println "")
41+
(println "Test 2: Selective import")
42+
43+
(import math (square cube))
44+
45+
(if (= (square 6) 36)
46+
(println " imported square: passed")
47+
(println " imported square: FAILED"))
48+
49+
(if (= (cube 4) 64)
50+
(println " imported cube: passed")
51+
(println " imported cube: FAILED"))
52+
53+
; Original qualified access still works after import
54+
(if (= (math/abs -10) 10)
55+
(println " qualified still works: passed")
56+
(println " qualified still works: FAILED"))
57+
58+
; ============================================================================
59+
; Test 3: Predicates module
60+
; ============================================================================
61+
(println "")
62+
(println "Test 3: Predicates module")
63+
64+
(if (predicates/positive? 5)
65+
(println " predicates/positive?: passed")
66+
(println " predicates/positive?: FAILED"))
67+
68+
(if (predicates/negative? -3)
69+
(println " predicates/negative?: passed")
70+
(println " predicates/negative?: FAILED"))
71+
72+
(if (predicates/zero? 0)
73+
(println " predicates/zero?: passed")
74+
(println " predicates/zero?: FAILED"))
75+
76+
(if (predicates/even? 4)
77+
(println " predicates/even?: passed")
78+
(println " predicates/even?: FAILED"))
79+
80+
(if (predicates/odd? 7)
81+
(println " predicates/odd?: passed")
82+
(println " predicates/odd?: FAILED"))
83+
84+
; ============================================================================
85+
; Test 4: Import from multiple modules
86+
; ============================================================================
87+
(println "")
88+
(println "Test 4: Import from multiple modules")
89+
90+
(import predicates (even? positive?))
91+
92+
(if (even? 10)
93+
(println " imported even?: passed")
94+
(println " imported even?: FAILED"))
95+
96+
(if (positive? 42)
97+
(println " imported positive?: passed")
98+
(println " imported positive?: FAILED"))
99+
100+
; ============================================================================
101+
; Test 5: Composing functions from different modules
102+
; ============================================================================
103+
(println "")
104+
(println "Test 5: Composing module functions")
105+
106+
; square of absolute value
107+
(def square-abs (fn (x) (math/square (math/abs x))))
108+
109+
(if (= (square-abs -5) 25)
110+
(println " square-abs: passed")
111+
(println " square-abs: FAILED"))
112+
113+
; check if double is positive
114+
(def double-positive? (fn (x) (predicates/positive? (math/double x))))
115+
116+
(if (double-positive? 3)
117+
(println " double-positive?: passed")
118+
(println " double-positive?: FAILED"))
119+
120+
; ============================================================================
121+
; Test 6: Module values
122+
; ============================================================================
123+
(println "")
124+
(println "Test 6: Module as value")
125+
126+
; Modules are first-class values
127+
(def m math)
128+
(println (list " module value: " m))
129+
130+
; ============================================================================
131+
; Test 7: Nested function calls with modules
132+
; ============================================================================
133+
(println "")
134+
(println "Test 7: Nested calls")
135+
136+
(if (= (math/square (math/abs -5)) 25)
137+
(println " nested calls: passed")
138+
(println " nested calls: FAILED"))
139+
140+
(if (= (math/cube (math/double 2)) 64) ; cube(double(2)) = cube(4) = 64
141+
(println " cube of double: passed")
142+
(println " cube of double: FAILED"))
143+
144+
; ============================================================================
145+
; Test 8: Using modules with higher-order functions (from stdlib)
146+
; ============================================================================
147+
(println "")
148+
(println "Test 8: Modules with higher-order functions (using stdlib)")
149+
150+
; Map with module function - check individual elements
151+
(def squares (map math/square (list 1 2 3 4 5)))
152+
(if (and (= (car squares) 1)
153+
(= (car (cdr squares)) 4)
154+
(= (car (cdr (cdr squares))) 9))
155+
(println " map with math/square: passed")
156+
(println " map with math/square: FAILED"))
157+
158+
; Filter with module predicate - check result
159+
(def evens (filter predicates/even? (list 1 2 3 4 5 6)))
160+
(if (and (= (car evens) 2)
161+
(= (car (cdr evens)) 4)
162+
(= (car (cdr (cdr evens))) 6))
163+
(println " filter with even?: passed")
164+
(println " filter with even?: FAILED"))
165+
166+
; Filter with positive?
167+
(def positives (filter predicates/positive? (list -2 -1 0 1 2)))
168+
(if (and (= (car positives) 1)
169+
(= (car (cdr positives)) 2))
170+
(println " filter with positive?: passed")
171+
(println " filter with positive?: FAILED"))
172+
173+
; Fold with module function
174+
(def sum-of-squares (fold (fn (acc x) (+ acc (math/square x))) 0 (list 1 2 3 4)))
175+
(if (= sum-of-squares 30) ; 1 + 4 + 9 + 16 = 30
176+
(println " fold with math/square: passed")
177+
(println " fold with math/square: FAILED"))
178+
179+
; ============================================================================
180+
; Test 9: Greetings module (side effects)
181+
; ============================================================================
182+
(println "")
183+
(println "Test 9: Greetings module")
184+
(greetings/hello "World")
185+
(greetings/goodbye "World")
186+
(greetings/welcome "Alice" "Lisp Land")
187+
(println " greetings module: passed (check output above)")
188+
189+
; ============================================================================
190+
; Summary
191+
; ============================================================================
192+
(println "All module tests completed!")
193+
(println "This test loaded modules from separate files:")
194+
(println " - lisp/stdlib.lisp (map, filter, fold)")
195+
(println " - lisp/modules/math.lisp")
196+
(println " - lisp/modules/predicates.lisp")
197+
(println " - lisp/modules/greetings.lisp")
198+
(println "")
199+
(println "Note: Private functions are not accessible.")
200+
(println "Trying (math/internal-constant) would error:")
201+
(println " 'internal-constant' is not exported from module 'math'")

src/vm.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,38 @@ impl VM {
452452
continue;
453453
}
454454

455+
// Special handling for load - push a new frame for the loaded code
456+
if &*symbol_rc == "load" {
457+
let loaded_chunk = self.compile_load(base, dest, nargs)?;
458+
459+
let frame = unsafe { self.frames.last().unwrap_unchecked() };
460+
let num_registers = frame.chunk.num_registers;
461+
let new_base = base + num_registers as usize;
462+
463+
if new_base + loaded_chunk.num_registers as usize > MAX_REGISTERS {
464+
return Err("Stack overflow in load".to_string());
465+
}
466+
467+
// Save current IP
468+
unsafe { self.frames.last_mut().unwrap_unchecked() }.ip = ip;
469+
470+
// Push frame for loaded code
471+
self.frames.push(CallFrame {
472+
chunk: loaded_chunk,
473+
ip: 0,
474+
base: new_base,
475+
return_reg: base + dest as usize,
476+
});
477+
478+
// Update cached frame values
479+
let frame = unsafe { self.frames.last().unwrap_unchecked() };
480+
code_ptr = frame.chunk.code.as_ptr();
481+
constants_ptr = frame.chunk.constants.as_ptr();
482+
ip = 0;
483+
base = new_base;
484+
continue;
485+
}
486+
455487
// Check for qualified name access (module/symbol) - verify export
456488
let name = &*symbol_rc;
457489
if let Some(slash_pos) = name.find('/') {
@@ -1540,6 +1572,49 @@ impl VM {
15401572
Ok(Value::from_heap(heap))
15411573
}
15421574

1575+
/// Compile a file for loading - returns the compiled chunk
1576+
/// Args: filename (string)
1577+
fn compile_load(&mut self, base: usize, dest: u8, nargs: u8) -> Result<Rc<Chunk>, String> {
1578+
use std::fs;
1579+
use crate::{parse_all, expand, Compiler, MacroRegistry, standard_env};
1580+
1581+
if nargs != 1 {
1582+
return Err("load expects 1 argument (filename)".to_string());
1583+
}
1584+
1585+
let arg_start = base + dest as usize + 1;
1586+
let filename = self.registers[arg_start]
1587+
.as_string()
1588+
.ok_or("load: argument must be a string filename")?
1589+
.to_string();
1590+
1591+
// Read the file
1592+
let contents = fs::read_to_string(&filename)
1593+
.map_err(|e| format!("load: could not read file '{}': {}", filename, e))?;
1594+
1595+
// Parse all expressions
1596+
let exprs = parse_all(&contents)
1597+
.map_err(|e| format!("load: parse error in '{}': {}", filename, e))?;
1598+
1599+
// For macro expansion
1600+
let env = standard_env();
1601+
let macros = MacroRegistry::new();
1602+
1603+
// Expand macros
1604+
let expanded: Result<Vec<Value>, String> = exprs
1605+
.iter()
1606+
.map(|expr| expand(expr, &macros, &env))
1607+
.collect();
1608+
let expanded = expanded
1609+
.map_err(|e| format!("load: macro error in '{}': {}", filename, e))?;
1610+
1611+
// Compile all expressions
1612+
let chunk = Compiler::compile_all(&expanded)
1613+
.map_err(|e| format!("load: compile error in '{}': {}", filename, e))?;
1614+
1615+
Ok(Rc::new(chunk))
1616+
}
1617+
15431618
/// Handle __make_module__ call - creates a module from qualified globals
15441619
/// Args: module_name, export1, export2, ...
15451620
fn handle_make_module(&self, base: usize, dest: u8, nargs: u8) -> Result<Value, String> {

0 commit comments

Comments
 (0)