Berry animation add 'if' to sequence (#24056)

This commit is contained in:
s-hadinger 2025-10-25 10:06:35 +02:00 committed by GitHub
parent 4ccc9f69fd
commit ea0bf79291
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 10122 additions and 9383 deletions

View File

@ -0,0 +1,77 @@
# Template animation with flags
template animation shutter_bidir {
param colors type palette
param period default 2s
param ascending type bool default true # define to true to enable 'ascending' part
param descending type bool default true # define to true to enable 'descending' part
# since 'strip_length()' is a value provider, it must be assigned to a variable before being used
set strip_len = strip_length()
# animated value for the size of the shutter, evolving linearly in time (sawtooth from 0% to 100%)
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period)
# define two rotating palettes, shifted by one color
color col1 = color_cycle(palette=colors, cycle_period=0)
color col2 = color_cycle(palette=colors, cycle_period=0)
col2.next = 1 # move 'col2' to the next color so it's shifte by one compared to 'col1'
# shutter moving in ascending
animation shutter_lr_animation = beacon_animation(
color = col2
back_color = col1
pos = 0
beacon_size = shutter_size
slew_size = 0
priority = 5
)
# shutter moving in descending
animation shutter_rl_animation = beacon_animation(
color = col1
back_color = col2
pos = 0
beacon_size = strip_len - shutter_size
slew_size = 0
priority = 5
)
# this is the overall sequence composed of two sub-sequences
# the first in ascending mode, the second in descending
sequence shutter_seq repeat forever {
if ascending { # conditional execution: run only if 'ascending' is true
repeat col1.palette_size times { # run the shutter animation
restart shutter_size # resync all times for this animation, to avoid temporal drift
play shutter_lr_animation for period # run the animation
col1.next = 1 # then move to next color for both palettes
col2.next = 1
}
}
if descending { # conditional execution: run only if 'descending' is true
repeat col1.palette_size times {
restart shutter_size
play shutter_rl_animation for period
col1.next = 1
col2.next = 1
}
}
}
run shutter_seq
}
# define a palette of rainbow colors including white with constant brightness
palette rainbow_with_white = [
0xFC0000 # Red
0xFF8000 # Orange
0xFFFF00 # Yellow
0x00FF00 # Green
0x00FFFF # Cyan
0x0080FF # Blue
0x8000FF # Violet
0xCCCCCC # White
]
animation main = shutter_bidir(colors = rainbow_with_white, period = 1.5s)
run main

View File

