Skip to content

Commit fdc092c

Browse files
authored
Merge pull request #198 from ryanbreen/feat/breenish-phase5-editing
feat: add array HOF methods, line editing, and history
2 parents 2517a0c + ceae8d4 commit fdc092c

File tree

5 files changed

+920
-24
lines changed

5 files changed

+920
-24
lines changed

docs/planning/BREENISH_SHELL_PLAN.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@
4848
- env() native function: get/set/enumerate environment variables
4949
- source command: load and evaluate script files in current context
5050
- .bshrc startup script: auto-loads /etc/bshrc on REPL start
51-
- 134 passing tests, bsh v0.5.0 with full shell builtins
51+
- Array HOF methods: map, filter, reduce, forEach, find, some, every, flat
52+
- call_function_sync helper for synchronous callback invocation
53+
- Single-parameter arrow functions without parens: `x => expr`
54+
- Interactive line editing: cursor movement, Home/End, Ctrl+A/E/U/K/W/C/D
55+
- Command history with Up/Down arrow navigation
56+
- Raw mode terminal handling via libbreenix termios
57+
- 149 passing tests, bsh v0.5.0 with full shell builtins
5258
- **Phase 6**: PLANNED -- Advanced features (class, Proxy, JIT)
5359

5460
## Architecture

