Berry animation add 'if' to sequence (#24056)
This commit is contained in:
parent
4ccc9f69fd
commit
ea0bf79291
@ -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
|
||||
@ -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
|
||||
|
||||
-#
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user