@ -0,0 +1,182 @@
# Generated Berry code from Animation DSL
# Source: chap_5_22_template_shutter_bidir.anim
#
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
import animation
# Template animation with flags
# Template animation class: shutter_bidir
class shutter_bidir_animation : animation.engine_proxy
static var PARAMS = animation.enc_params({
"colors": {"type": "palette"},
"period": {"default": 2000},
"ascending": {"type": "bool", "default": true},
"descending": {"type": "bool", "default": true}
})
# Template setup method - overrides EngineProxy placeholder
def setup_template()
var engine = self # using 'self' as a proxy to engine object (instead of 'self.engine')
var strip_len_ = animation.strip_length(engine)
# animated value for the size of the shutter, evolving linearly in time (sawtooth from 0% to 100%)
var shutter_size_ = (def (engine)
var provider = animation.sawtooth(engine)
provider.min_value = 0
provider.max_value = strip_len_
provider.duration = animation.create_closure_value(engine, def (engine) return self.period end)
return provider
end)(engine)
# define two rotating palettes, shifted by one color
var col1_ = animation.color_cycle(engine)
col1_.palette = animation.create_closure_value(engine, def (engine) return self.colors end)
col1_.cycle_period = 0
var col2_ = animation.color_cycle(engine)
col2_.palette = animation.create_closure_value(engine, def (engine) return self.colors end)
col2_.cycle_period = 0
col2_.next = 1 # move 'col2' to the next color so it's shifte by one compared to 'col1'
# shutter moving in ascending
var shutter_lr_animation_ = animation.beacon_animation(engine)
shutter_lr_animation_.color = col2_
shutter_lr_animation_.back_color = col1_
shutter_lr_animation_.pos = 0
shutter_lr_animation_.beacon_size = shutter_size_
shutter_lr_animation_.slew_size = 0
shutter_lr_animation_.priority = 5
# shutter moving in descending
var shutter_rl_animation_ = animation.beacon_animation(engine)
shutter_rl_animation_.color = col1_
shutter_rl_animation_.back_color = col2_
shutter_rl_animation_.pos = 0
shutter_rl_animation_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - animation.resolve(shutter_size_) end)
shutter_rl_animation_.slew_size = 0
shutter_rl_animation_.priority = 5
# this is the overall sequence composed of two sub-sequences
# the first in ascending mode, the second in descending
var shutter_seq_ = animation.sequence_manager(engine, -1)
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return bool(self.ascending) end)
# conditional execution: run only if 'ascending' is true
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return col1_.palette_size end)
# run the shutter animation
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end) # resync all times for this animation, to avoid temporal drift
.push_play_step(shutter_lr_animation_, def (engine) return self.period end) # run the animation
.push_closure_step(def (engine) col1_.next = 1 end) # then move to next color for both palettes
.push_closure_step(def (engine) col2_.next = 1 end)
)
)
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return bool(self.descending) end)
# conditional execution: run only if 'descending' is true
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return col1_.palette_size end)
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
.push_play_step(shutter_rl_animation_, def (engine) return self.period end)
.push_closure_step(def (engine) col1_.next = 1 end)
.push_closure_step(def (engine) col2_.next = 1 end)
)
)
self.add(shutter_seq_)
end
end
# define a palette of rainbow colors including white with constant brightness
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var rainbow_with_white_ = bytes(
"FFFC0000" # Red
"FFFF8000" # Orange
"FFFFFF00" # Yellow
"FF00FF00" # Green
"FF00FFFF" # Cyan
"FF0080FF" # Blue
"FF8000FF" # Violet
"FFCCCCCC" # White
)
var main_ = shutter_bidir_animation(engine)
main_.colors = rainbow_with_white_
main_.period = 1500
engine.add(main_)
engine.run()
#- Original DSL source:
# Template animation with flags
template animation shutter_bidir {
param colors type palette
param period default 2s
param ascending type bool default true # define to true to enable 'ascending' part
param descending type bool default true # define to true to enable 'descending' part
# since 'strip_length()' is a value provider, it must be assigned to a variable before being used
set strip_len = strip_length()
# animated value for the size of the shutter, evolving linearly in time (sawtooth from 0% to 100%)
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period)
# define two rotating palettes, shifted by one color
color col1 = color_cycle(palette=colors, cycle_period=0)
color col2 = color_cycle(palette=colors, cycle_period=0)
col2.next = 1 # move 'col2' to the next color so it's shifte by one compared to 'col1'
# shutter moving in ascending
animation shutter_lr_animation = beacon_animation(
color = col2
back_color = col1
pos = 0
beacon_size = shutter_size
slew_size = 0
priority = 5
)
# shutter moving in descending
animation shutter_rl_animation = beacon_animation(
color = col1
back_color = col2
pos = 0
beacon_size = strip_len - shutter_size
slew_size = 0
priority = 5
)
# this is the overall sequence composed of two sub-sequences
# the first in ascending mode, the second in descending
sequence shutter_seq repeat forever {
if ascending { # conditional execution: run only if 'ascending' is true
repeat col1.palette_size times { # run the shutter animation
restart shutter_size # resync all times for this animation, to avoid temporal drift
play shutter_lr_animation for period # run the animation
col1.next = 1 # then move to next color for both palettes
col2.next = 1
}
}
if descending { # conditional execution: run only if 'descending' is true
repeat col1.palette_size times {
restart shutter_size
play shutter_rl_animation for period
col1.next = 1
col2.next = 1
}
}
}
run shutter_seq
}
# define a palette of rainbow colors including white with constant brightness
palette rainbow_with_white = [
0xFC0000 # Red
0xFF8000 # Orange
0xFFFF00 # Yellow
0x00FF00 # Green
0x00FFFF # Cyan
0x0080FF # Blue
0x8000FF # Violet
0xCCCCCC # White
]
animation main = shutter_bidir(colors = rainbow_with_white, period = 1.5s)
run main
-#

View File

