Implemented arrays and array indexing

This commit is contained in:
2026-02-26 00:32:54 -05:00
parent b55e6e2dfd
commit f9312885bd
10 changed files with 570 additions and 309 deletions

View File

@@ -2,14 +2,14 @@ use std::collections::HashSet;
use crate::expand::perform_param_expansion;
use crate::prompt::readline::markers;
use crate::state::VarFlags;
use crate::state::{VarFlags, VarKind};
use super::*;
#[test]
fn simple_expansion() {
let varsub = "$foo";
write_vars(|v| v.set_var("foo", "this is the value of the variable", VarFlags::NONE));
write_vars(|v| v.set_var("foo", VarKind::Str("this is the value of the variable".into()), VarFlags::NONE));
let mut tokens: Vec<Tk> = LexStream::new(Arc::new(varsub.to_string()), LexFlags::empty())
.map(|tk| tk.unwrap())
@@ -132,8 +132,8 @@ fn test_infinite_recursive_alias() {
#[test]
fn param_expansion_defaultunsetornull() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("set_var", "value", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
v.set_var("set_var", VarKind::Str("value".into()), VarFlags::NONE);
});
let result = perform_param_expansion("unset:-default").unwrap();
assert_eq!(result, "default");
@@ -142,8 +142,8 @@ fn param_expansion_defaultunsetornull() {
#[test]
fn param_expansion_defaultunset() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("set_var", "value", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
v.set_var("set_var", VarKind::Str("value".into()), VarFlags::NONE);
});
let result = perform_param_expansion("unset-default").unwrap();
assert_eq!(result, "default");
@@ -152,8 +152,8 @@ fn param_expansion_defaultunset() {
#[test]
fn param_expansion_setdefaultunsetornull() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("set_var", "value", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
v.set_var("set_var", VarKind::Str("value".into()), VarFlags::NONE);
});
let result = perform_param_expansion("unset:=assigned").unwrap();
assert_eq!(result, "assigned");
@@ -162,8 +162,8 @@ fn param_expansion_setdefaultunsetornull() {
#[test]
fn param_expansion_setdefaultunset() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("set_var", "value", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
v.set_var("set_var", VarKind::Str("value".into()), VarFlags::NONE);
});
let result = perform_param_expansion("unset=assigned").unwrap();
assert_eq!(result, "assigned");
@@ -172,8 +172,8 @@ fn param_expansion_setdefaultunset() {
#[test]
fn param_expansion_altsetnotnull() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("set_var", "value", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
v.set_var("set_var", VarKind::Str("value".into()), VarFlags::NONE);
});
let result = perform_param_expansion("set_var:+alt").unwrap();
assert_eq!(result, "alt");
@@ -182,8 +182,8 @@ fn param_expansion_altsetnotnull() {
#[test]
fn param_expansion_altnotnull() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("set_var", "value", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
v.set_var("set_var", VarKind::Str("value".into()), VarFlags::NONE);
});
let result = perform_param_expansion("set_var+alt").unwrap();
assert_eq!(result, "alt");
@@ -192,7 +192,7 @@ fn param_expansion_altnotnull() {
#[test]
fn param_expansion_len() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("#foo").unwrap();
assert_eq!(result, "3");
@@ -201,7 +201,7 @@ fn param_expansion_len() {
#[test]
fn param_expansion_substr() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo:1").unwrap();
assert_eq!(result, "oo");
@@ -210,7 +210,7 @@ fn param_expansion_substr() {
#[test]
fn param_expansion_substrlen() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo:0:2").unwrap();
assert_eq!(result, "fo");
@@ -219,7 +219,7 @@ fn param_expansion_substrlen() {
#[test]
fn param_expansion_remshortestprefix() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo#f*").unwrap();
assert_eq!(result, "oo");
@@ -228,7 +228,7 @@ fn param_expansion_remshortestprefix() {
#[test]
fn param_expansion_remlongestprefix() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo##f*").unwrap();
assert_eq!(result, "");
@@ -237,7 +237,7 @@ fn param_expansion_remlongestprefix() {
#[test]
fn param_expansion_remshortestsuffix() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo%*o").unwrap();
assert_eq!(result, "fo");
@@ -246,7 +246,7 @@ fn param_expansion_remshortestsuffix() {
#[test]
fn param_expansion_remlongestsuffix() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo%%*o").unwrap();
assert_eq!(result, "");
@@ -255,7 +255,7 @@ fn param_expansion_remlongestsuffix() {
#[test]
fn param_expansion_replacefirstmatch() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo/foo/X").unwrap();
assert_eq!(result, "X");
@@ -264,7 +264,7 @@ fn param_expansion_replacefirstmatch() {
#[test]
fn param_expansion_replaceallmatches() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo//o/X").unwrap();
assert_eq!(result, "fXX");
@@ -273,7 +273,7 @@ fn param_expansion_replaceallmatches() {
#[test]
fn param_expansion_replaceprefix() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo/#f/X").unwrap();
assert_eq!(result, "Xoo");
@@ -282,7 +282,7 @@ fn param_expansion_replaceprefix() {
#[test]
fn param_expansion_replacesuffix() {
write_vars(|v| {
v.set_var("foo", "foo", VarFlags::NONE);
v.set_var("foo", VarKind::Str("foo".into()), VarFlags::NONE);
});
let result = perform_param_expansion("foo/%o/X").unwrap();
assert_eq!(result, "foX");

View File

@@ -1,4 +1,4 @@
use crate::state::{LogTab, MetaTab, ScopeStack, ShellParam, VarFlags, VarTab};
use crate::state::{LogTab, MetaTab, ScopeStack, ShellParam, VarFlags, VarKind, VarTab};
use std::path::PathBuf;
// ============================================================================
@@ -20,7 +20,7 @@ fn scopestack_descend_ascend() {
let mut stack = ScopeStack::new();
// Set a global variable
stack.set_var("GLOBAL", "value1", VarFlags::NONE);
stack.set_var("GLOBAL", VarKind::Str("value1".into()), VarFlags::NONE);
assert_eq!(stack.get_var("GLOBAL"), "value1");
// Descend into a new scope
@@ -30,7 +30,7 @@ fn scopestack_descend_ascend() {
assert_eq!(stack.get_var("GLOBAL"), "value1");
// Set a local variable
stack.set_var("LOCAL", "value2", VarFlags::LOCAL);
stack.set_var("LOCAL", VarKind::Str("value2".into()), VarFlags::LOCAL);
assert_eq!(stack.get_var("LOCAL"), "value2");
// Ascend back to global scope
@@ -48,14 +48,14 @@ fn scopestack_variable_shadowing() {
let mut stack = ScopeStack::new();
// Set global variable
stack.set_var("VAR", "global", VarFlags::NONE);
stack.set_var("VAR", VarKind::Str("global".into()), VarFlags::NONE);
assert_eq!(stack.get_var("VAR"), "global");
// Descend into local scope
stack.descend(None);
// Set local variable with same name
stack.set_var("VAR", "local", VarFlags::LOCAL);
stack.set_var("VAR", VarKind::Str("local".into()), VarFlags::LOCAL);
assert_eq!(stack.get_var("VAR"), "local", "Local should shadow global");
// Ascend back
@@ -77,10 +77,10 @@ fn scopestack_local_vs_global_flag() {
stack.descend(None);
// Set with LOCAL flag - should go in current scope
stack.set_var("LOCAL_VAR", "local", VarFlags::LOCAL);
stack.set_var("LOCAL_VAR", VarKind::Str("local".into()), VarFlags::LOCAL);
// Set without LOCAL flag - should go in global scope
stack.set_var("GLOBAL_VAR", "global", VarFlags::NONE);
stack.set_var("GLOBAL_VAR", VarKind::Str("global".into()), VarFlags::NONE);
// Both visible from local scope
assert_eq!(stack.get_var("LOCAL_VAR"), "local");
@@ -98,15 +98,15 @@ fn scopestack_local_vs_global_flag() {
fn scopestack_multiple_levels() {
let mut stack = ScopeStack::new();
stack.set_var("LEVEL0", "global", VarFlags::NONE);
stack.set_var("LEVEL0", VarKind::Str("global".into()), VarFlags::NONE);
// Level 1
stack.descend(None);
stack.set_var("LEVEL1", "first", VarFlags::LOCAL);
stack.set_var("LEVEL1", VarKind::Str("first".into()), VarFlags::LOCAL);
// Level 2
stack.descend(None);
stack.set_var("LEVEL2", "second", VarFlags::LOCAL);
stack.set_var("LEVEL2", VarKind::Str("second".into()), VarFlags::LOCAL);
// All variables visible from deepest scope
assert_eq!(stack.get_var("LEVEL0"), "global");
@@ -130,7 +130,7 @@ fn scopestack_multiple_levels() {
fn scopestack_cannot_ascend_past_global() {
let mut stack = ScopeStack::new();
stack.set_var("VAR", "value", VarFlags::NONE);
stack.set_var("VAR", VarKind::Str("value".into()), VarFlags::NONE);
// Try to ascend from global scope (should be no-op)
stack.ascend();
@@ -202,7 +202,7 @@ fn scopestack_global_parameters() {
fn scopestack_unset_var() {
let mut stack = ScopeStack::new();
stack.set_var("VAR", "value", VarFlags::NONE);
stack.set_var("VAR", VarKind::Str("value".into()), VarFlags::NONE);
assert_eq!(stack.get_var("VAR"), "value");
stack.unset_var("VAR");
@@ -215,11 +215,11 @@ fn scopestack_unset_finds_innermost() {
let mut stack = ScopeStack::new();
// Set global
stack.set_var("VAR", "global", VarFlags::NONE);
stack.set_var("VAR", VarKind::Str("global".into()), VarFlags::NONE);
// Descend and shadow
stack.descend(None);
stack.set_var("VAR", "local", VarFlags::LOCAL);
stack.set_var("VAR", VarKind::Str("local".into()), VarFlags::LOCAL);
assert_eq!(stack.get_var("VAR"), "local");
// Unset should remove local, revealing global
@@ -231,7 +231,7 @@ fn scopestack_unset_finds_innermost() {
fn scopestack_export_var() {
let mut stack = ScopeStack::new();
stack.set_var("VAR", "value", VarFlags::NONE);
stack.set_var("VAR", VarKind::Str("value".into()), VarFlags::NONE);
// Export the variable
stack.export_var("VAR");
@@ -246,7 +246,7 @@ fn scopestack_var_exists() {
assert!(!stack.var_exists("NONEXISTENT"));
stack.set_var("EXISTS", "yes", VarFlags::NONE);
stack.set_var("EXISTS", VarKind::Str("yes".into()), VarFlags::NONE);
assert!(stack.var_exists("EXISTS"));
stack.descend(None);
@@ -255,7 +255,7 @@ fn scopestack_var_exists() {
"Global var should be visible in local scope"
);
stack.set_var("LOCAL", "yes", VarFlags::LOCAL);
stack.set_var("LOCAL", VarKind::Str("yes".into()), VarFlags::LOCAL);
assert!(stack.var_exists("LOCAL"));
stack.ascend();
@@ -269,11 +269,11 @@ fn scopestack_var_exists() {
fn scopestack_flatten_vars() {
let mut stack = ScopeStack::new();
stack.set_var("GLOBAL1", "g1", VarFlags::NONE);
stack.set_var("GLOBAL2", "g2", VarFlags::NONE);
stack.set_var("GLOBAL1", VarKind::Str("g1".into()), VarFlags::NONE);
stack.set_var("GLOBAL2", VarKind::Str("g2".into()), VarFlags::NONE);
stack.descend(None);
stack.set_var("LOCAL1", "l1", VarFlags::LOCAL);
stack.set_var("LOCAL1", VarKind::Str("l1".into()), VarFlags::LOCAL);
let flattened = stack.flatten_vars();
@@ -291,11 +291,11 @@ fn scopestack_local_var_mutation() {
stack.descend(None);
// `local foo="biz"` — create a local variable with initial value
stack.set_var("foo", "biz", VarFlags::LOCAL);
stack.set_var("foo", VarKind::Str("biz".into()), VarFlags::LOCAL);
assert_eq!(stack.get_var("foo"), "biz");
// `foo="bar"` — reassign without LOCAL flag (plain assignment)
stack.set_var("foo", "bar", VarFlags::NONE);
stack.set_var("foo", VarKind::Str("bar".into()), VarFlags::NONE);
assert_eq!(
stack.get_var("foo"),
"bar",
@@ -321,11 +321,11 @@ fn scopestack_local_var_uninitialized() {
stack.descend(None);
// `local foo` — declare without a value
stack.set_var("foo", "", VarFlags::LOCAL);
stack.set_var("foo", VarKind::Str("".into()), VarFlags::LOCAL);
assert_eq!(stack.get_var("foo"), "");
// `foo="bar"` — assign a value later
stack.set_var("foo", "bar", VarFlags::NONE);
stack.set_var("foo", VarKind::Str("bar".into()), VarFlags::NONE);
assert_eq!(
stack.get_var("foo"),
"bar",
@@ -441,7 +441,7 @@ fn vartab_new() {
fn vartab_set_get_var() {
let mut vartab = VarTab::new();
vartab.set_var("TEST", "value", VarFlags::NONE);
vartab.set_var("TEST", VarKind::Str("value".into()), VarFlags::NONE);
assert_eq!(vartab.get_var("TEST"), "value");
}
@@ -449,10 +449,10 @@ fn vartab_set_get_var() {
fn vartab_overwrite_var() {
let mut vartab = VarTab::new();
vartab.set_var("VAR", "value1", VarFlags::NONE);
vartab.set_var("VAR", VarKind::Str("value1".into()), VarFlags::NONE);
assert_eq!(vartab.get_var("VAR"), "value1");
vartab.set_var("VAR", "value2", VarFlags::NONE);
vartab.set_var("VAR", VarKind::Str("value2".into()), VarFlags::NONE);
assert_eq!(vartab.get_var("VAR"), "value2");
}
@@ -462,7 +462,7 @@ fn vartab_var_exists() {
assert!(!vartab.var_exists("TEST"));
vartab.set_var("TEST", "value", VarFlags::NONE);
vartab.set_var("TEST", VarKind::Str("value".into()), VarFlags::NONE);
assert!(vartab.var_exists("TEST"));
}
@@ -470,7 +470,7 @@ fn vartab_var_exists() {
fn vartab_unset_var() {
let mut vartab = VarTab::new();
vartab.set_var("VAR", "value", VarFlags::NONE);
vartab.set_var("VAR", VarKind::Str("value".into()), VarFlags::NONE);
assert!(vartab.var_exists("VAR"));
vartab.unset_var("VAR");
@@ -482,7 +482,7 @@ fn vartab_unset_var() {
fn vartab_export_var() {
let mut vartab = VarTab::new();
vartab.set_var("VAR", "value", VarFlags::NONE);
vartab.set_var("VAR", VarKind::Str("value".into()), VarFlags::NONE);
vartab.export_var("VAR");
// Variable should still be accessible