diff --git a/docs/language/lists.md b/docs/language/lists.md index a8afa7a..72cc042 100644 --- a/docs/language/lists.md +++ b/docs/language/lists.md @@ -21,6 +21,24 @@ list point points; # empty list of points. list point points = [100, 200, 300, 400]; # points[1] == point {x: 100, y: 200} and so on... ``` +### With a fixed length + +Use `[value; length]` to make a list whose items all start with the same value. + +```goboscript +list scores = [0; 1000]; # 1,000 items, all set to 0. +list names = [""; 25]; # 25 empty strings. +``` + +This form also works with struct lists. The starting value is used for each field. + +```goboscript +struct point {x, y} +list point points = [0; 100]; # 100 points whose x and y fields are both 0. +``` + +The length cannot be negative, infinite, `NaN`, or greater than 200,000. + ### Read contents from a text file This allows you to load a text file line-by-line into a list of strings. @@ -123,4 +141,3 @@ value = list_name["last"]; | `list_name[index] //= y;` | ![](../assets/list_floor_divide.png){width="400"} | | `list_name[index] %= y;` | ![](../assets/list_mod.png){width="400"} | | `list_name[index] &= y;` | ![](../assets/list_join.png){width="400"} | - diff --git a/src/ast/list.rs b/src/ast/list.rs index b0c46da..82f2310 100644 --- a/src/ast/list.rs +++ b/src/ast/list.rs @@ -23,6 +23,7 @@ pub struct List { pub enum ListDefault { Values(Vec), File { path: SmolStr, span: Span }, + FixedLength(ConstExpr, ConstExpr), } impl List { @@ -63,4 +64,20 @@ impl List { is_used: false, } } + + pub fn new_fixed_length( + name: SmolStr, + span: Span, + type_: Type, + default: ConstExpr, + length: ConstExpr, + ) -> Self { + Self { + name, + span, + type_, + default: Some(ListDefault::FixedLength(default, length)), + is_used: false, + } + } } diff --git a/src/codegen/sb3.rs b/src/codegen/sb3.rs index dafd86c..df8ee45 100644 --- a/src/codegen/sb3.rs +++ b/src/codegen/sb3.rs @@ -938,6 +938,22 @@ where T: Write + Seek vec![] } }, + Some(ListDefault::FixedLength(value, length)) => { + let multiplier = list + .type_ + .struct_() + .and_then(|(struct_name, _)| s.get_struct(struct_name)) + .map(|struct_| struct_.fields.len()) + .unwrap_or(1); + let value = s.evaluate_const_expr(d, value); + let len = s.evaluate_const_expr(d, length).to_number(); + if len > 200_00_f64 || len.is_nan() || len.is_infinite() || len < 0_f64 { + d.report(DiagnosticKind::FixedLengthListInvalid(len), &length.span()); + vec![] + } else { + vec![value; len as usize * multiplier] + } + } None => vec![], }; match &list.type_ { diff --git a/src/diagnostic/diagnostic_kind.rs b/src/diagnostic/diagnostic_kind.rs index dca3116..a0648fa 100644 --- a/src/diagnostic/diagnostic_kind.rs +++ b/src/diagnostic/diagnostic_kind.rs @@ -10,6 +10,7 @@ use crate::{ ast::{ Sprite, Type, + Value, }, blocks::{ Block, @@ -127,6 +128,7 @@ pub enum DiagnosticKind { UnusedFunc(SmolStr), UnusedArg(SmolStr), UnusedStructField(SmolStr), + FixedLengthListInvalid(f64), } impl DiagnosticKind { @@ -298,6 +300,21 @@ impl DiagnosticKind { format!("duplicate variant {variant_name} in enum {enum_name}") } DiagnosticKind::EmptyStruct(name) => format!("struct {name} is empty"), + DiagnosticKind::FixedLengthListInvalid(value) => { + if *value < 0_f64 { + return format!("list length cannot be negative"); + } + if value.is_infinite() { + return format!("list length cannot be infinite"); + } + if value.is_nan() { + return format!("list length cannot be nan"); + } + if *value > 200_000_f64 { + return format!("list length cannot be greater than 200,000"); + } + unreachable!() + } } } @@ -485,6 +502,7 @@ impl From<&DiagnosticKind> for Level { | DiagnosticKind::InvalidCostumeFormat { .. } | DiagnosticKind::InvalidSoundFormat { .. } | DiagnosticKind::LocalNotSupported + | DiagnosticKind::FixedLengthListInvalid(..) | DiagnosticKind::UnknownDirective(_) => Level::Error, | DiagnosticKind::FollowedByUnreachableCode diff --git a/src/parser/grammar.lalrpop b/src/parser/grammar.lalrpop index eb1084e..5e92728 100644 --- a/src/parser/grammar.lalrpop +++ b/src/parser/grammar.lalrpop @@ -89,6 +89,9 @@ Declr: () = { LIST ";" => { sprite.add_list(List::new_file(name, l..r, type_, path, pl..pr), diagnostics); }, + LIST "=" "[" ";" "]" ";" => { + sprite.add_list(List::new_fixed_length(name, l..r, type_, default, length), diagnostics); + } } EnumVariant: EnumVariant = {