@ -79,6 +79,24 @@ SUCCESS
SUCCESS
```
## chap_5_22_template_shutter_bidir.anim
**Status:** ✅ Success
## Symbol Table
| Symbol | Type | Builtin | Dangerous | Takes Args |
|----------------------|-----------------------|---------|-----------|------------|
| `main` | animation | | | |
| `rainbow_with_white` | palette | | | |
| `shutter_bidir` | animation_constructor | | | ✓ |
### Compilation Output
```
SUCCESS
```
## christmas_tree.anim
**Status:** ✅ Success
@ -1209,8 +1227,8 @@ SUCCESS
## Summary
- **Total files processed:** 49
- **Successfully compiled:** 46
- **Total files processed:** 50
- **Successfully compiled:** 47
- **Failed to compile:** 3
### Successful Files
@ -1218,6 +1236,7 @@ SUCCESS
- ✅ breathing_colors.anim
- ✅ candy_cane.anim
- ✅ chap_5_21_template_shutter_bidir.anim
- ✅ chap_5_22_template_shutter_bidir.anim
- ✅ christmas_tree.anim
- ✅ comet_chase.anim
- ✅ computed_values_demo.anim

View File

@ -2,18 +2,22 @@
template animation shutter_bidir {
param colors type palette
param period default 5s
param ascending type bool default true
param descending type bool default true
param period default 2s
param ascending type bool default true # define to true to enable 'ascending' part
param descending type bool default true # define to true to enable 'descending' part
# since 'strip_length()' is a value provider, it must be assigned to a variable before being used
set strip_len = strip_length()
# animated value for the size of the shutter, evolving linearly in time (sawtooth from 0% to 100%)
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period)
# define two rotating palettes, shifted by one color
color col1 = color_cycle(palette=colors, cycle_period=0)
color col2 = color_cycle(palette=colors, cycle_period=0)
col2.next = 1
col2.next = 1 # move 'col2' to the next color so it's shifte by one compared to 'col1'
# shutter moving from left to right
# shutter moving in ascending
animation shutter_lr_animation = beacon_animation(
color = col2
back_color = col1
@ -23,7 +27,7 @@ template animation shutter_bidir {
priority = 5
)
# shutter moving from right to left
# shutter moving in descending
animation shutter_rl_animation = beacon_animation(
color = col1
back_color = col2
@ -33,16 +37,18 @@ template animation shutter_bidir {
priority = 5
)
# this is the overall sequence composed of two sub-sequences
# the first in ascending mode, the second in descending
sequence shutter_seq repeat forever {
repeat ascending times {
repeat col1.palette_size times {
restart shutter_size
play shutter_lr_animation for period
col1.next = 1
if ascending { # un only if 'ascending' is true
repeat col1.palette_size times { # run the shutter animation
restart shutter_size # resync all times for this animation, to avoid temporal drift
play shutter_lr_animation for period # run the animation
col1.next = 1 # then move to next color for both palettes
col2.next = 1
}
}
repeat descending times {
if descending { # run only if 'descending' is true
repeat col1.palette_size times {
restart shutter_size
play shutter_rl_animation for period

View File

@ -866,6 +866,84 @@ sequence cylon_eye {
}
```
#### If Statement
Conditional execution statements that run their body 0 or 1 times based on a boolean condition:
```berry
if condition { # Execute if condition is true (non-zero)
play animation for 1s
wait 500ms
}
```
**Condition Types:**
- **Static values**: `if true { ... }`, `if false { ... }`, `if 5 { ... }`
- **Variables**: `if flag { ... }` - using previously defined variables
- **Template parameters**: `if self.enabled { ... }` - dynamic values from template parameters
- **Computed expressions**: `if strip_length() > 30 { ... }` - calculated conditions
**If Behavior:**
- **Boolean Coercion**: All conditions are wrapped with `bool()` to ensure 0 or 1 iterations
- **Static Optimization**: Static conditions (literals) are evaluated at compile time without closures
- **Dynamic Evaluation**: Dynamic conditions (variables, parameters) are wrapped in closures
- **Conditional Gate**: Useful for enabling/disabling parts of sequences based on flags
**Examples:**
```berry
# Static condition
sequence demo {
if true {
play animation for 1s
}
}
# Template parameter condition
template animation configurable {
param enable_effect type bool default true
color my_red = 0xFF0000
animation solid_red = solid(color=my_red)
sequence main repeat forever {
if enable_effect {
play solid_red for 1s
}
}
run main
}
# Variable condition
set flag = true
sequence conditional {
if flag {
play animation for 2s
}
}
# Bidirectional animation with flags
template animation shutter_bidir {
param ascending type bool default true
param descending type bool default true
sequence shutter_seq repeat forever {
if ascending {
play shutter_lr for 2s
}
if descending {
play shutter_rl for 2s
}
}
run shutter_seq
}
```
**Comparison with Repeat:**
- `if condition { ... }` - Runs 0 or 1 times (boolean gate)
- `repeat count times { ... }` - Runs exactly `count` times (iteration)
#### Restart Statements
Restart statements allow you to restart value providers and animations from their initial state during sequence execution:
@ -1547,11 +1625,12 @@ property_assignment = identifier "." identifier "=" expression ;
(* Sequences *)
sequence = "sequence" identifier [ "repeat" ( expression "times" | "forever" ) ] "{" sequence_body "}" ;
sequence_body = { sequence_statement } ;
sequence_statement = play_stmt | wait_stmt | repeat_stmt | sequence_assignment | restart_stmt ;
sequence_statement = play_stmt | wait_stmt | repeat_stmt | if_stmt | sequence_assignment | restart_stmt ;
play_stmt = "play" identifier [ "for" time_expression ] ;
wait_stmt = "wait" time_expression ;
repeat_stmt = "repeat" ( expression "times" | "forever" ) "{" sequence_body "}" ;
if_stmt = "if" expression "{" sequence_body "}" ;
sequence_assignment = identifier "." identifier "=" expression ;
restart_stmt = "restart" identifier ;
@ -1676,7 +1755,8 @@ This applies to:
- Palette definitions with VRGB conversion
- Animation definitions with named parameters
- Property assignments
- Basic sequences (play, wait, repeat)
- Basic sequences (play, wait, repeat, if)
- **Conditional execution**: `if` statement for boolean-based conditional execution
- Variable assignments with type conversion
- Reserved name validation
- Parameter validation at compile time
@ -1693,7 +1773,7 @@ This applies to:
- Error recovery (basic error reporting)
### ❌ Planned Features
- Advanced control flow (if/else, choose random)
- Advanced control flow (else, elif, choose random)
- Event system and handlers
- Variable references with $ syntax
- Spatial operations and zones