libs/breenish-js/src/compiler.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1274,9 +1274,13 @@ impl<'a> Compiler<'a> {
12741274

12751275
if let TokenKind::Identifier(name) = &tok.kind {
12761276
let name = name.clone();
1277-
// Look ahead for assignment operator
1277+
// Look ahead for assignment operator or arrow function
12781278
let next = self.lexer.peek_ahead(1);
12791279
match &next.kind {
1280+
TokenKind::Arrow => {
1281+
// Single-param arrow function without parens: x => expr
1282+
return self.compile_arrow_function_single_param();
1283+
}
12801284
TokenKind::Assign => {
12811285
self.lexer.next_token(); // consume identifier
12821286
self.lexer.next_token(); // consume '='
@@ -2253,6 +2257,88 @@ impl<'a> Compiler<'a> {
22532257
Ok(())
22542258
}
22552259

2260+
/// Compile an arrow function with a single parameter and no parens: x => expr
2261+
fn compile_arrow_function_single_param(&mut self) -> JsResult<()> {
2262+
// Current token is the identifier (param name)
2263+
let tok = self.lexer.next_token().clone();
2264+
let param_name = match &tok.kind {
2265+
TokenKind::Identifier(n) => n.clone(),
2266+
_ => {
2267+
return Err(JsError::syntax(
2268+
"expected parameter name",
2269+
tok.span.line,
2270+
tok.span.column,
2271+
));
2272+
}
2273+
};
2274+
self.lexer.expect(&TokenKind::Arrow)?;
2275+
2276+
// Push current context onto the function stack
2277+
let parent_ctx = FunctionContext {
2278+
code: core::mem::replace(&mut self.code, CodeBlock::new("<arrow>")),
2279+
scopes: core::mem::replace(
2280+
&mut self.scopes,
2281+
vec![Scope {
2282+
locals: Vec::new(),
2283+
base_slot: 0,
2284+
}],
2285+
),
2286+
next_slot: self.next_slot,
2287+
loop_stack: core::mem::take(&mut self.loop_stack),
2288+
upvalues: core::mem::take(&mut self.upvalues),
2289+
is_async: self.is_async,
2290+
};
2291+
self.function_stack.push(parent_ctx);
2292+
self.next_slot = 0;
2293+
2294+
// Declare the single parameter as a local
2295+
self.declare_local(param_name, false);
2296+
2297+
// Compile body: either a block or a single expression
2298+
if self.lexer.peek().kind == TokenKind::LeftBrace {
2299+
// Block body: { statements }
2300+
self.lexer.next_token(); // consume '{'
2301+
while self.lexer.peek().kind != TokenKind::RightBrace
2302+
&& self.lexer.peek().kind != TokenKind::Eof
2303+
{
2304+
self.compile_statement()?;
2305+
}
2306+
self.lexer.expect(&TokenKind::RightBrace)?;
2307+
// Implicit undefined return if no explicit return
2308+
self.emit_undefined();
2309+
self.code.emit_op(Op::Return);
2310+
} else {
2311+
// Expression body: the expression result is the return value
2312+
self.compile_assignment()?;
2313+
self.code.emit_op(Op::Return);
2314+
}
2315+
2316+
self.code.local_count = self.next_slot;
2317+
2318+
// Collect upvalues
2319+
let func_upvalues = core::mem::take(&mut self.upvalues);
2320+
2321+
// Restore parent context
2322+
let parent_ctx = self.function_stack.pop().unwrap();
2323+
let func_code = core::mem::replace(&mut self.code, parent_ctx.code);
2324+
self.scopes = parent_ctx.scopes;
2325+
self.next_slot = parent_ctx.next_slot;
2326+
self.loop_stack = parent_ctx.loop_stack;
2327+
self.upvalues = parent_ctx.upvalues;
2328+
self.is_async = parent_ctx.is_async;
2329+
2330+
let func_index = self.functions.len();
2331+
self.functions.push(func_code);
2332+
2333+
if func_upvalues.is_empty() {
2334+
let const_idx = self.code.add_constant(Constant::Function(func_index as u32));
2335+
self.code.emit_op_u16(Op::LoadConst, const_idx);
2336+
} else {
2337+
self.emit_create_closure(func_index, &func_upvalues);
2338+
}
2339+
Ok(())
2340+
}
2341+
22562342
/// Compile an async arrow function: async (params) => expr or async (params) => { body }
22572343
/// The 'async' token has already been consumed.
22582344
fn compile_async_arrow_function(&mut self) -> JsResult<()> {

libs/breenish-js/src/lib.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,129 @@ mod tests {
13901390
);
13911391
}
13921392

1393+
// --- Higher-order array method tests ---
1394+
1395+
#[test]
1396+
fn test_array_map() {
1397+
assert_eq!(
1398+
eval_and_capture("let a = [1, 2, 3].map(x => x * 2); print(a);"),
1399+
"2,4,6\n"
1400+
);
1401+
}
1402+
1403+
#[test]
1404+
fn test_array_map_with_index() {
1405+
assert_eq!(
1406+
eval_and_capture("let a = [10, 20, 30].map((x, i) => x + i); print(a);"),
1407+
"10,21,32\n"
1408+
);
1409+
}
1410+
1411+
#[test]
1412+
fn test_array_filter() {
1413+
assert_eq!(
1414+
eval_and_capture("let a = [1, 2, 3, 4, 5].filter(x => x > 3); print(a);"),
1415+
"4,5\n"
1416+
);
1417+
}
1418+
1419+
#[test]
1420+
fn test_array_reduce() {
1421+
assert_eq!(
1422+
eval_and_capture("let r = [1, 2, 3].reduce((acc, x) => acc + x, 0); print(r);"),
1423+
"6\n"
1424+
);
1425+
}
1426+
1427+
#[test]
1428+
fn test_array_reduce_no_initial() {
1429+
assert_eq!(
1430+
eval_and_capture("let r = [1, 2, 3, 4].reduce((acc, x) => acc + x); print(r);"),
1431+
"10\n"
1432+
);
1433+
}
1434+
1435+
#[test]
1436+
fn test_array_foreach() {
1437+
// Use an array to accumulate results since closures capture by value
1438+
assert_eq!(
1439+
eval_and_capture("let result = []; [1, 2, 3].forEach(x => { result.push(x * 10); }); print(result);"),
1440+
"10,20,30\n"
1441+
);
1442+
}
1443+
1444+
#[test]
1445+
fn test_array_find() {
1446+
assert_eq!(
1447+
eval_and_capture("let r = [1, 2, 3, 4].find(x => x > 2); print(r);"),
1448+
"3\n"
1449+
);
1450+
}
1451+
1452+
#[test]
1453+
fn test_array_find_not_found() {
1454+
assert_eq!(
1455+
eval_and_capture("let r = [1, 2, 3].find(x => x > 10); print(r);"),
1456+
"undefined\n"
1457+
);
1458+
}
1459+
1460+
#[test]
1461+
fn test_array_some() {
1462+
assert_eq!(
1463+
eval_and_capture("print([1, 2, 3].some(x => x > 2));"),
1464+
"1\n"
1465+
);
1466+
}
1467+
1468+
#[test]
1469+
fn test_array_some_false() {
1470+
assert_eq!(
1471+
eval_and_capture("print([1, 2, 3].some(x => x > 10));"),
1472+
"0\n"
1473+
);
1474+
}
1475+
1476+
#[test]
1477+
fn test_array_every() {
1478+
assert_eq!(
1479+
eval_and_capture("print([1, 2, 3].every(x => x > 0));"),
1480+
"1\n"
1481+
);
1482+
}
1483+
1484+
#[test]
1485+
fn test_array_every_false() {
1486+
assert_eq!(
1487+
eval_and_capture("print([1, 2, 3].every(x => x > 2));"),
1488+
"0\n"
1489+
);
1490+
}
1491+
1492+
#[test]
1493+
fn test_array_flat() {
1494+
assert_eq!(
1495+
eval_and_capture("let a = [1, [2, 3], [4, 5]].flat(); print(a);"),
1496+
"1,2,3,4,5\n"
1497+
);
1498+
}
1499+
1500+
#[test]
1501+
fn test_array_flat_deep() {
1502+
assert_eq!(
1503+
eval_and_capture("let a = [1, [2, [3, [4]]]].flat(2); print(a);"),
1504+
"1,2,3,4\n"
1505+
);
1506+
}
1507+
1508+
#[test]
1509+
fn test_array_method_chaining() {
1510+
assert_eq!(
1511+
eval_and_capture("let r = [1, 2, 3, 4, 5].filter(x => x > 2).map(x => x * 10); print(r);"),
1512+
"30,40,50\n"
1513+
);
1514+
}
1515+
13931516
// --- String method tests ---
13941517

13951518
#[test]

0 commit comments

Comments
 (0)