changed assignment parser from var = statement to statement = statement

left side of assignments can now be various different things instead of just variables. any statement that returns a reference can be used to assign to the value behind that reference. variables are automatically referenced, so the '&' can be omitted. if the variable contains a reference and that reference should be used, dereference it with *varname instead of just varname.
This commit is contained in:
mark 2023-05-12 00:44:47 +02:00
parent 6f31abd5cc
commit bba48b311f
18 changed files with 348 additions and 204 deletions

19
amogus.mers Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,10 @@
list = [1 2 3 4 5 6 7 8 9 ...]
second = &list.get_ref(2).assume1()
second.debug()
*second = 24
second.debug()
// second = &list.get_ref(2).assume1()
// second.debug()
// *second = 24
// second.debug()
&list.get_ref(2).assume1() = 24
list.debug()

View File

@ -12,6 +12,6 @@ sleep(0.5)
http_get("https:\//github.com/").assume_no_enum()
println("got github start page as html")
// t.await()
t.await()
http_get("not a url").debug()

View File

@ -19,3 +19,21 @@ fn map_string_to_int(list [string ...] func fn((string int))) {
for v ["1" "2" "5" "-10" ...].map_string_to_int((s string) s.parse_int().assume1("list contained strings that can't be parsed to an int!")) {
debug(v)
}
// returns an iterator to iteratively compute square numbers
// using the fact that (n+1)^2 = n^2 + 2n + 1
fn square_numbers() {
i = 0
val = 0
() {
val = val + { 2 * i } + 1
i = i + 1
[val]
}
}
for n square_numbers() {
println(n.to_string())
n >= 100
}

4
mers/a.mers Normal file
View File

@ -0,0 +1,4 @@
a = 1
b = 2
a.debug()
b.debug()

View File

@ -128,6 +128,8 @@ fn normal_main() {
};
match parsing::parse::parse(&mut file) {
Ok(script) => {
#[cfg(debug_assertions)]
eprintln!("{script:#?}");
println!(" - - - - -");
let start = Instant::now();
let out = script.run(std::env::args().skip(2).collect());

View File

@ -120,10 +120,7 @@ impl Plugin for MersNuPlugin {
}
Err(e) => Value::Error {
error: Box::new(ShellError::IncorrectValue {
msg: format!(
"Couldn't compile mers, error: {}",
e.with_file(&file)
),
msg: format!("Couldn't compile mers, error: {}", e.with_file(&file)),
span: source_span,
}),
},

View File

@ -148,6 +148,9 @@ pub fn parse(file: &mut File) -> Result<RScript, Error> {
Err(e) => return Err((e.into(), ginfo.to_arc()).into()),
};
#[cfg(debug_assertions)]
eprintln!("{func:#?}");
ginfo.libs = match parse_step_libs_load(libs, &mut ginfo) {
Ok(v) => v,
Err(e) => return Err((e.into(), ginfo.to_arc()).into()),
@ -533,11 +536,12 @@ pub mod implementation {
}
pub(crate) fn parse_statement(file: &mut File) -> Result<SStatement, ParseError> {
parse_statement_adv(file, false)
parse_statement_adv(file, false, 0)
}
pub(crate) fn parse_statement_adv(
file: &mut File,
is_part_of_chain_already: bool,
chain_level: usize,
) -> Result<SStatement, ParseError> {
file.skip_whitespaces();
let err_start_of_statement = *file.get_pos();
@ -589,37 +593,6 @@ pub mod implementation {
};
match nchar {
Some(':') => {
let file_pos_before_pot_type = *file.get_pos();
let parsed_type = parse_type(file);
file.skip_whitespaces();
if let Some('=') = file.next() {
let err_equals_sign = *file.get_pos();
let start = start.trim();
let derefs = start.chars().take_while(|c| *c == '*').count();
match parse_statement(file) {
Ok(v) => break v
.output_to(start[derefs..].to_owned(), derefs)
.force_output_type(Some(match parsed_type {
Ok(v) => v,
Err(mut e) => {
e.context.push((
format!("interpreted this as an assignment to a variable with the format <var>::<var_type> = <statement>"), Some((err_start_of_statement, Some(err_equals_sign)))
));
return Err(e);
}
})),
Err(mut e) => {
e.context.push((
format!(
"statement was supposed to be assigned to variable {start}"
),
Some((err_start_of_statement, Some(err_equals_sign))),
));
return Err(e);
}
}
}
file.set_pos(file_pos_before_pot_type);
return Ok(SStatement::new(SStatementEnum::EnumVariant(
start,
parse_statement(file)?,
@ -636,25 +609,6 @@ pub mod implementation {
});
}
file.skip_whitespaces();
// var = statement
if let Some('=') = file.peek() {
file.next();
let err_equals_sign = *file.get_pos();
let start = start.trim();
let derefs = start.chars().take_while(|c| *c == '*').count();
match parse_statement(file) {
Ok(v) => break v.output_to(start[derefs..].to_owned(), derefs),
Err(mut e) => {
e.context.push((
format!(
"statement was supposed to be assigned to variable {start}"
),
Some((err_start_of_statement, Some(err_equals_sign))),
));
return Err(e);
}
};
}
// parse normal statement
let start = start.trim();
match start {
@ -889,23 +843,21 @@ pub mod implementation {
}
};
let err_end_of_original_statement = *file.get_pos();
file.skip_whitespaces();
if !file[file.get_pos().current_char_index..].starts_with("..") {
// dot chain syntax only works if there is only one dot
if let Some('.') = file.peek() {
// consume the dot (otherwise, a.b.c syntax will break in certain cases)
file.next();
}
if !is_part_of_chain_already {
let mut chain_length = 0;
let mut err_end_of_prev = err_end_of_original_statement;
while let Some('.') =
file.get_char(file.get_pos().current_char_index.saturating_sub(1))
// special characters that can follow a statement (loop because these can be chained)
loop {
file.skip_whitespaces();
out = match (chain_level, file.peek()) {
(0..=200, Some('.'))
if !matches!(
file.get_char(file.get_pos().current_char_index + 1),
Some('.')
) =>
{
file.next();
let err_start_of_wrapper = *file.get_pos();
let wrapper = parse_statement_adv(file, true)?;
let wrapper = parse_statement_adv(file, true, 250)?;
let err_end_of_wrapper = *file.get_pos();
out = match *wrapper.statement {
match *wrapper.statement {
SStatementEnum::FunctionCall(func, args) => {
let args = [out].into_iter().chain(args.into_iter()).collect();
SStatementEnum::FunctionCall(func, args).to()
@ -913,67 +865,131 @@ pub mod implementation {
SStatementEnum::Value(vd) => match &vd.data().0 {
VDataEnum::Int(i) => SStatementEnum::IndexFixed(out, *i as _).to(),
_ => {
let mut context = vec![];
if chain_length > 0 {
context.push((
format!(
"this is the {} wrapping statement in a chain.",
match chain_length {
1 => format!("second"),
2 => format!("third"),
// NOTE: this technically breaks at 21, but I don't care.
len => format!("{}th", len + 1),
}
),
None,
));
}
context.push((
format!("the statement that was supposed to be wrapped"),
Some((err_start_of_statement, Some(err_end_of_prev))),
));
return Err(ParseError {
err: ParseErrors::CannotUseFixedIndexingWithThisType(vd.out()),
location: err_start_of_wrapper,
location_end: Some(err_end_of_wrapper),
context,
context: vec![(
format!("this is a wrapping statement (a.f(), a.0, etc.).",),
None,
)],
info: None,
});
}
},
other => {
let mut context = vec![];
if chain_length > 0 {
context.push((
format!(
"this is the {} wrapping statement in a chain.",
match chain_length {
1 => format!("second"),
2 => format!("third"),
// NOTE: this technically breaks at 21, but I don't care.
len => format!("{}th", len + 1),
}
),
None,
));
}
context.push((
format!("the statement that was supposed to be wrapped"),
Some((err_start_of_statement, Some(err_end_of_prev))),
));
return Err(ParseError {
err: ParseErrors::CannotWrapWithThisStatement(other),
location: err_start_of_wrapper,
location_end: Some(err_end_of_wrapper),
context,
context: vec![(
format!("this is a wrapping statement (a.f(), a.0, etc.).",),
None,
)],
info: None,
});
}
};
err_end_of_prev = err_end_of_wrapper;
chain_length += 1;
}
}
}
(0..=100, Some('+')) => {
file.next();
SStatementEnum::FunctionCall(
"add".to_owned(),
// AMONG
vec![out, parse_statement_adv(file, true, 100)?],
)
.to()
}
(0..=100, Some('-')) => {
file.next();
SStatementEnum::FunctionCall(
"sub".to_owned(),
// US
vec![out, parse_statement_adv(file, true, 100)?],
)
.to()
}
(0..=100, Some('*')) => {
file.next();
SStatementEnum::FunctionCall(
"mul".to_owned(),
vec![out, parse_statement_adv(file, true, 100)?],
)
.to()
}
(0..=100, Some('/')) => {
file.next();
SStatementEnum::FunctionCall(
"div".to_owned(),
// RED SUSSY MOGUS MAN
vec![out, parse_statement_adv(file, true, 100)?],
)
.to()
}
(0..=100, Some('%')) => {
file.next();
SStatementEnum::FunctionCall(
"mod".to_owned(),
vec![out, parse_statement_adv(file, true, 100)?],
)
.to()
}
(0..=50, Some('>')) => {
file.next();
SStatementEnum::FunctionCall(
if let Some('=') = file.peek() {
file.next();
"gtoe".to_owned()
} else {
"gt".to_owned()
},
vec![out, parse_statement_adv(file, true, 50)?],
)
.to()
}
(0..=50, Some('<')) => {
file.next();
SStatementEnum::FunctionCall(
if let Some('=') = file.peek() {
file.next();
"ltoe".to_owned()
} else {
"lt".to_owned()
},
vec![out, parse_statement_adv(file, true, 50)?],
)
.to()
}
(0..=50, Some('='))
if matches!(
file.get_char(file.get_pos().current_char_index + 1),
Some('=')
) =>
{
file.next();
file.next();
SStatementEnum::FunctionCall(
"eq".to_owned(),
vec![out, parse_statement_adv(file, true, 50)?],
)
.to()
}
(0..=10, Some('=')) => {
file.next();
match out.statement.as_mut() {
SStatementEnum::Variable(name, r) => {
if name.starts_with("*") {
*name = name[1..].to_owned();
} else {
*r = true
}
}
_ => {}
}
parse_statement(file)?.output_to(out, 0)
}
_ => break,
};
}
Ok(out)
}

View File

@ -63,6 +63,7 @@ fn parse_mers_code(file: &mut File) -> Result<RScript, MacroError> {
}
}
#[derive(Debug)]
pub enum Macro {
/// Compiles and executes the provided mers code at compile-time and inserts the value
StaticMers(VData),

View File

@ -2,6 +2,7 @@ use std::fmt::{self, Display, Formatter, Pointer};
use super::{code_macro::Macro, global_info::GlobalScriptInfo, val_data::VData, val_type::VType};
#[derive(Debug)]
pub enum SStatementEnum {
Value(VData),
Tuple(Vec<SStatement>),
@ -30,8 +31,9 @@ impl SStatementEnum {
}
}
#[derive(Debug)]
pub struct SStatement {
pub output_to: Option<(String, usize)>,
pub output_to: Option<(Box<SStatement>, usize)>,
pub statement: Box<SStatementEnum>,
pub force_output_type: Option<VType>,
}
@ -44,8 +46,8 @@ impl SStatement {
force_output_type: None,
}
}
pub fn output_to(mut self, var: String, derefs: usize) -> Self {
self.output_to = Some((var, derefs));
pub fn output_to(mut self, statement: SStatement, derefs: usize) -> Self {
self.output_to = Some((Box::new(statement), derefs));
self
}
// forces the statement's output to fit in a certain type.
@ -56,6 +58,7 @@ impl SStatement {
}
/// A block of code is a collection of statements.
#[derive(Debug)]
pub struct SBlock {
pub statements: Vec<SStatement>,
}
@ -66,6 +69,7 @@ impl SBlock {
}
// A function is a block of code that starts with some local variables as inputs and returns some value as its output. The last statement in the block will be the output.
#[derive(Debug)]
pub struct SFunction {
pub inputs: Vec<(String, VType)>,
pub block: SBlock,

View File

@ -82,32 +82,23 @@ impl RFunction {
#[derive(Clone, Debug)]
pub struct RStatement {
// (_, _, is_init)
pub output_to: Option<(usize, usize, bool)>,
pub output_to: Option<(Box<RStatement>, usize, bool)>,
statement: Box<RStatementEnum>,
pub force_output_type: Option<VType>,
}
impl RStatement {
pub fn run(&self, vars: &mut Vec<VData>, info: &GSInfo) -> VData {
let out = self.statement.run(vars, info);
if let Some((v, derefs, is_init)) = self.output_to {
// let mut parent = None;
let mut val = &mut vars[v];
fn deref(n: usize, val: &mut VData, is_init: bool, out: VData) {
if n == 0 {
if is_init {
*val = out.inner().to();
} else {
val.assign(out.inner());
}
} else {
if let VDataEnum::Reference(r) = &mut val.data().0 {
deref(n - 1, r, is_init, out)
} else {
unreachable!("cannot derefence: not a reference.");
}
}
if let Some((v, derefs, is_init)) = &self.output_to {
let mut val = v.run(vars, info);
// even if 0 derefs, deref once because it *has* to end on a reference (otherwise writing to it would be unacceptable as the value might not expect to be modified)
for _ in 0..(derefs + 1) {
val = match val.inner().deref() {
Some(v) => v,
None => unreachable!("can't dereference..."),
};
}
deref(derefs, val, is_init, out);
val.assign(out.inner());
VDataEnum::Tuple(vec![]).to()
} else {
out
@ -370,6 +361,7 @@ impl RStatementEnum {
}
}
#[derive(Debug)]
pub struct RScript {
main: RFunction,
info: GSInfo,

View File

@ -6,6 +6,7 @@ use super::{builtins, val_type::VType};
pub type GSInfo = Arc<GlobalScriptInfo>;
#[derive(Debug)]
pub struct GlobalScriptInfo {
pub vars: usize,

View File

@ -36,6 +36,7 @@ pub enum ToRunnableError {
found: VType,
problematic: VType,
},
CannotAssignTo(VType, VType),
CaseForceButTypeNotCovered(VType),
MatchConditionInvalidReturn(VType),
NotIndexableFixed(VType, usize),
@ -129,6 +130,14 @@ impl ToRunnableError {
}
write!(f, ".")
}
Self::CannotAssignTo(val, target) => {
write!(f, "Cannot assign type ")?;
val.fmtgs(f, info)?;
write!(f, " to ")?;
target.fmtgs(f, info)?;
write!(f, ".")?;
Ok(())
},
Self::ForLoopContainerHasNoInnerTypes => {
write!(f, "For loop: container had no inner types, cannot iterate.")
}
@ -332,7 +341,16 @@ fn statement(
ginfo: &mut GlobalScriptInfo,
linfo: &mut LInfo,
) -> Result<RStatement, ToRunnableError> {
let mut statement = match &*s.statement {
statement_adv(s, ginfo, linfo, None)
}
fn statement_adv(
s: &SStatement,
ginfo: &mut GlobalScriptInfo,
linfo: &mut LInfo,
// if Some((t, is_init)), the statement creates by this function is the left side of an assignment, meaning it can create variables. t is the type that will be assigned to it.
to_be_assigned_to: Option<(VType, &mut bool)>,
) -> Result<RStatement, ToRunnableError> {
let mut state = match &*s.statement {
SStatementEnum::Value(v) => RStatementEnum::Value(v.clone()),
SStatementEnum::Tuple(v) | SStatementEnum::List(v) => {
let mut w = Vec::with_capacity(v.len());
@ -346,11 +364,18 @@ fn statement(
}
}
SStatementEnum::Variable(v, is_ref) => {
if !linfo.vars.contains_key(v) {
if let Some((t, is_init)) = to_be_assigned_to {
*is_init = true;
linfo.vars.insert(v.to_owned(), (ginfo.vars, t));
ginfo.vars += 1;
}
}
if let Some(var) = linfo.vars.get(v) {
RStatementEnum::Variable(var.0, {
let mut v = var.1.clone(); stypes(&mut v, ginfo)?; v }, *is_ref)
} else {
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
}
}
SStatementEnum::FunctionCall(v, args) => {
@ -620,61 +645,103 @@ fn statement(
if let Some(force_opt) = &s.force_output_type {
let mut force_opt = force_opt.to_owned();
stypes(&mut force_opt, ginfo)?;
let real_output_type = statement.out(ginfo);
let real_output_type = state.out(ginfo);
let problematic_types = real_output_type.fits_in(&force_opt, ginfo);
if problematic_types.is_empty() {
statement.force_output_type = Some(force_opt);
state.force_output_type = Some(force_opt);
} else {
return Err(ToRunnableError::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(force_opt.clone(), real_output_type, VType { types: problematic_types }));
}
}
if let Some((opt, derefs)) = &s.output_to {
if let Some((var_id, var_out)) = linfo.vars.get(opt) {
let out = statement.out(ginfo);
let mut var_derefd = var_out.clone();
for _ in 0..*derefs {
var_derefd = if let Some(v) = var_derefd.dereference() {
v
} else {
return Err(ToRunnableError::CannotDereferenceTypeNTimes(
var_out.clone(),
*derefs,
var_derefd,
));
}
}
let inv_types = out.fits_in(&var_derefd, ginfo);
if !inv_types.is_empty() {
eprintln!("Warn: shadowing variable {opt} because statement's output type {out} does not fit in the original variable's {var_out}. This might become an error in the future, or it might stop shadowing the variiable entirely - for stable scripts, avoid this by giving the variable a different name.");
if *derefs != 0 {
return Err(ToRunnableError::CannotDeclareVariableWithDereference(
opt.clone(),
));
}
linfo.vars.insert(opt.clone(), (ginfo.vars, out));
statement.output_to = Some((ginfo.vars, 0, true));
ginfo.vars += 1;
let mut is_init = false;
let optr = statement_adv(
opt,
ginfo,
linfo,
if *derefs == 0 {
Some((state.out(ginfo), &mut is_init))
} else {
// mutate existing variable
statement.output_to = Some((*var_id, *derefs, false));
None
},
)?;
let mut opt_type = optr.out(ginfo);
for _ in 0..*derefs {
if let Some(deref_type) = optr.out(ginfo).dereference() {
opt_type = deref_type;
} else {
return Err(ToRunnableError::CannotDereferenceTypeNTimes(
optr.out(ginfo),
*derefs,
opt_type,
));
}
} else {
let mut out = statement.out(ginfo);
for _ in 0..*derefs {
out = if let Some(v) = out.dereference() {
v
} else {
return Err(ToRunnableError::CannotDereferenceTypeNTimes(
statement.out(ginfo),
*derefs,
out,
));
}
}
linfo.vars.insert(opt.clone(), (ginfo.vars, out));
statement.output_to = Some((ginfo.vars, *derefs, true));
ginfo.vars += 1;
}
let opt_type_assign = match opt_type.dereference() {
Some(v) => v,
None => {
return Err(ToRunnableError::CannotDereferenceTypeNTimes(
optr.out(ginfo),
derefs + 1,
opt_type,
))
}
};
if state.out(ginfo).fits_in(&opt_type_assign, ginfo).is_empty() {
state.output_to = Some((Box::new(optr), *derefs, is_init));
} else {
return Err(ToRunnableError::CannotAssignTo(
state.out(ginfo),
opt_type_assign,
));
}
//
// if let Some((var_id, var_out)) = linfo.vars.get(opt) {
// let out = state.out(ginfo);
// let mut var_derefd = var_out.clone();
// for _ in 0..*derefs {
// var_derefd = if let Some(v) = var_derefd.dereference() {
// v
// } else {
// return Err(ToRunnableError::CannotDereferenceTypeNTimes(
// var_out.clone(),
// *derefs,
// var_derefd,
// ));
// }
// }
// let inv_types = out.fits_in(&var_derefd, ginfo);
// if !inv_types.is_empty() {
// eprintln!("Warn: shadowing variable {opt} because statement's output type {out} does not fit in the original variable's {var_out}. This might become an error in the future, or it might stop shadowing the variiable entirely - for stable scripts, avoid this by giving the variable a different name.");
// if *derefs != 0 {
// return Err(ToRunnableError::CannotDeclareVariableWithDereference(
// opt.clone(),
// ));
// }
// linfo.vars.insert(opt.clone(), (ginfo.vars, out));
// state.output_to = Some((ginfo.vars, 0, true));
// ginfo.vars += 1;
// } else {
// // mutate existing variable
// state.output_to = Some((*var_id, *derefs, false));
// }
// } else {
// let mut out = state.out(ginfo);
// for _ in 0..*derefs {
// out = if let Some(v) = out.dereference() {
// v
// } else {
// return Err(ToRunnableError::CannotDereferenceTypeNTimes(
// state.out(ginfo),
// *derefs,
// out,
// ));
// }
// }
// linfo.vars.insert(opt.clone(), (ginfo.vars, out));
// state.output_to = Some((ginfo.vars, *derefs, true));
// ginfo.vars += 1;
// }
}
Ok(statement)
Ok(state)
}

View File

@ -24,8 +24,8 @@ impl VData {
return;
}
}
#[cfg(debug_assertions)]
eprintln!("VData: assign: overwriting my previous Arc because it was immutable.");
// #[cfg(debug_assertions)]
// eprintln!("VData: assign: overwriting my previous Arc because it was immutable.");
*self = new_val.to();
}
pub fn inner_replace(&mut self, new_val: VDataEnum) -> VDataEnum {
@ -90,22 +90,18 @@ impl VData {
}
impl Clone for VData {
fn clone(&self) -> Self {
#[cfg(debug_assertions)]
eprint!("VData: Clone: ");
let mut d = self.data.lock().unwrap();
let o = if d.1 {
if Arc::strong_count(&self.data) > 1 {
// mutable, copy the value to avoid accidentally modifying it.
#[cfg(debug_assertions)]
eprint!(
"copying value due to clone of a mutable shared value. (strong count: {})",
eprintln!(
"VData: Clone: copying value due to clone of a mutable shared value. (strong count: {})",
Arc::strong_count(&self.data)
);
d.0.clone().to()
} else {
// mutable, but not shared. just change it to not being mutable.
#[cfg(debug_assertions)]
eprint!("setting mutable value to immutable to avoid copying. clone will happen when value is used mutably.");
d.1 = false;
// then return the same arc (-> avoid cloning)
Self {
@ -113,15 +109,11 @@ impl Clone for VData {
}
}
} else {
#[cfg(debug_assertions)]
eprint!("immutably cloning immutable value. no copy necessary.");
// immutable, return the same arc (-> avoid cloning)
Self {
data: Arc::clone(&self.data),
}
};
#[cfg(debug_assertions)]
eprintln!();
o
}
}
@ -229,6 +221,13 @@ impl VDataEnum {
data: Arc::new(Mutex::new((self, true))),
}
}
pub fn deref(self) -> Option<VData> {
if let Self::Reference(r) = self {
Some(r)
} else {
None
}
}
}
// get()

View File

@ -100,6 +100,7 @@ impl VType {
None
}
}
/// returns Some(t) where t is the type you get from dereferencing self or None if self contains even a single type that cannot be dereferenced.
pub fn dereference(&self) -> Option<Self> {
let mut out = Self::empty();
for t in self.types.iter() {

11
mers/t.mers Normal file
View File

@ -0,0 +1,11 @@
a = (max int) {
println("Max: " + max.to_string())
for i max {
println(i.to_string())
}
}
a.debug()
// why does this work
a.thread(10).await()
// and this just blocks and does nothing
a.run(10)

View File

@ -11,10 +11,12 @@ progress = 0
calculator = (max int) {
sum = 0
for i max {
i = i.add(1)
sum = sum.add(i)
i = i + 1
// println("i: {0}".format(i.to_string()))
println("i: {0} s: {1}".format(i.to_string() sum.to_string()))
sum = sum + i + 1
if fake_delay sleep(1)
progress = i.mul(100).div(max)
v = i * 100 / max
}
"the sum of all numbers from 0 to {0} is {1}!".format(max.to_string() sum.to_string())
}
@ -26,7 +28,7 @@ slow_calculation_thread = calculator.thread(if fake_delay 10 else 20000000)
loop {
sleep(1)
println("{0}%".format(progress.to_string()))
progress.eq(100) // break from the loop
progress == 100 // break from the loop
}
// use await() to get the result from the thread. if the thread is still running, this will block until the thread finishes.

8
when_clone.mers Normal file
View File

@ -0,0 +1,8 @@
&a = "value"
&list = ["a" "b" "c" ...]
&elem = &list.get_ref(1)
switch! elem {
[&string] elem.0 = "z"
[] {}
}
list.debug()