View File

@ -1156,18 +1156,21 @@ class SimpleDSLTranspiler
elif tok.type == 0 #-animation_dsl.Token.KEYWORD-# && tok.value == "repeat"
self.process_repeat_statement_fluent()
elif tok.type == 0 #-animation_dsl.Token.KEYWORD-# && tok.value == "if"
self.process_if_statement_fluent()
elif tok.type == 1 #-animation_dsl.Token.IDENTIFIER-#
# Check if this is a property assignment (identifier.property = value)
if self.peek() != nil && self.peek().type == 33 #-animation_dsl.Token.DOT-#
self.process_sequence_assignment_fluent()
else
# Unknown identifier in sequence - this is an error
self.error(f"Unknown command '{tok.value}' in sequence. Valid sequence commands are: play, wait, repeat, restart, log, or property assignments (object.property = value)")
self.error(f"Unknown command '{tok.value}' in sequence. Valid sequence commands are: play, wait, repeat, if, restart, log, or property assignments (object.property = value)")
self.skip_statement()
end
else
# Unknown token type in sequence - this is an error
self.error(f"Invalid statement in sequence. Expected: play, wait, repeat, restart, log, or property assignments")
self.error(f"Invalid statement in sequence. Expected: play, wait, repeat, if, restart, log, or property assignments")
self.skip_statement()
end
end
@ -1387,6 +1390,43 @@ class SimpleDSLTranspiler
self.indent_level -= 1
end
# Process if statement (conditional execution - runs 0 or 1 times based on boolean)
def process_if_statement_fluent()
self.next() # skip 'if'
# Parse condition expression - use CONTEXT_EXPRESSION to avoid automatic function wrapping
var condition_result = self.process_additive_expression(self.CONTEXT_EXPRESSION, true, false)
self.expect_left_brace()
# Create a nested sub-sequence with bool() wrapper to ensure 0 or 1 iterations
# Check if expression is dynamic (needs closure) or static (can be evaluated directly)
var repeat_count_expr
if condition_result.has_dynamic
# Dynamic expression - wrap in closure
repeat_count_expr = f"def (engine) return bool({condition_result.expr}) end"
else
# Static expression - evaluate directly
repeat_count_expr = f"bool({condition_result.expr})"
end
self.add(f"{self.get_indent()}.push_repeat_subsequence(animation.sequence_manager(engine, {repeat_count_expr})")
# Increase indentation level for nested content
self.indent_level += 1
# Process if body recursively
while !self.at_end() && !self.check_right_brace()
self.process_sequence_statement()
end
self.expect_right_brace()
# Decrease indentation level and close the sub-sequence
self.add(f"{self.get_indent()})")
self.indent_level -= 1
end
# Process import statement: import user_functions or import module_name
def process_import()
self.next() # skip 'import'

View File

@ -386,6 +386,221 @@ def test_sequence_manager_stress()
print(f"✓ Stress test passed - {still_running} sequences still running out of 10")
end
def test_dsl_if_statement_static()
print("=== DSL If Statement (Static) Tests ===")
import animation_dsl
# Test 1: if with static true (should execute)
var dsl_source1 = "color my_red = 0xFF0000\n" +
"animation solid_red = solid(color=my_red)\n" +
"sequence test repeat forever {\n" +
" if true {\n" +
" play solid_red for 100ms\n" +
" }\n" +
"}\n" +
"run test"
var berry_code1 = animation_dsl.compile(dsl_source1)
assert(berry_code1 != nil, "Should compile if statement with true")
assert(string.find(berry_code1, "bool(true)") >= 0, "Should generate bool(true) for static true")
assert(string.find(berry_code1, "def (engine) return bool(true) end") < 0, "Should NOT wrap static true in closure")
# Test 2: if with static false (should not execute)
var dsl_source2 = "color my_blue = 0x0000FF\n" +
"animation solid_blue = solid(color=my_blue)\n" +
"sequence test repeat forever {\n" +
" if false {\n" +
" play solid_blue for 100ms\n" +
" }\n" +
"}\n" +
"run test"
var berry_code2 = animation_dsl.compile(dsl_source2)
assert(berry_code2 != nil, "Should compile if statement with false")
assert(string.find(berry_code2, "bool(false)") >= 0, "Should generate bool(false) for static false")
# Test 3: if with static number (should convert to boolean)
var dsl_source3 = "color my_green = 0x00FF00\n" +
"animation solid_green = solid(color=my_green)\n" +
"sequence test repeat forever {\n" +
" if 5 {\n" +
" play solid_green for 100ms\n" +
" }\n" +
"}\n" +
"run test"
var berry_code3 = animation_dsl.compile(dsl_source3)
assert(berry_code3 != nil, "Should compile if statement with number")
assert(string.find(berry_code3, "bool(5)") >= 0, "Should generate bool(5) for static number")
assert(string.find(berry_code3, "def (engine) return bool(5) end") < 0, "Should NOT wrap static number in closure")
print("✓ DSL if statement (static) tests passed")
end
def test_dsl_if_statement_dynamic()
print("=== DSL If Statement (Dynamic) Tests ===")
import animation_dsl
# Test 1: if with template parameter (dynamic)
var dsl_source1 = "template animation test_if {\n" +
" param flag type bool default true\n" +
" color my_red = 0xFF0000\n" +
" animation solid_red = solid(color=my_red)\n" +
" sequence test_seq repeat forever {\n" +
" if flag {\n" +
" play solid_red for 100ms\n" +
" }\n" +
" }\n" +
" run test_seq\n" +
"}\n" +
"animation main = test_if(flag=true)\n" +
"run main"
var berry_code1 = animation_dsl.compile(dsl_source1)
assert(berry_code1 != nil, "Should compile if statement with parameter")
assert(string.find(berry_code1, "def (engine) return bool(self.flag) end") >= 0, "Should wrap parameter in closure with bool()")
# Test 2: if with property access (dynamic)
var dsl_source2 = "color my_red = 0xFF0000\n" +
"color my_blue = 0x0000FF\n" +
"color col1 = color_cycle(cycle_period=0)\n" +
"animation solid_red = solid(color=my_red)\n" +
"set some_value = 1\n" +
"sequence test repeat forever {\n" +
" if some_value {\n" +
" play solid_red for 100ms\n" +
" }\n" +
"}\n" +
"run test"
var berry_code2 = animation_dsl.compile(dsl_source2)
assert(berry_code2 != nil, "Should compile if statement with variable")
assert(string.find(berry_code2, "def (engine) return bool(") >= 0, "Should wrap variable in closure with bool()")
print("✓ DSL if statement (dynamic) tests passed")
end
def test_dsl_if_statement_execution()
print("=== DSL If Statement Execution Tests ===")
import animation_dsl
# Test execution with true condition
var dsl_source_true = "color my_red = 0xFF0000\n" +
"animation solid_red = solid(color=my_red)\n" +
"sequence test {\n" +
" if true {\n" +
" play solid_red for 50ms\n" +
" }\n" +
"}\n" +
"run test"
var berry_code_true = animation_dsl.compile(dsl_source_true)
var compiled_true = compile(berry_code_true)
# Execute the compiled code
tasmota.set_millis(200000)
compiled_true()
# The sequence should have started
# We can't easily verify execution without accessing the engine, but compilation success is a good sign
# Test execution with false condition
var dsl_source_false = "color my_blue = 0x0000FF\n" +
"animation solid_blue = solid(color=my_blue)\n" +
"sequence test {\n" +
" if false {\n" +
" play solid_blue for 50ms\n" +
" }\n" +
"}\n" +
"run test"
var berry_code_false = animation_dsl.compile(dsl_source_false)
var compiled_false = compile(berry_code_false)
# Execute the compiled code
tasmota.set_millis(201000)
compiled_false()
print("✓ DSL if statement execution tests passed")
end
def test_dsl_if_statement_nested()
print("=== DSL If Statement Nested Tests ===")
import animation_dsl
# Test nested if statements
var dsl_source = "color my_red = 0xFF0000\n" +
"animation solid_red = solid(color=my_red)\n" +
"sequence test repeat forever {\n" +
" if true {\n" +
" if true {\n" +
" play solid_red for 50ms\n" +
" }\n" +
" }\n" +
"}\n" +
"run test"
var berry_code = animation_dsl.compile(dsl_source)
assert(berry_code != nil, "Should compile nested if statements")
# Count occurrences of push_repeat_subsequence (should have 3: outer repeat forever + 2 if statements)
var count = 0
var pos = 0
while true
pos = string.find(berry_code, "push_repeat_subsequence", pos)
if pos < 0
break
end
count += 1
pos += 1
end
assert(count == 2, f"Should have 2 push_repeat_subsequence calls (2 if), got {count}")
print("✓ DSL if statement nested tests passed")
end
def test_dsl_if_vs_repeat_comparison()
print("=== DSL If vs Repeat Comparison Tests ===")
import animation_dsl
# Test that 'if' uses bool() wrapper while 'repeat' doesn't
var dsl_if = "color my_red = 0xFF0000\n" +
"animation solid_red = solid(color=my_red)\n" +
"set flag = 1\n" +
"sequence test repeat forever {\n" +
" if flag {\n" +
" play solid_red for 50ms\n" +
" }\n" +
"}\n" +
"run test"
var dsl_repeat = "color my_blue = 0x0000FF\n" +
"animation solid_blue = solid(color=my_blue)\n" +
"set count = 1\n" +
"sequence test repeat forever {\n" +
" repeat count times {\n" +
" play solid_blue for 50ms\n" +
" }\n" +
"}\n" +
"run test"
var berry_if = animation_dsl.compile(dsl_if)
var berry_repeat = animation_dsl.compile(dsl_repeat)
# If statement should have bool() wrapper
assert(string.find(berry_if, "bool(") >= 0, "If statement should use bool() wrapper")
# Repeat statement should NOT have bool() wrapper
assert(string.find(berry_repeat, "bool(") < 0, "Repeat statement should NOT use bool() wrapper")
print("✓ DSL if vs repeat comparison tests passed")
end
# Run all layering tests
def run_all_sequence_manager_layering_tests()
print("Starting SequenceManager Layering Tests...")
@ -396,6 +611,11 @@ def run_all_sequence_manager_layering_tests()
test_sequence_manager_removal()
test_sequence_manager_clear_all()
test_sequence_manager_stress()
test_dsl_if_statement_static()
test_dsl_if_statement_dynamic()
test_dsl_if_statement_execution()
test_dsl_if_statement_nested()
test_dsl_if_vs_repeat_comparison()
print("\n🎉 All SequenceManager layering tests passed!")
return true
@ -411,5 +631,10 @@ return {
"test_sequence_manager_engine_integration": test_sequence_manager_engine_integration,
"test_sequence_manager_removal": test_sequence_manager_removal,
"test_sequence_manager_clear_all": test_sequence_manager_clear_all,
"test_sequence_manager_stress": test_sequence_manager_stress
"test_sequence_manager_stress": test_sequence_manager_stress,
"test_dsl_if_statement_static": test_dsl_if_statement_static,
"test_dsl_if_statement_dynamic": test_dsl_if_statement_dynamic,
"test_dsl_if_statement_execution": test_dsl_if_statement_execution,
"test_dsl_if_statement_nested": test_dsl_if_statement_nested,
"test_dsl_if_vs_repeat_comparison": test_dsl_if_vs_repeat_comparison
}