Berry animation transpiler refactoring (#23890)
This commit is contained in:
parent
e865041c4b
commit
1eb38f0b78
@ -1,36 +0,0 @@
|
||||
# Aurora Borealis - Northern lights simulation
|
||||
# Flowing green and purple aurora colors
|
||||
|
||||
#strip length 60
|
||||
|
||||
# Define aurora color palette
|
||||
palette aurora_colors = [
|
||||
(0, 0x000022), # Dark night sky
|
||||
(64, 0x004400), # Dark green
|
||||
(128, 0x00AA44), # Aurora green
|
||||
(192, 0x44AA88), # Light green
|
||||
(255, 0x88FFAA) # Bright aurora
|
||||
]
|
||||
|
||||
# Secondary purple palette
|
||||
palette aurora_purple = [
|
||||
(0, 0x220022), # Dark purple
|
||||
(64, 0x440044), # Medium purple
|
||||
(128, 0x8800AA), # Bright purple
|
||||
(192, 0xAA44CC), # Light purple
|
||||
(255, 0xCCAAFF) # Pale purple
|
||||
]
|
||||
|
||||
# Base aurora animation with slow flowing colors
|
||||
animation aurora_base = rich_palette_animation(
|
||||
palette=aurora_colors # palette
|
||||
cycle_period=10s # cycle period
|
||||
transition_type=SINE # transition type (explicit for clarity)
|
||||
brightness=180 # brightness (dimmed for aurora effect)
|
||||
)
|
||||
|
||||
sequence demo {
|
||||
play aurora_base # infinite duration (no 'for' clause)
|
||||
}
|
||||
|
||||
run demo
|
||||
@ -1,80 +0,0 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: aurora_borealis.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Aurora Borealis - Northern lights simulation
|
||||
# Flowing green and purple aurora colors
|
||||
#strip length 60
|
||||
# Define aurora color palette
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var aurora_colors_ = bytes(
|
||||
"00000022" # Dark night sky
|
||||
"40004400" # Dark green
|
||||
"8000AA44" # Aurora green
|
||||
"C044AA88" # Light green
|
||||
"FF88FFAA" # Bright aurora
|
||||
)
|
||||
# Secondary purple palette
|
||||
var aurora_purple_ = bytes(
|
||||
"00220022" # Dark purple
|
||||
"40440044" # Medium purple
|
||||
"808800AA" # Bright purple
|
||||
"C0AA44CC" # Light purple
|
||||
"FFCCAAFF" # Pale purple
|
||||
)
|
||||
# Base aurora animation with slow flowing colors
|
||||
var aurora_base_ = animation.rich_palette_animation(engine)
|
||||
aurora_base_.palette = aurora_colors_ # palette
|
||||
aurora_base_.cycle_period = 10000 # cycle period
|
||||
aurora_base_.transition_type = animation.SINE # transition type (explicit for clarity)
|
||||
aurora_base_.brightness = 180 # brightness (dimmed for aurora effect)
|
||||
var demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(aurora_base_, nil) # infinite duration (no 'for' clause)
|
||||
engine.add(demo_)
|
||||
engine.run()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Aurora Borealis - Northern lights simulation
|
||||
# Flowing green and purple aurora colors
|
||||
|
||||
#strip length 60
|
||||
|
||||
# Define aurora color palette
|
||||
palette aurora_colors = [
|
||||
(0, 0x000022) # Dark night sky
|
||||
(64, 0x004400) # Dark green
|
||||
(128, 0x00AA44) # Aurora green
|
||||
(192, 0x44AA88) # Light green
|
||||
(255, 0x88FFAA) # Bright aurora
|
||||
]
|
||||
|
||||
# Secondary purple palette
|
||||
palette aurora_purple = [
|
||||
(0, 0x220022) # Dark purple
|
||||
(64, 0x440044) # Medium purple
|
||||
(128, 0x8800AA) # Bright purple
|
||||
(192, 0xAA44CC) # Light purple
|
||||
(255, 0xCCAAFF) # Pale purple
|
||||
]
|
||||
|
||||
# Base aurora animation with slow flowing colors
|
||||
animation aurora_base = rich_palette_animation(
|
||||
palette=aurora_colors # palette
|
||||
cycle_period=10s # cycle period
|
||||
transition_type=SINE # transition type (explicit for clarity)
|
||||
brightness=180 # brightness (dimmed for aurora effect)
|
||||
)
|
||||
|
||||
sequence demo {
|
||||
play aurora_base # infinite duration (no 'for' clause)
|
||||
}
|
||||
|
||||
run demo
|
||||
-#
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,144 +0,0 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: demo_shutter_rainbow.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from left to right iterating in all colors, then right to left
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: shutter_bidir
|
||||
def shutter_bidir_template(engine, colors_, duration_)
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var shutter_size_ = (def (engine)
|
||||
var provider = animation.sawtooth(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = strip_len_
|
||||
provider.duration = duration_
|
||||
return provider
|
||||
end)(engine)
|
||||
var col1_ = animation.color_cycle(engine)
|
||||
col1_.palette = colors_
|
||||
col1_.cycle_period = 0
|
||||
var col2_ = animation.color_cycle(engine)
|
||||
col2_.palette = colors_
|
||||
col2_.cycle_period = 0
|
||||
col2_.next = 1
|
||||
# shutter moving from left to right
|
||||
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 from right to left
|
||||
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 (self) return self.resolve(strip_len_) - self.resolve(shutter_size_) end)
|
||||
shutter_rl_animation_.slew_size = 0
|
||||
shutter_rl_animation_.priority = 5
|
||||
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_lr_animation_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
.push_repeat_subsequence(animation.SequenceManager(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_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
engine.add(shutter_seq_)
|
||||
end
|
||||
|
||||
animation.register_user_function('shutter_bidir', shutter_bidir_template)
|
||||
|
||||
var rainbow_with_white_ = bytes(
|
||||
"FFFF0000"
|
||||
"FFFFA500"
|
||||
"FFFFFF00"
|
||||
"FF008000" # comma left on-purpose to test transpiler
|
||||
"FF0000FF" # need for a lighter blue
|
||||
"FF4B0082"
|
||||
"FFFFFFFF"
|
||||
)
|
||||
shutter_bidir_template(engine, rainbow_with_white_, 1500)
|
||||
engine.run()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from left to right iterating in all colors, then right to left
|
||||
|
||||
template shutter_bidir {
|
||||
param colors type palette
|
||||
param duration
|
||||
|
||||
set strip_len = strip_length()
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
# shutter moving from left to right
|
||||
animation shutter_lr_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# shutter moving from right to left
|
||||
animation shutter_rl_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = 0
|
||||
beacon_size = strip_len - shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
sequence shutter_seq repeat forever {
|
||||
repeat col1.palette_size times {
|
||||
reset shutter_size
|
||||
play shutter_lr_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
reset shutter_size
|
||||
play shutter_rl_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
}
|
||||
|
||||
palette rainbow_with_white = [ red
|
||||
orange
|
||||
yellow
|
||||
green, # comma left on-purpose to test transpiler
|
||||
blue # need for a lighter blue
|
||||
indigo
|
||||
white
|
||||
]
|
||||
|
||||
shutter_bidir(rainbow_with_white, 1.5s)
|
||||
|
||||
-#
|
||||
@ -9,9 +9,6 @@ import animation
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from center to both left and right
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: shutter_central
|
||||
def shutter_central_template(engine, colors_, duration_)
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
@ -30,24 +27,43 @@ def shutter_central_template(engine, colors_, duration_)
|
||||
col2_.palette = colors_
|
||||
col2_.cycle_period = 0
|
||||
col2_.next = 1
|
||||
# shutter moving from left to right
|
||||
var shutter_central_animation_ = animation.beacon_animation(engine)
|
||||
shutter_central_animation_.color = col2_
|
||||
shutter_central_animation_.back_color = col1_
|
||||
shutter_central_animation_.pos = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len2_) - animation.resolve(shutter_size_) / 2 end)
|
||||
shutter_central_animation_.beacon_size = shutter_size_
|
||||
shutter_central_animation_.slew_size = 0
|
||||
shutter_central_animation_.priority = 5
|
||||
# shutter moving in to out
|
||||
var shutter_inout_animation_ = animation.beacon_animation(engine)
|
||||
shutter_inout_animation_.color = col2_
|
||||
shutter_inout_animation_.back_color = col1_
|
||||
shutter_inout_animation_.pos = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len2_) - (animation.resolve(shutter_size_) + 1) / 2 end)
|
||||
shutter_inout_animation_.beacon_size = shutter_size_
|
||||
shutter_inout_animation_.slew_size = 0
|
||||
shutter_inout_animation_.priority = 5
|
||||
# shutter moving out to in
|
||||
var shutter_outin_animation_ = animation.beacon_animation(engine)
|
||||
shutter_outin_animation_.color = col1_
|
||||
shutter_outin_animation_.back_color = col2_
|
||||
shutter_outin_animation_.pos = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len2_) - (animation.resolve(strip_len_) - animation.resolve(shutter_size_) + 1) / 2 end)
|
||||
shutter_outin_animation_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - animation.resolve(shutter_size_) end)
|
||||
shutter_outin_animation_.slew_size = 0
|
||||
shutter_outin_animation_.priority = 5
|
||||
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_central_animation_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_inout_animation_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_outin_animation_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
engine.add(shutter_seq_)
|
||||
end
|
||||
|
||||
animation.register_user_function('shutter_central', shutter_central_template)
|
||||
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var rainbow_with_white_ = bytes(
|
||||
"FFFF0000"
|
||||
"FFFFA500"
|
||||
@ -78,21 +94,39 @@ template shutter_central {
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
# shutter moving from left to right
|
||||
animation shutter_central_animation = beacon_animation(
|
||||
# shutter moving in to out
|
||||
animation shutter_inout_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = strip_len2 - shutter_size / 2
|
||||
pos = strip_len2 - (shutter_size + 1) / 2
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# shutter moving out to in
|
||||
animation shutter_outin_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = strip_len2 - (strip_len - shutter_size + 1) / 2
|
||||
beacon_size = strip_len - shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
sequence shutter_seq repeat forever {
|
||||
restart shutter_size
|
||||
play shutter_central_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_inout_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_outin_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
|
||||
@ -23,11 +23,13 @@ var disco_colors_ = bytes(
|
||||
"FFFF00FF" # Magenta
|
||||
)
|
||||
# Fast color cycling base
|
||||
var disco_base_ = animation.rich_palette_animation(engine)
|
||||
disco_base_.palette = disco_colors_
|
||||
disco_base_.cycle_period = 1000
|
||||
disco_base_.transition_type = animation.LINEAR
|
||||
disco_base_.brightness = 255
|
||||
var disco_rich_color_ = animation.rich_palette(engine)
|
||||
disco_rich_color_.palette = disco_colors_
|
||||
disco_rich_color_.cycle_period = 1000
|
||||
disco_rich_color_.transition_type = animation.LINEAR
|
||||
disco_rich_color_.brightness = 255
|
||||
var disco_base_ = animation.solid(engine)
|
||||
disco_base_.color = disco_rich_color_
|
||||
# Add strobe effect
|
||||
disco_base_.opacity = (def (engine)
|
||||
var provider = animation.square(engine)
|
||||
@ -105,7 +107,8 @@ palette disco_colors = [
|
||||
]
|
||||
|
||||
# Fast color cycling base
|
||||
animation disco_base = rich_palette_animation(palette=disco_colors, cycle_period=1s, transition_type=LINEAR, brightness=255)
|
||||
color disco_rich_color = rich_palette(palette=disco_colors, cycle_period=1s, transition_type=LINEAR, brightness=255)
|
||||
animation disco_base = solid(color=disco_rich_color)
|
||||
|
||||
# Add strobe effect
|
||||
disco_base.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=30) # Fast strobe
|
||||
|
||||
@ -21,11 +21,13 @@ var fire_colors_ = bytes(
|
||||
"FFFFFF00" # Yellow
|
||||
)
|
||||
# Create base fire animation with palette
|
||||
var fire_base_ = animation.rich_palette_animation(engine)
|
||||
fire_base_.palette = fire_colors_
|
||||
fire_base_.cycle_period = 3000
|
||||
fire_base_.transition_type = animation.LINEAR
|
||||
fire_base_.brightness = 255
|
||||
var fire_base_color_ = animation.rich_palette(engine)
|
||||
fire_base_color_.palette = fire_colors_
|
||||
fire_base_color_.cycle_period = 3000
|
||||
fire_base_color_.transition_type = animation.LINEAR
|
||||
fire_base_color_.brightness = 255
|
||||
var fire_base_ = animation.solid(engine)
|
||||
fire_base_.color = fire_base_color_
|
||||
# Add flickering effect with random intensity changes
|
||||
fire_base_.opacity = (def (engine)
|
||||
var provider = animation.smooth(engine)
|
||||
@ -67,7 +69,8 @@ palette fire_colors = [
|
||||
]
|
||||
|
||||
# Create base fire animation with palette
|
||||
animation fire_base = rich_palette_animation(palette=fire_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
|
||||
color fire_base_color = rich_palette(palette=fire_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
|
||||
animation fire_base = solid(color=fire_base_color)
|
||||
|
||||
# Add flickering effect with random intensity changes
|
||||
fire_base.opacity = smooth(min_value=180, max_value=255, duration=800ms)
|
||||
|
||||
@ -42,13 +42,13 @@ import user_functions
|
||||
|
||||
# Create animations that use imported user functions
|
||||
animation random_red = solid(color=red)
|
||||
random_red.opacity = user.rand_demo()
|
||||
random_red.opacity = rand_demo()
|
||||
|
||||
animation breathing_blue = solid(color=blue)
|
||||
breathing_blue.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
breathing_blue.opacity = max(50, min(255, rand_demo() + 100))
|
||||
|
||||
animation dynamic_green = solid(color=green)
|
||||
dynamic_green.opacity = abs(user.rand_demo() - 128) + 64
|
||||
dynamic_green.opacity = abs(rand_demo() - 128) + 64
|
||||
|
||||
# Create a sequence that cycles through the animations
|
||||
sequence import_demo {
|
||||
|
||||
@ -49,9 +49,13 @@ var sunset_sky_ = bytes(
|
||||
"FFFFFF00" # Sun
|
||||
)
|
||||
# Create animations using each palette
|
||||
var fire_effect_ = animation.rich_palette_animation(engine)
|
||||
fire_effect_.palette = fire_gradient_
|
||||
fire_effect_.cycle_period = 3000
|
||||
var fire_effect_ = animation.solid(engine)
|
||||
fire_effect_.color = (def (engine)
|
||||
var provider = animation.rich_palette(engine)
|
||||
provider.palette = fire_gradient_
|
||||
provider.cycle_period = 3000
|
||||
return provider
|
||||
end)(engine)
|
||||
var ocean_waves_ = animation.rich_palette_animation(engine)
|
||||
ocean_waves_.palette = ocean_depths_
|
||||
ocean_waves_.cycle_period = 8000
|
||||
@ -139,7 +143,7 @@ palette sunset_sky = [
|
||||
]
|
||||
|
||||
# Create animations using each palette
|
||||
animation fire_effect = rich_palette_animation(palette=fire_gradient, cycle_period=3s)
|
||||
animation fire_effect = solid(color=rich_palette(palette=fire_gradient, cycle_period=3s))
|
||||
|
||||
animation ocean_waves = rich_palette_animation(palette=ocean_depths, cycle_period=8s, transition_type=SINE, brightness=200)
|
||||
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env berry
|
||||
# Test runner for compiled DSL examples
|
||||
# Generated automatically by compile_dsl_examples.be
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add animation library path
|
||||
sys.path().push("lib/libesp32/berry_animation/src")
|
||||
|
||||
# Import animation framework
|
||||
import animation
|
||||
import animation_dsl
|
||||
|
||||
def run_compiled_example(filename)
|
||||
print(f"Running {filename}...")
|
||||
try
|
||||
var f = open(f"lib/libesp32/berry_animation/anim_examples/compiled/{filename}", "r")
|
||||
var code = f.read()
|
||||
f.close()
|
||||
|
||||
var compiled_func = compile(code)
|
||||
if compiled_func != nil
|
||||
compiled_func()
|
||||
print(f" ✓ {filename} executed successfully")
|
||||
return true
|
||||
else
|
||||
print(f" ✗ {filename} failed to compile")
|
||||
return false
|
||||
end
|
||||
except .. as e, msg
|
||||
print(f" ✗ {filename} execution failed: {msg}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def run_all_examples()
|
||||
var files = os.listdir("lib/libesp32/berry_animation/anim_examples/compiled")
|
||||
var success_count = 0
|
||||
var total_count = 0
|
||||
|
||||
for file : files
|
||||
if string.endswith(file, ".be")
|
||||
total_count += 1
|
||||
if run_compiled_example(file)
|
||||
success_count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print(f"\nTest Results: {success_count}/{total_count} examples ran successfully")
|
||||
end
|
||||
|
||||
# Run all examples if script is executed directly
|
||||
run_all_examples()
|
||||
@ -0,0 +1,29 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: test_compute_multiple.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Computed Values Demo - Example from the original request
|
||||
# Shows how to use computed values from value providers
|
||||
# Get the current strip length
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var a_ = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) / 4 end)
|
||||
var b_ = animation.create_closure_value(engine, def (engine) return animation._math.abs(animation.resolve(strip_len_) / 4) end)
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Computed Values Demo - Example from the original request
|
||||
# Shows how to use computed values from value providers
|
||||
|
||||
# Get the current strip length
|
||||
set strip_len = strip_length()
|
||||
set a = strip_len / 4
|
||||
set b = abs(strip_len / 4)
|
||||
|
||||
-#
|
||||
@ -9,9 +9,6 @@ import animation
|
||||
# Demo Shutter Rainbow Bidir
|
||||
#
|
||||
# Shutter from left to right iterating in all colors, then right to left
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: shutter_bidir
|
||||
def shutter_bidir_template(engine, colors_, duration_)
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
@ -43,16 +40,18 @@ def shutter_bidir_template(engine, colors_, duration_)
|
||||
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 = animation.create_closure_value(engine, def (engine) return 0 + 0 end)
|
||||
shutter_rl_animation_.slew_size = 0 + 0
|
||||
shutter_rl_animation_.priority = 5
|
||||
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||
.push_closure_step(def (engine) log(f"begin 1", 3) end)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_lr_animation_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||
.push_closure_step(def (engine) log(f"begin 2", 3) end)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_rl_animation_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
@ -63,6 +62,9 @@ end
|
||||
|
||||
animation.register_user_function('shutter_bidir', shutter_bidir_template)
|
||||
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var rainbow_with_white_ = bytes(
|
||||
"FFFF0000"
|
||||
"FFFFA500"
|
||||
@ -114,12 +116,14 @@ template shutter_bidir {
|
||||
|
||||
sequence shutter_seq repeat forever {
|
||||
repeat col1.palette_size times {
|
||||
log("begin 1")
|
||||
restart shutter_size
|
||||
play shutter_lr_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
log("begin 2")
|
||||
restart shutter_size
|
||||
play shutter_rl_animation for duration
|
||||
col1.next = 1
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: test_shutter_rainbow_central.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from center to both left and right
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: shutter_central
|
||||
def shutter_central_template(engine, colors_, duration_)
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var strip_len2_ = animation.create_closure_value(engine, def (engine) return (animation.resolve(strip_len_) + 1) / 2 end)
|
||||
# the following is highly discouraged because it creates a new value provider at each tick
|
||||
var strip_len3_ = animation.create_closure_value(engine, def (engine) return (animation.resolve(animation.strip_length(engine)) + 1) / 2 end)
|
||||
var shutter_size_ = (def (engine)
|
||||
var provider = animation.sawtooth(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = strip_len_
|
||||
provider.duration = duration_
|
||||
return provider
|
||||
end)(engine)
|
||||
var col1_ = animation.color_cycle(engine)
|
||||
col1_.palette = colors_
|
||||
col1_.cycle_period = 0
|
||||
var col2_ = animation.color_cycle(engine)
|
||||
col2_.palette = colors_
|
||||
col2_.cycle_period = 0
|
||||
col2_.next = 1
|
||||
# shutter moving from left to right
|
||||
var shutter_central_animation_ = animation.beacon_animation(engine)
|
||||
shutter_central_animation_.color = col2_
|
||||
shutter_central_animation_.back_color = col1_
|
||||
shutter_central_animation_.pos = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len3_) - animation.resolve(shutter_size_) / 2 end)
|
||||
shutter_central_animation_.beacon_size = shutter_size_
|
||||
shutter_central_animation_.slew_size = 0
|
||||
shutter_central_animation_.priority = 5
|
||||
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_central_animation_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
engine.add(shutter_seq_)
|
||||
end
|
||||
|
||||
animation.register_user_function('shutter_central', shutter_central_template)
|
||||
|
||||
var rainbow_with_white_ = bytes(
|
||||
"FFFF0000"
|
||||
"FFFFA500"
|
||||
"FFFFFF00"
|
||||
"FF008000" # comma left on-purpose to test transpiler
|
||||
"FF0000FF" # need for a lighter blue
|
||||
"FF4B0082"
|
||||
"FFFFFFFF"
|
||||
)
|
||||
shutter_central_template(engine, rainbow_with_white_, 1500)
|
||||
engine.run()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from center to both left and right
|
||||
|
||||
template shutter_central {
|
||||
param colors type palette
|
||||
param duration
|
||||
|
||||
set strip_len = strip_length()
|
||||
set strip_len2 = (strip_len + 1) / 2
|
||||
# the following is highly discouraged because it creates a new value provider at each tick
|
||||
set strip_len3 = (strip_length() + 1) / 2
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
# shutter moving from left to right
|
||||
animation shutter_central_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = strip_len3 - shutter_size / 2
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
sequence shutter_seq repeat forever {
|
||||
restart shutter_size
|
||||
play shutter_central_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
}
|
||||
|
||||
palette rainbow_with_white = [ red
|
||||
orange
|
||||
yellow
|
||||
green, # comma left on-purpose to test transpiler
|
||||
blue # need for a lighter blue
|
||||
indigo
|
||||
white
|
||||
]
|
||||
|
||||
shutter_central(rainbow_with_white, 1.5s)
|
||||
|
||||
-#
|
||||
@ -0,0 +1,23 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: test_simple_math.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Test simple math
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var x_ = 2000
|
||||
var y_ = animation.create_closure_value(engine, def (engine) return animation.resolve(x_) + 4 end)
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Test simple math
|
||||
|
||||
set x = 2s
|
||||
set y = x + 4
|
||||
|
||||
-#
|
||||
@ -68,7 +68,7 @@ animation random_base = solid(
|
||||
priority=10
|
||||
)
|
||||
# Use user function in property assignment
|
||||
random_base.opacity = user.rand_demo()
|
||||
random_base.opacity = rand_demo()
|
||||
|
||||
# Example 2: User function with mathematical operations
|
||||
animation random_bounded = solid(
|
||||
@ -76,7 +76,7 @@ animation random_bounded = solid(
|
||||
priority=8
|
||||
)
|
||||
# User function with bounds using math functions
|
||||
random_bounded.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
random_bounded.opacity = max(50, min(255, rand_demo() + 100))
|
||||
|
||||
# Example 3: User function in arithmetic expressions
|
||||
animation random_variation = solid(
|
||||
@ -84,7 +84,7 @@ animation random_variation = solid(
|
||||
priority=15
|
||||
)
|
||||
# Mix user function with arithmetic operations
|
||||
random_variation.opacity = abs(user.rand_demo() - 128) + 64
|
||||
random_variation.opacity = abs(rand_demo() - 128) + 64
|
||||
|
||||
# Example 4: User function affecting different properties
|
||||
animation random_multi = solid(
|
||||
@ -92,7 +92,7 @@ animation random_multi = solid(
|
||||
priority=12
|
||||
)
|
||||
# Use user function for multiple properties
|
||||
random_multi.opacity = max(100, user.rand_demo())
|
||||
random_multi.opacity = max(100, rand_demo())
|
||||
|
||||
# Example 5: Complex expression with user function
|
||||
animation random_complex = solid(
|
||||
@ -100,7 +100,7 @@ animation random_complex = solid(
|
||||
priority=20
|
||||
)
|
||||
# Complex expression with user function and math operations
|
||||
random_complex.opacity = round((user.rand_demo() + 128) / 2 + abs(user.rand_demo() - 100))
|
||||
random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))
|
||||
|
||||
# Run all animations to demonstrate the effects
|
||||
run random_base
|
||||
|
||||
@ -14,21 +14,39 @@ template shutter_central {
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
# shutter moving from left to right
|
||||
animation shutter_central_animation = beacon_animation(
|
||||
# shutter moving in to out
|
||||
animation shutter_inout_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = strip_len2 - shutter_size / 2
|
||||
pos = strip_len2 - (shutter_size + 1) / 2
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# shutter moving out to in
|
||||
animation shutter_outin_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = strip_len2 - (strip_len - shutter_size + 1) / 2
|
||||
beacon_size = strip_len - shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
sequence shutter_seq repeat forever {
|
||||
restart shutter_size
|
||||
play shutter_central_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_inout_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_outin_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
|
||||
@ -15,7 +15,8 @@ palette disco_colors = [
|
||||
]
|
||||
|
||||
# Fast color cycling base
|
||||
animation disco_base = rich_palette_animation(palette=disco_colors, cycle_period=1s, transition_type=LINEAR, brightness=255)
|
||||
color disco_rich_color = rich_palette(palette=disco_colors, cycle_period=1s, transition_type=LINEAR, brightness=255)
|
||||
animation disco_base = solid(color=disco_rich_color)
|
||||
|
||||
# Add strobe effect
|
||||
disco_base.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=30) # Fast strobe
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
|
||||
set red = 0xFF0000
|
||||
@ -0,0 +1,4 @@
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
|
||||
set abs = 0xFF0000
|
||||
@ -0,0 +1,6 @@
|
||||
# Fail value_provider addition
|
||||
#1+1
|
||||
|
||||
set a = linear() + triangle()
|
||||
set b = triangle()
|
||||
set c = linear() + triangle()
|
||||
@ -13,7 +13,8 @@ palette fire_colors = [
|
||||
]
|
||||
|
||||
# Create base fire animation with palette
|
||||
animation fire_base = rich_palette_animation(palette=fire_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
|
||||
color fire_base_color = rich_palette(palette=fire_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
|
||||
animation fire_base = solid(color=fire_base_color)
|
||||
|
||||
# Add flickering effect with random intensity changes
|
||||
fire_base.opacity = smooth(min_value=180, max_value=255, duration=800ms)
|
||||
|
||||
@ -6,13 +6,13 @@ import user_functions
|
||||
|
||||
# Create animations that use imported user functions
|
||||
animation random_red = solid(color=red)
|
||||
random_red.opacity = user.rand_demo()
|
||||
random_red.opacity = rand_demo()
|
||||
|
||||
animation breathing_blue = solid(color=blue)
|
||||
breathing_blue.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
breathing_blue.opacity = max(50, min(255, rand_demo() + 100))
|
||||
|
||||
animation dynamic_green = solid(color=green)
|
||||
dynamic_green.opacity = abs(user.rand_demo() - 128) + 64
|
||||
dynamic_green.opacity = abs(rand_demo() - 128) + 64
|
||||
|
||||
# Create a sequence that cycles through the animations
|
||||
sequence import_demo {
|
||||
|
||||
@ -44,7 +44,7 @@ palette sunset_sky = [
|
||||
]
|
||||
|
||||
# Create animations using each palette
|
||||
animation fire_effect = rich_palette_animation(palette=fire_gradient, cycle_period=3s)
|
||||
animation fire_effect = solid(color=rich_palette(palette=fire_gradient, cycle_period=3s))
|
||||
|
||||
animation ocean_waves = rich_palette_animation(palette=ocean_depths, cycle_period=8s, transition_type=SINE, brightness=200)
|
||||
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
# Computed Values Demo - Example from the original request
|
||||
# Shows how to use computed values from value providers
|
||||
|
||||
# Get the current strip length
|
||||
set strip_len = strip_length()
|
||||
set a = strip_len / 4
|
||||
set b = abs(strip_len / 4)
|
||||
@ -0,0 +1,29 @@
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
|
||||
template cylon_effect {
|
||||
param eye_color type color
|
||||
param back_color type color
|
||||
param duration
|
||||
|
||||
set aaa = abs2(45) + 2
|
||||
set strip_len = strip_length()
|
||||
set strip_len_div_2 = abs(strip_len / 2)
|
||||
set strip_len2 = strip_length(is_running=0) # artifically putting an argument to cause a closure
|
||||
|
||||
set osc1 = sawtooth()
|
||||
set osc2 = triangle()
|
||||
set osc3 = scale(osc1 + osc2, 0, 200, 0, 255)
|
||||
set osc9 = sawtooth() + triangle() # this should fail
|
||||
|
||||
animation eye_animation = beacon_animation(
|
||||
color = eye_color
|
||||
back_color = back_color
|
||||
pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = duration)
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 5
|
||||
)
|
||||
|
||||
run eye_animation
|
||||
}
|
||||
@ -35,12 +35,14 @@ template shutter_bidir {
|
||||
|
||||
sequence shutter_seq repeat forever {
|
||||
repeat col1.palette_size times {
|
||||
log("begin 1")
|
||||
restart shutter_size
|
||||
play shutter_lr_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
log("begin 2")
|
||||
restart shutter_size
|
||||
play shutter_rl_animation for duration
|
||||
col1.next = 1
|
||||
|
||||
@ -9,7 +9,8 @@ template shutter_central {
|
||||
set strip_len = strip_length()
|
||||
set strip_len2 = (strip_len + 1) / 2
|
||||
# the following is highly discouraged because it creates a new value provider at each tick
|
||||
set strip_len3 = (strip_length() + 1) / 2
|
||||
set strip_len3 = 1 + strip_length()
|
||||
set strip_len4 = (strip_length() + 1) / 2
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
# Test simple math
|
||||
|
||||
set x = 2s
|
||||
set y = x + 4
|
||||
@ -12,7 +12,7 @@ animation random_base = solid(
|
||||
priority=10
|
||||
)
|
||||
# Use user function in property assignment
|
||||
random_base.opacity = user.rand_demo()
|
||||
random_base.opacity = rand_demo()
|
||||
|
||||
# Example 2: User function with mathematical operations
|
||||
animation random_bounded = solid(
|
||||
@ -20,7 +20,7 @@ animation random_bounded = solid(
|
||||
priority=8
|
||||
)
|
||||
# User function with bounds using math functions
|
||||
random_bounded.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
random_bounded.opacity = max(50, min(255, rand_demo() + 100))
|
||||
|
||||
# Example 3: User function in arithmetic expressions
|
||||
animation random_variation = solid(
|
||||
@ -28,7 +28,7 @@ animation random_variation = solid(
|
||||
priority=15
|
||||
)
|
||||
# Mix user function with arithmetic operations
|
||||
random_variation.opacity = abs(user.rand_demo() - 128) + 64
|
||||
random_variation.opacity = abs(rand_demo() - 128) + 64
|
||||
|
||||
# Example 4: User function affecting different properties
|
||||
animation random_multi = solid(
|
||||
@ -36,7 +36,7 @@ animation random_multi = solid(
|
||||
priority=12
|
||||
)
|
||||
# Use user function for multiple properties
|
||||
random_multi.opacity = max(100, user.rand_demo())
|
||||
random_multi.opacity = max(100, rand_demo())
|
||||
|
||||
# Example 5: Complex expression with user function
|
||||
animation random_complex = solid(
|
||||
@ -44,7 +44,7 @@ animation random_complex = solid(
|
||||
priority=20
|
||||
)
|
||||
# Complex expression with user function and math operations
|
||||
random_complex.opacity = round((user.rand_demo() + 128) / 2 + abs(user.rand_demo() - 100))
|
||||
random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))
|
||||
|
||||
# Run all animations to demonstrate the effects
|
||||
run random_base
|
||||
|
||||
@ -18,21 +18,14 @@ ParameterizedObject
|
||||
├── Animation
|
||||
│ ├── BreatheAnimation
|
||||
│ ├── CometAnimation
|
||||
│ ├── SparkleAnimation
|
||||
│ ├── BounceAnimation
|
||||
│ ├── FireAnimation
|
||||
│ ├── GradientAnimation
|
||||
│ ├── JitterAnimation
|
||||
│ ├── NoiseAnimation
|
||||
│ ├── PlasmaAnimation
|
||||
│ ├── PulseAnimation
|
||||
│ ├── BeaconAnimation
|
||||
│ ├── CrenelPositionAnimation
|
||||
│ ├── RichPaletteAnimation
|
||||
│ ├── TwinkleAnimation
|
||||
│ ├── WaveAnimation
|
||||
│ ├── ShiftAnimation
|
||||
│ ├── ScaleAnimation
|
||||
│ ├── PalettePatternAnimation
|
||||
│ │ ├── PaletteWaveAnimation
|
||||
│ │ ├── PaletteGradientAnimation
|
||||
@ -442,119 +435,8 @@ Creates a comet effect with a bright head and fading tail. Inherits from `Animat
|
||||
|
||||
**Factory**: `animation.comet_animation(engine)`
|
||||
|
||||
### SparkleAnimation
|
||||
|
||||
Creates random twinkling effects where individual pixels appear as sparkles that fade out over time. Perfect for starfield effects, magical sparkles, or glitter-like accents. Inherits from `Animation`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `color` | int | 0xFFFFFFFF | - | Sparkle color |
|
||||
| `back_color` | int | 0xFF000000 | - | Background color shown when no sparkle is active |
|
||||
| `density` | int | 30 | 0-255 | Sparkle frequency (0=none, 255=maximum rate) |
|
||||
| `fade_speed` | int | 50 | 0-255 | How quickly sparkles dim and fade out |
|
||||
| `sparkle_duration` | int | 60 | 0-255 | How long sparkles last in frames (~30 FPS) |
|
||||
| `min_brightness` | int | 100 | 0-255 | Minimum brightness for new sparkles |
|
||||
| `max_brightness` | int | 255 | 0-255 | Maximum brightness for new sparkles |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
|
||||
#### Sparkle Lifecycle
|
||||
|
||||
Each sparkle follows a predictable lifecycle:
|
||||
1. **Creation**: Random appearance based on density parameter
|
||||
2. **Brightness**: Random value between min_brightness and max_brightness
|
||||
3. **Aging**: Frame-by-frame age tracking
|
||||
4. **Fading**: Brightness reduction based on age and fade_speed
|
||||
5. **Death**: Sparkle removed when too dim or duration exceeded
|
||||
|
||||
#### Density Effects
|
||||
|
||||
- **Low density (10-40)**: Occasional, subtle sparkles
|
||||
- **Medium density (50-100)**: Regular twinkling effect
|
||||
- **High density (120-200)**: Frequent, busy sparkles
|
||||
- **Maximum density (255)**: Nearly constant sparkles
|
||||
|
||||
#### Fade Speed Effects
|
||||
|
||||
- **Slow fade (10-30)**: Long, gentle fade-out
|
||||
- **Medium fade (40-80)**: Balanced sparkle lifecycle
|
||||
- **Fast fade (100-200)**: Quick, snappy sparkles
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# Basic white starfield
|
||||
animation starfield = sparkle_animation(
|
||||
color=white,
|
||||
back_color=black,
|
||||
density=80,
|
||||
fade_speed=60
|
||||
)
|
||||
|
||||
# Magical rainbow sparkles
|
||||
animation magic_sparkles = sparkle_animation(
|
||||
color=rainbow_cycle,
|
||||
density=100,
|
||||
fade_speed=50,
|
||||
min_brightness=80,
|
||||
max_brightness=220
|
||||
)
|
||||
|
||||
# Subtle ambient sparkles
|
||||
animation ambient_sparkles = sparkle_animation(
|
||||
color=0xFFFFFFAA,
|
||||
density=20,
|
||||
fade_speed=30
|
||||
)
|
||||
```
|
||||
|
||||
#### Common Use Cases
|
||||
|
||||
- **Starfield**: White sparkles on black background with low density
|
||||
- **Magic Effects**: Rainbow sparkles with medium density
|
||||
- **Accent Lighting**: Subtle colored sparkles over other effects
|
||||
- **Party Atmosphere**: High-density, fast-fading sparkles
|
||||
- **Ambient Decoration**: Low-density, slow-fading warm sparkles
|
||||
|
||||
### BounceAnimation
|
||||
|
||||
Creates physics-based bouncing effects with configurable gravity, damping, and motion parameters. Inherits from `Animation`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `source_animation` | instance | nil | - | Animation to bounce |
|
||||
| `bounce_speed` | int | 128 | 0-255 | Initial bounce speed |
|
||||
| `bounce_range` | int | 0 | 0-1000 | Bounce range in pixels (0 = full strip) |
|
||||
| `damping` | int | 250 | 0-255 | Velocity damping factor (255 = no damping) |
|
||||
| `gravity` | int | 0 | 0-255 | Gravity strength (0 = no gravity) |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
|
||||
#### Physics Behavior
|
||||
|
||||
- **Realistic Physics**: Simulates gravity, velocity, and damping
|
||||
- **Bounce Range**: Can be constrained to specific strip regions
|
||||
- **Damping Effects**: Controls energy loss on each bounce
|
||||
- **Gravity Simulation**: Optional downward acceleration
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# Bouncing ball effect with gravity
|
||||
animation ball = pulsating_animation(color=green, period=2s)
|
||||
animation bouncing_ball = bounce_animation(
|
||||
source_animation=ball,
|
||||
bounce_speed=150,
|
||||
gravity=80
|
||||
)
|
||||
|
||||
# Elastic bounce without gravity
|
||||
animation elastic_bounce = bounce_animation(
|
||||
source_animation=ball,
|
||||
bounce_speed=120,
|
||||
damping=240
|
||||
)
|
||||
```
|
||||
|
||||
**Factories**: `animation.bounce_animation(engine)`, `animation.bounce_basic(engine)`, `animation.bounce_gravity(engine)`, `animation.bounce_constrained(engine)`
|
||||
|
||||
### FireAnimation
|
||||
|
||||
@ -588,50 +470,7 @@ Creates smooth color gradients that can be linear or radial. Inherits from `Anim
|
||||
|
||||
**Factories**: `animation.gradient_animation(engine)`, `animation.gradient_rainbow_linear(engine)`, `animation.gradient_rainbow_radial(engine)`, `animation.gradient_two_color_linear(engine)`
|
||||
|
||||
### JitterAnimation
|
||||
|
||||
Adds random shake effects to patterns with configurable intensity, frequency, and jitter types. Inherits from `Animation`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `source_animation` | instance | nil | - | Animation to apply jitter to |
|
||||
| `jitter_intensity` | int | 100 | 0-255 | Overall jitter intensity |
|
||||
| `jitter_frequency` | int | 60 | 0-255 | Jitter frequency in Hz (0-30 Hz) |
|
||||
| `jitter_type` | int | 0 | 0-3 | 0=position, 1=color, 2=brightness, 3=all |
|
||||
| `position_range` | int | 50 | 0-255 | Position jitter range in pixels |
|
||||
| `color_range` | int | 30 | 0-255 | Color jitter range |
|
||||
| `brightness_range` | int | 40 | 0-255 | Brightness jitter range |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
|
||||
#### Jitter Types
|
||||
|
||||
- **0 - Position**: Random position shifts
|
||||
- **1 - Color**: Random color variations
|
||||
- **2 - Brightness**: Random brightness changes
|
||||
- **3 - All**: Combination of all jitter types
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# Digital glitch effect
|
||||
animation base_pattern = gradient_animation(color=rainbow_cycle)
|
||||
animation glitch_effect = jitter_animation(
|
||||
source_animation=base_pattern,
|
||||
jitter_intensity=200,
|
||||
jitter_frequency=120,
|
||||
jitter_type=3
|
||||
)
|
||||
|
||||
# Subtle shake effect
|
||||
animation subtle_shake = jitter_animation(
|
||||
source_animation=base_pattern,
|
||||
jitter_intensity=60,
|
||||
jitter_frequency=40,
|
||||
jitter_type=0
|
||||
)
|
||||
```
|
||||
|
||||
**Factories**: `animation.jitter_animation(engine)`, `animation.jitter_position(engine)`, `animation.jitter_color(engine)`, `animation.jitter_brightness(engine)`, `animation.jitter_all(engine)`
|
||||
|
||||
### NoiseAnimation
|
||||
|
||||
@ -701,78 +540,7 @@ animation cloud_pattern = noise_animation(
|
||||
- **Cloud Simulation**: White/gray colors with large-scale patterns
|
||||
- **Abstract Art**: Rainbow colors with high detail and multiple octaves
|
||||
|
||||
### PlasmaAnimation
|
||||
|
||||
Creates classic plasma effects using sine wave interference patterns. Generates smooth, flowing patterns reminiscent of 1990s demoscene effects and natural phenomena like aurora. Inherits from `Animation`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `color` | instance | nil | - | Color provider for plasma mapping (nil = rainbow) |
|
||||
| `freq_x` | int | 32 | 1-255 | Primary wave frequency |
|
||||
| `freq_y` | int | 23 | 1-255 | Secondary wave frequency |
|
||||
| `phase_x` | int | 0 | 0-255 | Primary wave phase shift |
|
||||
| `phase_y` | int | 64 | 0-255 | Secondary wave phase shift |
|
||||
| `time_speed` | int | 50 | 0-255 | Animation speed (0 = static pattern) |
|
||||
| `blend_mode` | int | 0 | 0-2 | 0=add, 1=multiply, 2=average |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
|
||||
#### Wave Interference
|
||||
|
||||
The plasma effect combines two sine waves with different frequencies to create interference patterns. The interaction between these waves produces complex, organic-looking effects.
|
||||
|
||||
#### Frequency Effects
|
||||
|
||||
- **Low frequencies (10-30)**: Large, flowing waves
|
||||
- **Medium frequencies (30-80)**: Balanced wave patterns
|
||||
- **High frequencies (100-200)**: Fine, detailed interference
|
||||
|
||||
#### Blend Modes
|
||||
|
||||
- **Add (0)**: Bright, energetic patterns with high contrast
|
||||
- **Multiply (1)**: Darker patterns with rich color depth
|
||||
- **Average (2)**: Balanced patterns with smooth transitions
|
||||
|
||||
#### Phase Relationships
|
||||
|
||||
- **In-phase (phase_y = 0)**: Aligned waves create regular patterns
|
||||
- **Quarter-phase (phase_y = 64)**: Creates diagonal flow effects
|
||||
- **Opposite-phase (phase_y = 128)**: Creates standing wave patterns
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# Classic rainbow plasma
|
||||
animation rainbow_plasma = plasma_animation(
|
||||
freq_x=32,
|
||||
freq_y=23,
|
||||
time_speed=60,
|
||||
blend_mode=0
|
||||
)
|
||||
|
||||
# High-frequency intense plasma
|
||||
animation intense_plasma = plasma_animation(
|
||||
color=purple,
|
||||
freq_x=100,
|
||||
freq_y=80,
|
||||
time_speed=120,
|
||||
blend_mode=1
|
||||
)
|
||||
|
||||
# Static plasma pattern for backgrounds
|
||||
animation static_plasma = plasma_animation(
|
||||
color=blue,
|
||||
freq_x=40,
|
||||
freq_y=30,
|
||||
time_speed=0
|
||||
)
|
||||
```
|
||||
|
||||
#### Common Use Cases
|
||||
|
||||
- **Ambient Lighting**: Slow, smooth plasma for relaxing environments
|
||||
- **Party Effects**: Fast, rainbow plasma for energetic atmospheres
|
||||
- **Retro Gaming**: Classic plasma effects for nostalgic themes
|
||||
- **Abstract Art**: Complex frequency combinations for artistic displays
|
||||
|
||||
### PulseAnimation
|
||||
|
||||
@ -1133,90 +901,7 @@ animation strobe = wave_animation(
|
||||
- **Color Cycling**: Rainbow waves for spectrum effects
|
||||
- **Pulse Patterns**: Triangle waves for rhythmic pulses
|
||||
|
||||
### ShiftAnimation
|
||||
|
||||
Creates scrolling and translation effects by moving patterns horizontally across the LED strip. Inherits from `Animation`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `source_animation` | instance | nil | - | Animation to shift/scroll |
|
||||
| `shift_speed` | int | 128 | 0-255 | Scrolling speed (0=static, 255=fastest) |
|
||||
| `direction` | int | 1 | -1 to 1 | Scroll direction (1=right, -1=left) |
|
||||
| `wrap_around` | bool | true | - | Whether to wrap around the strip edges |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
|
||||
#### Movement Behavior
|
||||
|
||||
- **Horizontal Scrolling**: Moves patterns left or right across the strip
|
||||
- **Wrap-Around**: Patterns can wrap around strip edges or disappear
|
||||
- **Variable Speed**: Configurable scrolling speed from static to very fast
|
||||
- **Direction Control**: Forward and reverse scrolling
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# Scrolling text effect
|
||||
animation text_pattern = solid(color=white)
|
||||
animation scrolling_text = shift_animation(
|
||||
source_animation=text_pattern,
|
||||
shift_speed=100,
|
||||
direction=1,
|
||||
wrap_around=true
|
||||
)
|
||||
|
||||
# Moving rainbow pattern
|
||||
animation rainbow_base = gradient_animation(color=rainbow_cycle)
|
||||
animation moving_rainbow = shift_animation(
|
||||
source_animation=rainbow_base,
|
||||
shift_speed=80,
|
||||
direction=-1
|
||||
)
|
||||
```
|
||||
|
||||
**Factories**: `animation.shift_animation(engine)`, `animation.shift_scroll_right(engine)`, `animation.shift_scroll_left(engine)`, `animation.shift_fast_scroll(engine)`
|
||||
|
||||
### ScaleAnimation
|
||||
|
||||
Creates size transformation effects with multiple animation modes including static scaling, oscillation, growing, and shrinking. Inherits from `Animation`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `source_animation` | instance | nil | - | Animation to scale |
|
||||
| `scale_factor` | int | 128 | 1-255 | Scale factor (128=1.0x, 64=0.5x, 255=2.0x) |
|
||||
| `scale_speed` | int | 0 | 0-255 | Animation speed for dynamic modes |
|
||||
| `scale_mode` | int | 0 | 0-3 | 0=static, 1=oscillate, 2=grow, 3=shrink |
|
||||
| `scale_center` | int | 128 | 0-255 | Center point for scaling (128=center) |
|
||||
| `interpolation` | int | 1 | 0-1 | 0=nearest neighbor, 1=linear interpolation |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
|
||||
#### Scale Modes
|
||||
|
||||
- **0 - Static**: Fixed scale factor
|
||||
- **1 - Oscillate**: Oscillates between 0.5x and 2.0x (breathing effect)
|
||||
- **2 - Grow**: Grows from 0.5x to 2.0x
|
||||
- **3 - Shrink**: Shrinks from 2.0x to 0.5x
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# Breathing effect with oscillating scale
|
||||
animation base_pattern = gradient_animation(color=rainbow_cycle)
|
||||
animation breathing_effect = scale_animation(
|
||||
source_animation=base_pattern,
|
||||
scale_mode=1,
|
||||
scale_speed=60
|
||||
)
|
||||
|
||||
# Static zoom effect
|
||||
animation zoomed_sparkles = scale_animation(
|
||||
source_animation=sparkle_animation(color=white),
|
||||
scale_factor=180,
|
||||
scale_mode=0
|
||||
)
|
||||
# Scale factor 180 = 1.4x zoom
|
||||
```
|
||||
|
||||
**Factories**: `animation.scale_animation(engine)`, `animation.scale_static(engine)`, `animation.scale_oscillate(engine)`, `animation.scale_grow(engine)`
|
||||
|
||||
### PalettePatternAnimation
|
||||
|
||||
@ -1305,34 +990,31 @@ Motion effects can be chained to create sophisticated transformations:
|
||||
# Base animation
|
||||
animation base_pulse = pulsating_animation(color=blue, period=3s)
|
||||
|
||||
# Chain multiple transformations
|
||||
animation scaled_pulse = scale_animation(
|
||||
source_animation=base_pulse,
|
||||
scale_factor=150
|
||||
# Simple animation composition
|
||||
animation fire_effect = fire_animation(
|
||||
color=fire_colors,
|
||||
intensity=180,
|
||||
flicker_speed=8
|
||||
)
|
||||
|
||||
animation scrolling_scaled = shift_animation(
|
||||
source_animation=scaled_pulse,
|
||||
shift_speed=60,
|
||||
direction=-1
|
||||
animation gradient_wave = gradient_animation(
|
||||
color=rainbow_cycle,
|
||||
gradient_type=0,
|
||||
movement_speed=50
|
||||
)
|
||||
|
||||
animation final_effect = jitter_animation(
|
||||
source_animation=scrolling_scaled,
|
||||
jitter_intensity=40,
|
||||
jitter_type=1
|
||||
)
|
||||
|
||||
# Result: A scaled, scrolling, color-jittered pulse
|
||||
run final_effect
|
||||
# Result: Multiple independent animations
|
||||
run base_pulse
|
||||
run fire_effect
|
||||
run gradient_wave
|
||||
```
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Each motion effect uses approximately 4 bytes per pixel for color storage
|
||||
- Bounce animation includes additional physics calculations
|
||||
- Scale animation requires interpolation calculations
|
||||
- Limit jitter frequency for better performance
|
||||
- Each animation uses approximately 4 bytes per pixel for color storage
|
||||
- Fire animation includes additional flicker calculations
|
||||
- Gradient animation requires color interpolation calculations
|
||||
- Noise animation includes pseudo-random pattern generation
|
||||
- Consider strip length impact on transformation calculations
|
||||
|
||||
## Parameter Constraints
|
||||
|
||||
@ -58,6 +58,7 @@ The following keywords are reserved and cannot be used as identifiers:
|
||||
- `strip` - Strip configuration (temporarily disabled, reserved keyword)
|
||||
- `set` - Variable assignment
|
||||
- `import` - Import Berry modules
|
||||
- `berry` - Embed arbitrary Berry code
|
||||
|
||||
**Definition Keywords:**
|
||||
- `color` - Color definition
|
||||
@ -237,7 +238,7 @@ import string # Import utility modules
|
||||
import user_functions
|
||||
|
||||
animation dynamic = solid(color=blue)
|
||||
dynamic.opacity = user.my_custom_function()
|
||||
dynamic.opacity = my_custom_function()
|
||||
|
||||
# Import custom animation libraries
|
||||
import fire_effects
|
||||
@ -254,6 +255,46 @@ import user_functions
|
||||
import "user_functions"
|
||||
```
|
||||
|
||||
### Berry Code Blocks
|
||||
|
||||
The `berry` keyword allows embedding arbitrary Berry code within DSL files using triple-quoted strings:
|
||||
|
||||
```berry
|
||||
berry """
|
||||
import math
|
||||
var custom_value = math.pi * 2
|
||||
print("Custom calculation:", custom_value)
|
||||
"""
|
||||
|
||||
berry '''
|
||||
# Alternative syntax with single quotes
|
||||
def helper_function(x)
|
||||
return x * 1.5
|
||||
end
|
||||
'''
|
||||
```
|
||||
|
||||
**Berry Code Block Features:**
|
||||
- Code is copied verbatim to the generated Berry code
|
||||
- Supports both `"""` and `'''` triple-quote syntax
|
||||
- Can span multiple lines and include complex Berry syntax
|
||||
- Variables and functions defined in one block are available in subsequent blocks
|
||||
- Can interact with DSL-generated objects (e.g., `animation_name_.property = value`)
|
||||
|
||||
**Example with DSL Integration:**
|
||||
```berry
|
||||
animation pulse = pulsating_animation(color=red, period=2s)
|
||||
|
||||
berry """
|
||||
# Modify animation using Berry code
|
||||
pulse_.opacity = 200
|
||||
pulse_.priority = 10
|
||||
print("Animation configured")
|
||||
"""
|
||||
|
||||
run pulse
|
||||
```
|
||||
|
||||
## Color Definitions
|
||||
|
||||
The `color` keyword defines static colors or color providers:
|
||||
@ -379,7 +420,7 @@ animation.register_user_function("custom_palette", create_custom_palette)
|
||||
```berry
|
||||
# Use in DSL
|
||||
animation dynamic_anim = rich_palette(
|
||||
palette=user.custom_palette(0xFF0000, 200)
|
||||
palette=custom_palette(0xFF0000, 200)
|
||||
cycle_period=3s
|
||||
)
|
||||
```
|
||||
@ -446,10 +487,15 @@ pulse_red.opacity = opacity_mask # Dynamic opacity from animation
|
||||
|
||||
The DSL supports computed values using arithmetic expressions with value providers and mathematical functions:
|
||||
|
||||
### Safe Patterns
|
||||
|
||||
```berry
|
||||
# Get strip dimensions
|
||||
# ✅ RECOMMENDED: Single value provider assignment
|
||||
set strip_len = strip_length()
|
||||
|
||||
# ✅ RECOMMENDED: Computation with existing values
|
||||
set strip_len2 = (strip_len + 1) / 2
|
||||
|
||||
# Use computed values in animation parameters
|
||||
animation stream1 = comet_animation(
|
||||
color=red
|
||||
@ -457,7 +503,43 @@ animation stream1 = comet_animation(
|
||||
speed=1.5
|
||||
priority=10
|
||||
)
|
||||
```
|
||||
|
||||
### ⚠️ Dangerous Patterns (Prevented by Transpiler)
|
||||
|
||||
The transpiler prevents dangerous patterns that would create new value provider instances at each evaluation:
|
||||
|
||||
```berry
|
||||
# ❌ DANGEROUS: Function creation in computed expression
|
||||
# This would create a new strip_length() instance at each evaluation
|
||||
set strip_len3 = (strip_length() + 1) / 2
|
||||
|
||||
# ❌ ERROR: Transpiler will reject this with:
|
||||
# "Function 'strip_length()' cannot be used in computed expressions.
|
||||
# This creates a new instance at each evaluation."
|
||||
```
|
||||
|
||||
**Why This Is Dangerous:**
|
||||
- Creates a new function instance every time the expression is evaluated
|
||||
- Causes memory leaks and performance degradation
|
||||
- Each new instance has its own timing and state, leading to inconsistent behavior
|
||||
|
||||
**Safe Alternative:**
|
||||
```berry
|
||||
# ✅ CORRECT: Separate the value provider creation from computation
|
||||
set strip_len = strip_length() # Single value provider
|
||||
set strip_len3 = (strip_len + 1) / 2 # Computation with existing value
|
||||
```
|
||||
|
||||
**Functions That Are Restricted in Computed Expressions:**
|
||||
- Any function that creates instances (value providers, animations, etc.) when called
|
||||
- Examples: `strip_length()`, `triangle()`, `smooth()`, `solid()`, etc.
|
||||
|
||||
**Note:** These functions are allowed in `set` statements as they create the instance once, but they cannot be used inside arithmetic expressions that get wrapped in closures, as this would create new instances at each evaluation.
|
||||
|
||||
### Advanced Computed Values
|
||||
|
||||
```berry
|
||||
# Complex expressions with multiple operations
|
||||
set base_speed = 2.0
|
||||
animation stream2 = comet_animation(
|
||||
@ -541,37 +623,37 @@ test.opacity = min(255, max(50, scale(sqrt(strip_len), 0, 16, 100, 255)))
|
||||
When the DSL detects arithmetic expressions containing value providers, variable references, or mathematical functions, it automatically creates closure functions that capture the computation. These closures are called with `(self, param_name, time_ms)` parameters, allowing the computation to be re-evaluated dynamically as needed. Mathematical functions are automatically prefixed with `animation._math.` in the closure context to access the ClosureValueProvider's mathematical methods.
|
||||
|
||||
**User Functions in Computed Parameters:**
|
||||
User-defined functions can also be used in computed parameter expressions, providing powerful custom effects. User functions must be called with the `user.` prefix:
|
||||
User-defined functions can also be used in computed parameter expressions, providing powerful custom effects:
|
||||
|
||||
```berry
|
||||
# Simple user function in computed parameter
|
||||
animation base = solid(color=blue)
|
||||
base.opacity = user.rand_demo()
|
||||
base.opacity = rand_demo()
|
||||
|
||||
# User functions mixed with math operations
|
||||
animation dynamic = solid(
|
||||
color=purple
|
||||
opacity=max(50, min(255, user.rand_demo() + 100))
|
||||
opacity=max(50, min(255, rand_demo() + 100))
|
||||
)
|
||||
```
|
||||
|
||||
### User Functions
|
||||
|
||||
User functions are custom Berry functions that can be called from computed parameters. They provide dynamic values that change over time. User functions must be called with the `user.` prefix.
|
||||
User functions are custom Berry functions that can be called from computed parameters. They provide dynamic values that change over time.
|
||||
|
||||
**Available User Functions:**
|
||||
- `user.rand_demo()` - Returns random values for demonstration purposes
|
||||
- `rand_demo()` - Returns random values for demonstration purposes
|
||||
|
||||
**Usage in Computed Parameters:**
|
||||
```berry
|
||||
# Simple user function
|
||||
animation.opacity = user.rand_demo()
|
||||
animation.opacity = rand_demo()
|
||||
|
||||
# User function with math operations
|
||||
animation.opacity = max(100, user.rand_demo())
|
||||
animation.opacity = max(100, rand_demo())
|
||||
|
||||
# User function in arithmetic expressions
|
||||
animation.opacity = abs(user.rand_demo() - 128) + 64
|
||||
animation.opacity = abs(rand_demo() - 128) + 64
|
||||
```
|
||||
|
||||
**Available User Functions:**
|
||||
@ -579,7 +661,7 @@ The following user functions are available by default (see [User Functions Guide
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|------------|-------------|
|
||||
| `user.rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
|
||||
**User Function Behavior:**
|
||||
- User functions are automatically detected by the transpiler
|
||||
@ -825,6 +907,8 @@ sequence clean_transitions {
|
||||
|
||||
Templates provide a powerful way to create reusable, parameterized animation patterns. They allow you to define animation blueprints that can be instantiated with different parameters, promoting code reuse and maintainability.
|
||||
|
||||
**Template-Only Files**: DSL files containing only template definitions transpile to pure Berry functions without engine initialization or execution code. This allows templates to be used as reusable function libraries.
|
||||
|
||||
### Template Definition
|
||||
|
||||
Templates are defined using the `template` keyword followed by a parameter block and body:
|
||||
@ -971,6 +1055,9 @@ end
|
||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
||||
```
|
||||
|
||||
**Template-Only Transpilation:**
|
||||
Files containing only templates generate pure Berry function definitions without `var engine = animation.init_strip()` or `engine.run()` calls, making them suitable as reusable function libraries.
|
||||
|
||||
**Parameter Handling:**
|
||||
- Parameters get `_` suffix in generated code to avoid naming conflicts
|
||||
- Templates receive `engine` as the first parameter automatically
|
||||
@ -1194,13 +1281,7 @@ Animation classes create visual effects on LED strips:
|
||||
| `twinkle_animation` | Twinkling stars effect |
|
||||
| `gradient_animation` | Color gradient effects |
|
||||
| `noise_animation` | Perlin noise-based patterns |
|
||||
| `plasma_animation` | Plasma wave effects |
|
||||
| `sparkle_animation` | Sparkling/glitter effects |
|
||||
| `wave_animation` | Wave propagation effects |
|
||||
| `shift_animation` | Shifting/scrolling patterns |
|
||||
| `bounce_animation` | Bouncing ball effects |
|
||||
| `scale_animation` | Scaling/zooming effects |
|
||||
| `jitter_animation` | Random jitter/shake effects |
|
||||
| `rich_palette_animation` | Palette-based color cycling |
|
||||
| `palette_wave_animation` | Wave patterns using palettes |
|
||||
| `palette_gradient_animation` | Gradient patterns using palettes |
|
||||
|
||||
@ -104,7 +104,7 @@ The Animation DSL uses a declarative syntax with named parameters. All animation
|
||||
- **Named colors**: `red`, `blue`, `white`, etc.
|
||||
- **Comments**: `# This is a comment`
|
||||
- **Property assignment**: `animation.property = value`
|
||||
- **User functions**: `user.function_name()` for custom functions
|
||||
- **User functions**: `function_name()` for custom functions
|
||||
|
||||
### Basic Structure
|
||||
|
||||
@ -125,7 +125,7 @@ animation comet_blue = comet_animation(color=blue, tail_length=10, speed=1500)
|
||||
|
||||
# Property assignments with user functions
|
||||
pulse_red.priority = 10
|
||||
pulse_red.opacity = user.breathing_effect()
|
||||
pulse_red.opacity = breathing_effect()
|
||||
comet_blue.direction = -1
|
||||
|
||||
# Execution
|
||||
@ -242,7 +242,7 @@ animation.register_user_function("rand_demo", rand_demo)
|
||||
import user_functions
|
||||
|
||||
animation test = solid(color=blue)
|
||||
test.opacity = user.rand_demo()
|
||||
test.opacity = rand_demo()
|
||||
run test
|
||||
```
|
||||
|
||||
@ -260,12 +260,69 @@ engine.add(test_)
|
||||
engine.run()
|
||||
```
|
||||
|
||||
## Berry Code Block Transpilation
|
||||
|
||||
The DSL supports embedding arbitrary Berry code using the `berry` keyword with triple-quoted strings. This provides an escape hatch for complex logic while maintaining the declarative nature of the DSL.
|
||||
|
||||
### Berry Code Block Syntax
|
||||
|
||||
```berry
|
||||
# DSL Berry Code Block
|
||||
berry """
|
||||
import math
|
||||
var custom_value = math.pi * 2
|
||||
print("Custom calculation:", custom_value)
|
||||
"""
|
||||
```
|
||||
|
||||
### Transpilation Behavior
|
||||
|
||||
Berry code blocks are copied verbatim to the generated Berry code with comment markers:
|
||||
|
||||
```berry
|
||||
# DSL Code
|
||||
berry """
|
||||
var test_var = 42
|
||||
print("Hello from berry block")
|
||||
"""
|
||||
|
||||
# Transpiles to Berry Code
|
||||
# Berry code block
|
||||
var test_var = 42
|
||||
print("Hello from berry block")
|
||||
# End berry code block
|
||||
```
|
||||
|
||||
### Integration with DSL Objects
|
||||
|
||||
Berry code can interact with DSL-generated objects by using the underscore suffix naming convention:
|
||||
|
||||
```berry
|
||||
# DSL Code
|
||||
animation pulse = pulsating_animation(color=red, period=2s)
|
||||
berry """
|
||||
pulse_.opacity = 200
|
||||
pulse_.priority = 10
|
||||
"""
|
||||
|
||||
# Transpiles to Berry Code
|
||||
var pulse_ = animation.pulsating_animation(engine)
|
||||
pulse_.color = animation.red
|
||||
pulse_.period = 2000
|
||||
# Berry code block
|
||||
pulse_.opacity = 200
|
||||
pulse_.priority = 10
|
||||
# End berry code block
|
||||
```
|
||||
|
||||
## Advanced DSL Features
|
||||
|
||||
### Templates
|
||||
|
||||
Templates provide a DSL-native way to create reusable animation patterns with parameters. Templates are transpiled into Berry functions and automatically registered for use.
|
||||
|
||||
**Template-Only Files**: DSL files containing only template definitions generate pure Berry function code without engine initialization or execution, creating reusable function libraries.
|
||||
|
||||
#### Template Definition Transpilation
|
||||
|
||||
```berry
|
||||
@ -361,6 +418,7 @@ animation.register_user_function("comet_chase", comet_chase)
|
||||
- Automatically registered
|
||||
- Type annotations supported
|
||||
- Transpiled to Berry functions
|
||||
- Template-only files generate pure function libraries
|
||||
|
||||
**User Functions** (Berry-native):
|
||||
- Defined in Berry code
|
||||
@ -375,23 +433,24 @@ Register custom Berry functions for use in DSL. User functions must take `engine
|
||||
|
||||
```berry
|
||||
# Define custom function in Berry - engine must be first parameter
|
||||
def custom_sparkle(engine, color, density, speed)
|
||||
def custom_twinkle(engine, color, count, period)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = color
|
||||
anim.density = density
|
||||
anim.speed = speed
|
||||
anim.count = count
|
||||
atml:parameter>
|
||||
</invoke>
|
||||
return anim
|
||||
end
|
||||
|
||||
# Register the function for DSL use
|
||||
animation.register_user_function("sparkle", custom_sparkle)
|
||||
animation.register_user_function("twinkle", custom_twinkle)
|
||||
```
|
||||
|
||||
```berry
|
||||
# Use in DSL - engine is automatically passed as first argument
|
||||
animation gold_sparkle = sparkle(#FFD700, 8, 500ms)
|
||||
animation blue_sparkle = sparkle(blue, 12, 300ms)
|
||||
run gold_sparkle
|
||||
animation gold_twinkle = twinkle(#FFD700, 8, 500ms)
|
||||
animation blue_twinkle = twinkle(blue, 12, 300ms)
|
||||
run gold_twinkle
|
||||
```
|
||||
|
||||
**Important**: The DSL transpiler automatically passes `engine` as the first argument to all user functions. Your function signature must include `engine` as the first parameter, but DSL users don't need to provide it when calling the function.
|
||||
@ -433,16 +492,13 @@ DSL supports nested function calls for complex compositions:
|
||||
```berry
|
||||
# Nested calls in animation definitions (now supported)
|
||||
animation complex = pulsating_animation(
|
||||
source=shift_animation(
|
||||
source=solid(color=red),
|
||||
offset=triangle(min=0, max=29, period=3s)
|
||||
),
|
||||
color=red,
|
||||
period=2s
|
||||
)
|
||||
|
||||
# Nested calls in run statements
|
||||
sequence demo {
|
||||
play pulsating_animation(source=shift_animation(source=solid(color=blue), offset=5), period=1s) for 10s
|
||||
play pulsating_animation(color=blue, period=1s) for 10s
|
||||
}
|
||||
```
|
||||
|
||||
@ -521,6 +577,29 @@ sequence demo {
|
||||
# Transpiler error: "Undefined reference 'nonexistent_animation' in sequence play"
|
||||
```
|
||||
|
||||
**Function Call Safety Validation:**
|
||||
```berry
|
||||
# Error: Dangerous function creation in computed expression
|
||||
set strip_len3 = (strip_length() + 1) / 2
|
||||
# Transpiler error: "Function 'strip_length()' cannot be used in computed expressions.
|
||||
# This creates a new instance at each evaluation. Use either:
|
||||
# set var_name = strip_length() # Single function call
|
||||
# set computed = (existing_var + 1) / 2 # Computation with existing values"
|
||||
```
|
||||
|
||||
**Why This Validation Exists:**
|
||||
The transpiler prevents dangerous patterns where functions that create instances are called inside computed expressions that get wrapped in closures. This would create a new instance every time the closure is evaluated, leading to:
|
||||
- Memory leaks
|
||||
- Performance degradation
|
||||
- Inconsistent behavior due to multiple timing states
|
||||
|
||||
**Safe Alternative:**
|
||||
```berry
|
||||
# ✅ CORRECT: Separate function call from computation
|
||||
set strip_len = strip_length() # Single function call
|
||||
set strip_len3 = (strip_len + 1) / 2 # Computation with existing value
|
||||
```
|
||||
|
||||
### Error Categories
|
||||
|
||||
- **Syntax errors**: Invalid DSL syntax (lexer/parser errors)
|
||||
@ -529,6 +608,7 @@ sequence demo {
|
||||
- **Constraint validation**: Parameter values that violate defined constraints (min/max, enums, types)
|
||||
- **Reference validation**: Using undefined colors, animations, or variables
|
||||
- **Type validation**: Incorrect parameter types or incompatible assignments
|
||||
- **Safety validation**: Dangerous patterns that could cause memory leaks or performance issues
|
||||
- **Runtime errors**: Errors during Berry code execution (rare with good validation)
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
@ -123,11 +123,11 @@ animation comet = comet_animation(
|
||||
speed=2000
|
||||
)
|
||||
|
||||
# Sparkle effect
|
||||
animation sparkles = sparkle_animation(
|
||||
# Twinkling effect
|
||||
animation sparkles = twinkle_animation(
|
||||
color=white
|
||||
density=80
|
||||
fade_speed=60
|
||||
count=8
|
||||
period=800ms
|
||||
)
|
||||
|
||||
run breathing
|
||||
@ -241,22 +241,22 @@ For complex logic, create custom functions in Berry:
|
||||
|
||||
```berry
|
||||
# Define custom function - engine must be first parameter
|
||||
def my_sparkle(engine, color, density, speed)
|
||||
def my_twinkle(engine, color, count, period)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = color
|
||||
anim.density = density
|
||||
anim.speed = speed
|
||||
anim.count = count
|
||||
anim.period = period
|
||||
return anim
|
||||
end
|
||||
|
||||
# Register for DSL use
|
||||
animation.register_user_function("sparkle", my_sparkle)
|
||||
animation.register_user_function("twinkle", my_twinkle)
|
||||
```
|
||||
|
||||
```berry
|
||||
# Use in DSL - engine is automatically passed
|
||||
animation gold_sparkles = sparkle(0xFFD700, 8, 500ms)
|
||||
run gold_sparkles
|
||||
animation gold_twinkles = twinkle(0xFFD700, 8, 500ms)
|
||||
run gold_twinkles
|
||||
```
|
||||
|
||||
**Note**: The DSL automatically passes `engine` as the first argument to user functions.
|
||||
|
||||
@ -56,6 +56,8 @@ transpile()
|
||||
│ │ ├── process_restart_statement_fluent()
|
||||
│ │ └── process_sequence_assignment_fluent()
|
||||
│ ├── process_import() (direct Berry import generation)
|
||||
│ ├── process_event_handler() (basic event system support)
|
||||
│ ├── process_berry_code_block() (embed arbitrary Berry code)
|
||||
│ ├── process_run() (collect for single engine.run())
|
||||
│ └── process_property_assignment()
|
||||
└── generate_engine_start() (single call for all run statements)
|
||||
@ -217,24 +219,210 @@ is_anonymous_function(expr_str)
|
||||
└── Skip closure wrapping for already-wrapped functions
|
||||
```
|
||||
|
||||
## Enhanced Symbol Table System
|
||||
|
||||
The transpiler uses a sophisticated **SymbolTable** system for holistic symbol management and caching. This system provides dynamic symbol detection, type validation, and conflict prevention.
|
||||
|
||||
### SymbolTable Architecture
|
||||
|
||||
The symbol table consists of two main classes in `symbol_table.be`:
|
||||
|
||||
#### SymbolEntry Class
|
||||
```
|
||||
SymbolEntry
|
||||
├── name: string # Symbol name
|
||||
├── type: string # Symbol type classification
|
||||
├── instance: object # Actual instance for validation
|
||||
├── takes_args: boolean # Whether symbol accepts arguments
|
||||
├── arg_type: string # "positional", "named", or "none"
|
||||
└── is_builtin: boolean # Whether this is a built-in symbol from animation module
|
||||
```
|
||||
|
||||
**Symbol Types Supported:**
|
||||
- `"palette"` - Palette objects like `PALETTE_RAINBOW` (bytes instances)
|
||||
- `"constant"` - Integer constants like `LINEAR`, `SINE`, `COSINE`
|
||||
- `"math_function"` - Mathematical functions like `max`, `min`
|
||||
- `"user_function"` - User-defined functions registered at runtime
|
||||
- `"value_provider"` - Value provider constructors
|
||||
- `"animation"` - Animation constructors
|
||||
- `"color"` - Color definitions and providers
|
||||
- `"variable"` - User-defined variables
|
||||
- `"sequence"` - Sequence definitions
|
||||
- `"template"` - Template definitions
|
||||
|
||||
#### SymbolTable Class
|
||||
```
|
||||
SymbolTable
|
||||
├── entries: map # Map of name -> SymbolEntry
|
||||
├── mock_engine: MockEngine # For validation testing
|
||||
├── Dynamic Detection Methods:
|
||||
│ ├── _detect_and_cache_symbol() # On-demand symbol detection
|
||||
│ ├── contains() # Existence check with auto-detection
|
||||
│ └── get() # Retrieval with auto-detection
|
||||
├── Creation Methods:
|
||||
│ ├── create_palette()
|
||||
│ ├── create_color()
|
||||
│ ├── create_animation()
|
||||
│ ├── create_value_provider()
|
||||
│ ├── create_variable()
|
||||
│ ├── create_sequence()
|
||||
│ └── create_template()
|
||||
└── Validation Methods:
|
||||
├── symbol_exists()
|
||||
├── get_reference()
|
||||
└── takes_args() / takes_positional_args() / takes_named_args()
|
||||
```
|
||||
|
||||
### Dynamic Symbol Detection
|
||||
|
||||
The SymbolTable uses **lazy detection** to identify and cache symbols as they are encountered:
|
||||
|
||||
```
|
||||
_detect_and_cache_symbol(name)
|
||||
├── Check if already cached → return cached entry
|
||||
├── Check animation module using introspection:
|
||||
│ ├── Detect bytes() instances → create_palette()
|
||||
│ ├── Detect integer constants (type == "int") → create_constant()
|
||||
│ ├── Detect math functions in animation._math → create_math_function()
|
||||
│ ├── Detect user functions via animation.is_user_function() → create_user_function()
|
||||
│ ├── Test constructors with MockEngine:
|
||||
│ │ ├── Create instance with mock_engine
|
||||
│ │ ├── Check isinstance(instance, animation.value_provider) → create_value_provider()
|
||||
│ │ └── Check isinstance(instance, animation.animation) → create_animation()
|
||||
│ └── Cache result for future lookups
|
||||
└── Return nil if not found (handled as user-defined)
|
||||
```
|
||||
|
||||
### Symbol Type Detection Examples
|
||||
|
||||
**Palette Detection:**
|
||||
```berry
|
||||
# DSL: animation rainbow = rich_palette_animation(palette=PALETTE_RAINBOW)
|
||||
# Detection: PALETTE_RAINBOW exists in animation module, isinstance(obj, bytes)
|
||||
# Result: SymbolEntry("PALETTE_RAINBOW", "palette", bytes_instance, true)
|
||||
# Reference: "animation.PALETTE_RAINBOW"
|
||||
```
|
||||
|
||||
**Constant Detection:**
|
||||
```berry
|
||||
# DSL: animation wave = wave_animation(waveform=LINEAR)
|
||||
# Detection: LINEAR exists in animation module, type(LINEAR) == "int"
|
||||
# Result: SymbolEntry("LINEAR", "constant", 1, true)
|
||||
# Reference: "animation.LINEAR"
|
||||
```
|
||||
|
||||
**Math Function Detection:**
|
||||
```berry
|
||||
# DSL: animation.opacity = max(100, min(255, brightness))
|
||||
# Detection: max exists in animation._math, is callable
|
||||
# Result: SymbolEntry("max", "math_function", nil, true)
|
||||
# Reference: "animation.max" (transformed to "animation._math.max" in closures)
|
||||
```
|
||||
|
||||
**Value Provider Detection:**
|
||||
```berry
|
||||
# DSL: set oscillator = triangle(min_value=0, max_value=100, period=2s)
|
||||
# Detection: triangle(mock_engine) creates instance, isinstance(instance, animation.value_provider)
|
||||
# Result: SymbolEntry("triangle", "value_provider", instance, true)
|
||||
# Reference: "animation.triangle"
|
||||
```
|
||||
|
||||
**User Function Detection:**
|
||||
```berry
|
||||
# DSL: animation demo = rand_demo(color=red)
|
||||
# Detection: animation.is_user_function("rand_demo") returns true
|
||||
# Result: SymbolEntry("rand_demo", "user_function", nil, true)
|
||||
# Reference: "rand_demo_" (handled specially in function calls)
|
||||
```
|
||||
|
||||
### Symbol Conflict Prevention
|
||||
|
||||
The SymbolTable prevents symbol redefinition conflicts:
|
||||
|
||||
```
|
||||
add(name, entry)
|
||||
├── Check for built-in symbol conflicts:
|
||||
│ ├── _detect_and_cache_symbol(name)
|
||||
│ └── Raise "symbol_redefinition_error" if types differ
|
||||
├── Check existing user-defined symbols:
|
||||
│ ├── Compare entry.type with existing.type
|
||||
│ └── Raise "symbol_redefinition_error" if types differ
|
||||
├── Allow same-type updates (reassignment)
|
||||
└── Return entry for method chaining
|
||||
```
|
||||
|
||||
**Example Conflict Detection:**
|
||||
```berry
|
||||
# This would raise an error:
|
||||
color max = #FF0000 # Conflicts with built-in math function "max"
|
||||
|
||||
# This would also raise an error:
|
||||
color red = #FF0000
|
||||
animation red = solid(color=blue) # Redefining "red" as different type
|
||||
```
|
||||
|
||||
### Integration with Transpiler
|
||||
|
||||
The SymbolTable integrates seamlessly with the transpiler's processing flow:
|
||||
|
||||
### Performance Optimizations
|
||||
|
||||
**Caching Strategy:**
|
||||
- **Lazy Detection**: Symbols detected only when first encountered
|
||||
- **Instance Reuse**: MockEngine instances reused for validation
|
||||
- **Introspection Caching**: Built-in symbol detection cached permanently
|
||||
|
||||
**Memory Efficiency:**
|
||||
- **Minimal Storage**: Only essential information stored per symbol
|
||||
- **Shared MockEngine**: Single MockEngine instance for all validation
|
||||
- **Reference Counting**: Automatic cleanup of unused entries
|
||||
|
||||
### MockEngine Integration
|
||||
|
||||
The SymbolTable uses a lightweight MockEngine for constructor validation:
|
||||
|
||||
```
|
||||
MockEngine
|
||||
├── time_ms: 0 # Mock time for validation
|
||||
├── get_strip_length(): 30 # Default strip length
|
||||
└── Minimal interface for instance creation testing
|
||||
```
|
||||
|
||||
**Usage in Detection:**
|
||||
```berry
|
||||
# Test if function creates value provider
|
||||
try
|
||||
var instance = factory_func(self.mock_engine)
|
||||
if isinstance(instance, animation.value_provider)
|
||||
return SymbolEntry.create_value_provider(name, instance, animation.value_provider)
|
||||
end
|
||||
except .. as e, msg
|
||||
# Constructor failed - not a valid provider
|
||||
end
|
||||
```
|
||||
|
||||
## Validation System (Comprehensive)
|
||||
|
||||
The transpiler includes **extensive compile-time validation** with robust error handling:
|
||||
|
||||
### Factory Function Validation (Enhanced)
|
||||
### Factory Function Validation (Simplified using SymbolTable)
|
||||
```
|
||||
_validate_factory_function(func_name, expected_base_class)
|
||||
├── Check if function exists in animation module using introspection
|
||||
├── Check if it's callable (function or class)
|
||||
├── Create mock instance with MockEngine for type checking
|
||||
├── Validate instance type matches expected base class
|
||||
├── Handle mathematical functions separately (skip validation)
|
||||
└── Graceful error handling with try/catch
|
||||
_validate_animation_factory_exists(func_name)
|
||||
├── Skip validation for mathematical functions
|
||||
├── Use symbol_table.get(func_name) for dynamic detection
|
||||
└── Return true if entry exists (any callable function is valid)
|
||||
|
||||
MockEngine class provides:
|
||||
├── time_ms property for validation
|
||||
├── get_strip_length() method returning default 30
|
||||
└── Minimal interface for instance creation
|
||||
_validate_animation_factory_creates_animation(func_name)
|
||||
├── Use symbol_table.get(func_name) for dynamic detection
|
||||
└── Return true if entry.type == "animation"
|
||||
|
||||
_validate_color_provider_factory_onsts(func_name)
|
||||
├── Use symbol_table.get(func_name) for dynamic detection
|
||||
└── Return true if entry exists (any callable function is valid)
|
||||
|
||||
_validate_value_provider_factory_exists(func_name)
|
||||
├── Use symbol_table.get(func_name) for dynamic detection
|
||||
└── Return true if entry.type == "value_provider"
|
||||
```
|
||||
|
||||
### Parameter Validation (Real-time)
|
||||
@ -246,33 +434,29 @@ _validate_single_parameter(func_name, param_name, animation_instance)
|
||||
├── Validate immediately as parameters are parsed
|
||||
└── Graceful error handling to ensure transpiler robustness
|
||||
|
||||
_create_instance_for_validation(func_name)
|
||||
├── Create MockEngine for validation
|
||||
├── Call factory function with mock engine
|
||||
├── Return instance for parameter validation
|
||||
└── Handle creation failures gracefully
|
||||
_create_instance_for_validation(func_name) - Simplified using SymbolTable
|
||||
├── Use symbol_table.get(func_name) for dynamic detection
|
||||
└── Return entry.instance if available, nil otherwise
|
||||
```
|
||||
|
||||
### Reference Validation (Consolidated)
|
||||
### Reference Validation (Simplified using SymbolTable)
|
||||
```
|
||||
resolve_symbol_reference(name) - Unified symbol resolution
|
||||
├── Check if it's a named color → get_named_color_value()
|
||||
├── Check symbol_table for user-defined objects → name_
|
||||
├── Check sequence_names for sequences → name_
|
||||
├── Check animation module using introspection → animation.name
|
||||
└── Default to user-defined format → name_
|
||||
resolve_symbol_reference(name) - Simplified using SymbolTable
|
||||
└── Use symbol_table.get_reference(name) for all symbol resolution
|
||||
|
||||
validate_symbol_reference(name, context) - With error reporting
|
||||
├── Use symbol_exists() to check all sources
|
||||
├── Use symbol_exists() to check symbol_table
|
||||
├── Report detailed error with context information
|
||||
└── Return validation status
|
||||
|
||||
symbol_exists(name) - Existence check
|
||||
├── Check animation_dsl.is_color_name(name)
|
||||
├── Check symbol_table.contains(name)
|
||||
├── Check sequence_names.contains(name)
|
||||
├── Check introspect.contains(animation, name)
|
||||
└── Return boolean result
|
||||
symbol_exists(name) - Simplified existence check
|
||||
└── Use symbol_table.symbol_exists(name) for unified checking
|
||||
|
||||
_validate_value_provider_reference(object_name, context) - Simplified
|
||||
├── Check symbol_exists() using symbol_table
|
||||
├── Use symbol_table.get(name) for type information
|
||||
├── Check entry.type == "value_provider" || entry.type == "animation"
|
||||
└── Report detailed error messages for invalid types
|
||||
```
|
||||
|
||||
### User Name Validation (Reserved Names)
|
||||
@ -309,13 +493,15 @@ pulse_.color = animation.red
|
||||
pulse_.period = 2000
|
||||
```
|
||||
|
||||
### Symbol Resolution (Consolidated)
|
||||
The transpiler resolves symbols at compile time using **unified resolution logic**:
|
||||
```berry
|
||||
# Built-in symbols from animation module → animation.symbol
|
||||
animation.red, animation.PALETTE_RAINBOW, animation.SINE, animation.COSINE
|
||||
**Template-Only Exception**: Files containing only template definitions skip engine initialization and `engine.run()` generation, producing pure function libraries.
|
||||
|
||||
# User-defined symbols → symbol_
|
||||
### Symbol Resolution (Consolidated)
|
||||
The transpiler resolves symbols at compile time using **unified resolution logic** based on the `is_builtin` flag:
|
||||
```berry
|
||||
# Built-in symbols (is_builtin=true) from animation module → animation.symbol
|
||||
animation.linear, animation.PALETTE_RAINBOW, animation.SINE, animation.solid
|
||||
|
||||
# User-defined symbols (is_builtin=false) → symbol_
|
||||
my_color_, my_animation_, my_sequence_
|
||||
|
||||
# Named colors → direct ARGB values (resolved at compile time)
|
||||
@ -323,7 +509,7 @@ red → 0xFFFF0000, blue → 0xFF0000FF
|
||||
|
||||
# Template calls → template_function(engine, args)
|
||||
my_template(red, 2s) → my_template_template(engine, 0xFFFF0000, 2000)
|
||||
```
|
||||
|
||||
|
||||
### Closure Generation (Enhanced)
|
||||
Dynamic expressions are wrapped in closures with **mathematical function support**:
|
||||
@ -333,7 +519,7 @@ Dynamic expressions are wrapped in closures with **mathematical function support
|
||||
animation.opacity = animation.create_closure_value(engine,
|
||||
def (self) return animation.resolve(strip_length_(engine)) / 2 + 50 end)
|
||||
|
||||
# DSL: animation.opacity = max(100, min(255, user.rand_demo() + 50))
|
||||
# DSL: animation.opacity = max(100, min(255, rand_demo() + 50))
|
||||
# Generated:
|
||||
animation.opacity = animation.create_closure_value(engine,
|
||||
def (self) return animation._math.max(100, animation._math.min(255, animation.get_user_function('rand_demo')(engine) + 50)) end)
|
||||
@ -385,6 +571,8 @@ var demo_ = animation.SequenceManager(engine)
|
||||
|
||||
Templates are transpiled into Berry functions with **comprehensive parameter handling**:
|
||||
|
||||
**Template-Only Optimization**: Files containing only template definitions skip engine initialization and execution code generation, producing pure Berry function libraries.
|
||||
|
||||
```
|
||||
process_template()
|
||||
├── expect_identifier() → template name
|
||||
@ -445,6 +633,7 @@ The transpiler provides **comprehensive error reporting** with graceful degradat
|
||||
- **Reference validation** - Undefined object references with context information
|
||||
- **Constraint validation** - Parameter values outside valid ranges
|
||||
- **Type validation** - Incorrect parameter types with expected types
|
||||
- **Safety validation** - Dangerous patterns that could cause memory leaks or performance issues
|
||||
- **Template errors** - Template definition and call validation
|
||||
- **Reserved name conflicts** - User names conflicting with built-ins
|
||||
|
||||
@ -509,7 +698,7 @@ get_error_report()
|
||||
|
||||
### User Function Integration
|
||||
- **Template registration** as user functions with automatic naming
|
||||
- **User function call detection** with user. prefix handling
|
||||
- **User function call detection** usable as normal functions with positional arguments
|
||||
- **Closure generation** for computed parameters with mathematical functions
|
||||
- **Template call resolution** in multiple contexts (animation, property, standalone)
|
||||
- **Import statement processing** for user function modules
|
||||
@ -535,9 +724,86 @@ The refactored transpiler emphasizes:
|
||||
|
||||
1. **Simplicity** - Ultra-simplified single-pass architecture
|
||||
2. **Robustness** - Comprehensive error handling and graceful degradation
|
||||
3. **Validation** - Extensive compile-time validation with detailed error messages
|
||||
4. **Flexibility** - Support for templates, multiple syntax variants, and user functions
|
||||
5. **Performance** - Efficient processing with minimal memory overhead
|
||||
6. **Maintainability** - Clear separation of concerns and unified processing methods
|
||||
3. **Enhanced Symbol Management** - Dynamic SymbolTable system with intelligent caching and conflict detection
|
||||
4. **Validation** - Extensive compile-time validation with detailed error messages
|
||||
5. **Flexibility** - Support for templates, multiple syntax variants, and user functions
|
||||
6. **Performance** - Efficient processing with minimal memory overhead and lazy symbol detection
|
||||
7. **Maintainability** - Clear separation of concerns and unified processing methods
|
||||
|
||||
This architecture ensures robust, efficient transpilation from DSL to executable Berry code while providing comprehensive validation, detailed error reporting, and extensive language features.
|
||||
## Recent Refactoring Improvements
|
||||
|
||||
### Code Simplification Using SymbolTable
|
||||
|
||||
The transpiler has been significantly refactored to leverage the `symbol_table.be` system more extensively:
|
||||
|
||||
#### **Factory Validation Simplification**
|
||||
- **Before**: Complex validation with introspection and manual instance creation (~50 lines)
|
||||
- **After**: Simple validation using symbol_table's dynamic detection (~25 lines)
|
||||
- **Improvement**: 50% code reduction with better maintainability
|
||||
|
||||
#### **Symbol Resolution Consolidation**
|
||||
- **Before**: Multiple separate checks for sequences, introspection, etc.
|
||||
- **After**: Unified resolution through `symbol_table.get_reference()`
|
||||
- **Improvement**: Single source of truth for all symbol resolution
|
||||
|
||||
#### **Duplicate Code Elimination**
|
||||
- **Before**: Duplicate code patterns in `process_color()` and `process_animation()` methods
|
||||
- **After**: Consolidated into reusable `_process_simple_value_assignment()` helper
|
||||
- **Improvement**: 70% reduction in duplicate code blocks
|
||||
|
||||
#### **Legacy Variable Removal**
|
||||
- **Before**: Separate tracking of sequences in `sequence_names` variable
|
||||
- **After**: All symbols tracked uniformly in `symbol_table`
|
||||
- **Improvement**: Eliminated redundancy and simplified state management
|
||||
|
||||
### Major Enhancements
|
||||
|
||||
**SymbolTable System:**
|
||||
- **Dynamic Detection**: Automatically detects and caches symbol types as encountered
|
||||
- **Conflict Prevention**: Prevents redefinition of symbols with different types
|
||||
- **Performance Optimization**: Lazy loading and efficient symbol resolution for optimal performance
|
||||
- **Type Safety**: Comprehensive type checking with MockEngine validation
|
||||
- **Modular Design**: Separated into `symbol_table.be` for reusability
|
||||
- **Constant Detection**: Added support for integer constants like `LINEAR`, `SINE`, `COSINE`
|
||||
|
||||
**Enhanced Symbol Detection:**
|
||||
- **Palette Objects**: `PALETTE_RAINBOW` → `animation.PALETTE_RAINBOW`
|
||||
- **Integer Constants**: `LINEAR`, `SINE`, `COSINE` → `animation.LINEAR`, `animation.SINE`, `animation.COSINE`
|
||||
- **Math Functions**: `max`, `min` → `animation.max`, `animation.min` (transformed to `animation._math.*` in closures)
|
||||
- **Value Providers**: `triangle`, `smooth` → `animation.triangle`, `animation.smooth`
|
||||
- **Animation Constructors**: `solid`, `pulsating_animation` → `animation.solid`, `animation.pulsating_animation`
|
||||
- **User-defined Symbols**: `my_color`, `my_animation` → `my_color_`, `my_animation_`
|
||||
|
||||
**Validation Improvements:**
|
||||
- **Real-time Validation**: Parameter validation as symbols are parsed
|
||||
- **Instance-based Checking**: Uses actual instances for accurate validation
|
||||
- **Graceful Error Handling**: Robust error recovery with detailed error messages
|
||||
- **Simplified Validation Methods**: Factory validation reduced from ~50 to ~25 lines using symbol_table
|
||||
- **Unified Symbol Checking**: All symbol existence checks go through symbol_table system
|
||||
- **Enhanced Type Detection**: Automatic detection of constants, palettes, functions, and constructors
|
||||
|
||||
This architecture ensures robust, efficient transpilation from DSL to executable Berry code while providing comprehensive validation, detailed error reporting, intelligent symbol management, and extensive language features.
|
||||
|
||||
### Symbol Reference Generation
|
||||
|
||||
The enhanced SymbolEntry system uses the `is_builtin` flag to determine correct reference generation:
|
||||
|
||||
```berry
|
||||
# SymbolEntry.get_reference() method
|
||||
def get_reference()
|
||||
if self.is_builtin
|
||||
return f"animation.{self.name}" # Built-in symbols: animation.LINEAR
|
||||
else
|
||||
return f"{self.name}_" # User-defined symbols: my_color_
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
- **Built-in Constants**: `LINEAR` → `animation.LINEAR`
|
||||
- **Built-in Functions**: `triangle` → `animation.triangle`
|
||||
- **Built-in Palettes**: `PALETTE_RAINBOW` → `animation.PALETTE_RAINBOW`
|
||||
- **User-defined Colors**: `my_red` → `my_red_`
|
||||
- **User-defined Animations**: `pulse_anim` → `pulse_anim_`
|
||||
|
||||
This ensures consistent and correct symbol resolution throughout the transpilation process.
|
||||
@ -30,7 +30,7 @@ animation.register_user_function("breathing", my_breathing)
|
||||
|
||||
### 3. Use It in DSL
|
||||
|
||||
First, import your user functions module, then call your function with the `user.` prefix in computed parameters:
|
||||
First, import your user functions module, then call your function directly in computed parameters:
|
||||
|
||||
```berry
|
||||
# Import your user functions module
|
||||
@ -38,10 +38,10 @@ import user_functions
|
||||
|
||||
# Use your custom function in computed parameters
|
||||
animation calm = solid(color=blue)
|
||||
calm.opacity = user.breathing_effect()
|
||||
calm.opacity = breathing_effect()
|
||||
|
||||
animation energetic = solid(color=red)
|
||||
energetic.opacity = user.breathing_effect()
|
||||
energetic.opacity = breathing_effect()
|
||||
|
||||
sequence demo {
|
||||
play calm for 10s
|
||||
@ -61,9 +61,9 @@ The DSL supports importing Berry modules using the `import` keyword. This is the
|
||||
# Import user functions at the beginning of your DSL file
|
||||
import user_functions
|
||||
|
||||
# Now user functions are available with the user. prefix
|
||||
# Now user functions are available directly
|
||||
animation test = solid(color=blue)
|
||||
test.opacity = user.my_function()
|
||||
test.opacity = my_function()
|
||||
```
|
||||
|
||||
### Import Behavior
|
||||
@ -107,15 +107,15 @@ import user_functions
|
||||
|
||||
# Simple user function call
|
||||
animation random_test = solid(color=red)
|
||||
random_test.opacity = user.rand_demo()
|
||||
random_test.opacity = rand_demo()
|
||||
|
||||
# User function with parameters
|
||||
animation breathing_blue = solid(color=blue)
|
||||
breathing_blue.opacity = user.breathing(128, 64)
|
||||
breathing_blue.opacity = breathing(128, 64)
|
||||
|
||||
# User functions in mathematical expressions
|
||||
animation complex = solid(color=green)
|
||||
complex.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
complex.opacity = max(50, min(255, rand_demo() + 100))
|
||||
|
||||
run random_test
|
||||
```
|
||||
@ -129,11 +129,11 @@ import user_functions # Basic user functions
|
||||
import fire_effects # Fire animation functions
|
||||
import color_utilities # Color manipulation functions
|
||||
|
||||
animation base = solid(color=user.random_color())
|
||||
base.opacity = user.breathing(200, 50)
|
||||
animation base = solid(color=random_color())
|
||||
base.opacity = breathing(200, 50)
|
||||
|
||||
animation flames = solid(color=red)
|
||||
flames.opacity = user.fire_intensity(180)
|
||||
flames.opacity = fire_intensity(180)
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
@ -153,10 +153,10 @@ animation.register_user_function("bright", solid_bright)
|
||||
|
||||
```berry
|
||||
animation bright_red = solid(color=red)
|
||||
bright_red.opacity = user.bright(80)
|
||||
bright_red.opacity = bright(80)
|
||||
|
||||
animation dim_blue = solid(color=blue)
|
||||
dim_blue.opacity = user.bright(30)
|
||||
dim_blue.opacity = bright(30)
|
||||
```
|
||||
|
||||
### Fire Effects
|
||||
@ -178,32 +178,32 @@ animation.register_user_function("fire", custom_fire)
|
||||
|
||||
```berry
|
||||
animation campfire = solid(color=red)
|
||||
campfire.opacity = user.fire(200, 2000)
|
||||
campfire.opacity = fire(200, 2000)
|
||||
|
||||
animation torch = solid(color=orange)
|
||||
torch.opacity = user.fire(255, 500)
|
||||
torch.opacity = fire(255, 500)
|
||||
```
|
||||
|
||||
### Sparkle Effects
|
||||
### Twinkling Effects
|
||||
|
||||
```berry
|
||||
def sparkles(engine, color, density, speed)
|
||||
def twinkles(engine, color, count, period)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = color
|
||||
anim.density = density
|
||||
anim.speed = speed
|
||||
anim.count = count
|
||||
anim.period = period
|
||||
return anim
|
||||
end
|
||||
|
||||
animation.register_user_function("sparkles", sparkles)
|
||||
animation.register_user_function("twinkles", twinkles)
|
||||
```
|
||||
|
||||
```berry
|
||||
animation stars = solid(color=white)
|
||||
stars.opacity = user.sparkles(12, 300)
|
||||
stars.opacity = twinkles(12, 800ms)
|
||||
|
||||
animation fairy_dust = solid(color=#FFD700)
|
||||
fairy_dust.opacity = user.sparkles(8, 500)
|
||||
fairy_dust.opacity = twinkles(8, 600ms)
|
||||
```
|
||||
|
||||
### Position-Based Effects
|
||||
@ -223,10 +223,10 @@ animation.register_user_function("pulse_at", pulse_at)
|
||||
|
||||
```berry
|
||||
animation left_pulse = solid(color=green)
|
||||
left_pulse.position = user.pulse_at(5, 3, 2000)
|
||||
left_pulse.position = pulse_at(5, 3, 2000)
|
||||
|
||||
animation right_pulse = solid(color=blue)
|
||||
right_pulse.position = user.pulse_at(25, 3, 2000)
|
||||
right_pulse.position = pulse_at(25, 3, 2000)
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
@ -234,7 +234,7 @@ right_pulse.position = user.pulse_at(25, 3, 2000)
|
||||
### Multi-Layer Effects
|
||||
|
||||
```berry
|
||||
def rainbow_sparkle(engine, base_speed, sparkle_density)
|
||||
def rainbow_twinkle(engine, base_speed, twinkle_density)
|
||||
# Create base rainbow animation
|
||||
var rainbow_provider = animation.rich_palette(engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
@ -291,7 +291,7 @@ animation.register_user_function("custom_palette", create_custom_palette)
|
||||
```berry
|
||||
# Use dynamic palette in DSL
|
||||
animation gradient_effect = rich_palette(
|
||||
palette=user.custom_palette(0xFF6B35, 5, 255)
|
||||
palette=custom_palette(0xFF6B35, 5, 255)
|
||||
cycle_period=4s
|
||||
)
|
||||
|
||||
@ -325,13 +325,13 @@ animation.register_user_function("alert", gentle_alert)
|
||||
|
||||
```berry
|
||||
animation emergency = solid(color=red)
|
||||
emergency.opacity = user.strobe()
|
||||
emergency.opacity = strobe()
|
||||
|
||||
animation notification = solid(color=yellow)
|
||||
notification.opacity = user.alert()
|
||||
notification.opacity = alert()
|
||||
|
||||
animation custom_police = solid(color=blue)
|
||||
custom_police.opacity = user.police(500)
|
||||
custom_police.opacity = police(500)
|
||||
```
|
||||
|
||||
## Function Organization
|
||||
@ -350,14 +350,14 @@ def fire_effect(engine, intensity, speed)
|
||||
# ... implementation
|
||||
end
|
||||
|
||||
def sparkle_effect(engine, color, density, speed)
|
||||
def twinkle_effect(engine, color, count, period)
|
||||
# ... implementation
|
||||
end
|
||||
|
||||
# Register all functions
|
||||
animation.register_user_function("breathing", breathing)
|
||||
animation.register_user_function("fire", fire_effect)
|
||||
animation.register_user_function("sparkle", sparkle_effect)
|
||||
animation.register_user_function("twinkle", twinkle_effect)
|
||||
|
||||
print("Custom animations loaded!")
|
||||
```
|
||||
@ -456,7 +456,7 @@ User functions can be used in computed parameter expressions alongside mathemati
|
||||
```berry
|
||||
# Simple user function call in property assignment
|
||||
animation base = solid(color=blue, priority=10)
|
||||
base.opacity = user.rand_demo() # User function as computed parameter
|
||||
base.opacity = rand_demo() # User function as computed parameter
|
||||
```
|
||||
|
||||
### User Functions with Mathematical Operations
|
||||
@ -468,7 +468,7 @@ set strip_len = strip_length()
|
||||
# Mix user functions with mathematical functions
|
||||
animation dynamic_solid = solid(
|
||||
color=purple
|
||||
opacity=max(50, min(255, user.rand_demo() + 100)) # Random opacity with bounds
|
||||
opacity=max(50, min(255, rand_demo() + 100)) # Random opacity with bounds
|
||||
priority=15
|
||||
)
|
||||
```
|
||||
@ -479,7 +479,7 @@ animation dynamic_solid = solid(
|
||||
# Use user function in arithmetic expressions
|
||||
animation random_effect = solid(
|
||||
color=cyan
|
||||
opacity=abs(user.rand_demo() - 128) + 64 # Random variation around middle value
|
||||
opacity=abs(rand_demo() - 128) + 64 # Random variation around middle value
|
||||
priority=12
|
||||
)
|
||||
```
|
||||
@ -496,7 +496,7 @@ When you use user functions in computed parameters:
|
||||
**Generated Code Example:**
|
||||
```berry
|
||||
# DSL code
|
||||
animation.opacity = max(100, user.breathing(red, 2000))
|
||||
animation.opacity = max(100, breathing(red, 2000))
|
||||
```
|
||||
|
||||
**Transpiles to:**
|
||||
@ -513,7 +513,7 @@ The following user functions are available by default:
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|------------|-------------|
|
||||
| `user.rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
|
||||
### Best Practices for Computed Parameters
|
||||
|
||||
@ -537,13 +537,13 @@ var dsl_code =
|
||||
"import user_functions\n"
|
||||
"\n"
|
||||
"animation my_fire = solid(color=red)\n"
|
||||
"my_fire.opacity = user.fire(200, 1500)\n"
|
||||
"animation my_sparkles = solid(color=white)\n"
|
||||
"my_sparkles.opacity = user.sparkle(8, 400)\n"
|
||||
"my_fire.opacity = fire(200, 1500)\n"
|
||||
"animation my_twinkles = solid(color=white)\n"
|
||||
"my_twinkles.opacity = twinkle(8, 400ms)\n"
|
||||
"\n"
|
||||
"sequence show {\n"
|
||||
" play my_fire for 10s\n"
|
||||
" play my_sparkles for 5s\n"
|
||||
" play my_twinkles for 5s\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"run show"
|
||||
@ -559,9 +559,9 @@ var my_show =
|
||||
"import user_functions\n"
|
||||
"\n"
|
||||
"animation campfire = solid(color=orange)\n"
|
||||
"campfire.opacity = user.fire(180, 2000)\n"
|
||||
"campfire.opacity = fire(180, 2000)\n"
|
||||
"animation stars = solid(color=#FFFFFF)\n"
|
||||
"stars.opacity = user.sparkle(6, 600)\n"
|
||||
"stars.opacity = twinkle(6, 600ms)\n"
|
||||
"\n"
|
||||
"sequence night_scene {\n"
|
||||
" play campfire for 30s\n"
|
||||
|
||||
@ -107,6 +107,8 @@ import "providers/oscillator_value_provider.be" as oscillator_value_provider
|
||||
register_to_animation(oscillator_value_provider)
|
||||
import "providers/strip_length_provider.be" as strip_length_provider
|
||||
register_to_animation(strip_length_provider)
|
||||
import "providers/iteration_number_provider.be" as iteration_number_provider
|
||||
register_to_animation(iteration_number_provider)
|
||||
import "providers/closure_value_provider.be" as closure_value_provider
|
||||
register_to_animation(closure_value_provider)
|
||||
|
||||
@ -145,24 +147,26 @@ import "animations/gradient" as gradient_animation
|
||||
register_to_animation(gradient_animation)
|
||||
import "animations/noise" as noise_animation
|
||||
register_to_animation(noise_animation)
|
||||
import "animations/plasma" as plasma_animation
|
||||
register_to_animation(plasma_animation)
|
||||
import "animations/sparkle" as sparkle_animation
|
||||
register_to_animation(sparkle_animation)
|
||||
# import "animations/plasma" as plasma_animation
|
||||
# register_to_animation(plasma_animation)
|
||||
# import "animations/sparkle" as sparkle_animation
|
||||
# register_to_animation(sparkle_animation)
|
||||
import "animations/wave" as wave_animation
|
||||
register_to_animation(wave_animation)
|
||||
import "animations/shift" as shift_animation
|
||||
register_to_animation(shift_animation)
|
||||
import "animations/bounce" as bounce_animation
|
||||
register_to_animation(bounce_animation)
|
||||
import "animations/scale" as scale_animation
|
||||
register_to_animation(scale_animation)
|
||||
import "animations/jitter" as jitter_animation
|
||||
register_to_animation(jitter_animation)
|
||||
# import "animations/shift" as shift_animation
|
||||
# register_to_animation(shift_animation)
|
||||
# import "animations/bounce" as bounce_animation
|
||||
# register_to_animation(bounce_animation)
|
||||
# import "animations/scale" as scale_animation
|
||||
# register_to_animation(scale_animation)
|
||||
# import "animations/jitter" as jitter_animation
|
||||
# register_to_animation(jitter_animation)
|
||||
|
||||
# Import palette examples
|
||||
import "animations/palettes" as palettes
|
||||
register_to_animation(palettes)
|
||||
# import "animations/all_wled_palettes" as all_wled_palettes
|
||||
# register_to_animation(all_wled_palettes)
|
||||
|
||||
# Import specialized animation classes
|
||||
import "animations/rich_palette_animation" as rich_palette_animation
|
||||
|
||||
@ -25,6 +25,7 @@ import animation
|
||||
# We don't include it to not create a closure, but use the global instead
|
||||
|
||||
# Create the DSL module and make it globally accessible
|
||||
#@ solidify:animation_dsl.SimpleDSLTranspiler.ExpressionResult,weak
|
||||
#@ solidify:animation_dsl,weak
|
||||
var animation_dsl = module("animation_dsl")
|
||||
global.animation_dsl = animation_dsl
|
||||
@ -46,8 +47,12 @@ import "dsl/lexer.be" as dsl_lexer
|
||||
register_to_dsl(dsl_lexer)
|
||||
import "dsl/transpiler.be" as dsl_transpiler
|
||||
register_to_dsl(dsl_transpiler)
|
||||
import "dsl/symbol_table.be" as dsl_symbol_table
|
||||
register_to_dsl(dsl_symbol_table)
|
||||
import "dsl/runtime.be" as dsl_runtime
|
||||
register_to_dsl(dsl_runtime)
|
||||
import "dsl/named_colors.be" as dsl_named_colors
|
||||
register_to_dsl(dsl_named_colors)
|
||||
|
||||
# Main DSL compilation function
|
||||
# Compiles DSL source code to Berry code
|
||||
|
||||
1244
lib/libesp32/berry_animation/src/animations/all_wled_palettes.be
Normal file
1244
lib/libesp32/berry_animation/src/animations/all_wled_palettes.be
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ class CometAnimation : animation.animation
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFFFFFF}, # Color for the comet head (32-bit ARGB value)
|
||||
# 'color' for the comet head (32-bit ARGB value), inherited from animation class
|
||||
"tail_length": {"min": 1, "max": 50, "default": 5}, # Length of the comet tail in pixels
|
||||
"speed": {"min": 1, "max": 25600, "default": 2560}, # Movement speed in 1/256th pixels per second
|
||||
"direction": {"enum": [-1, 1], "default": 1}, # Direction of movement (1 = forward, -1 = backward)
|
||||
@ -58,6 +58,9 @@ class CometAnimation : animation.animation
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
# Cache parameter values for performance (read once, use multiple times)
|
||||
var current_speed = self.speed
|
||||
var current_direction = self.direction
|
||||
@ -179,8 +182,6 @@ class CometAnimation : animation.animation
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
var color_str
|
||||
@ -193,4 +194,4 @@ class CometAnimation : animation.animation
|
||||
end
|
||||
end
|
||||
|
||||
return {'comet_animation': CometAnimation}
|
||||
return {'comet_animation': CometAnimation}
|
||||
|
||||
@ -24,12 +24,12 @@ class CrenelPositionAnimation : animation.animation
|
||||
|
||||
# Parameter definitions with constraints
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFFFFFF},
|
||||
"back_color": {"default": 0xFF000000},
|
||||
"pos": {"default": 0},
|
||||
"pulse_size": {"min": 0, "default": 1},
|
||||
"low_size": {"min": 0, "default": 3},
|
||||
"nb_pulse": {"default": -1}
|
||||
# 'color' for the comet head (32-bit ARGB value), inherited from animation class
|
||||
"back_color": {"default": 0xFF000000}, # background color, TODO change to transparent
|
||||
"pos": {"default": 0}, # start of the pulse (in pixel)
|
||||
"pulse_size": {"min": 0, "default": 1}, # number of pixels of the pulse
|
||||
"low_size": {"min": 0, "default": 3}, # number of pixel until next pos - full cycle is 2 + 3
|
||||
"nb_pulse": {"default": -1} # number of pulses, or `-1` for infinite
|
||||
}
|
||||
|
||||
# Render the crenel pattern to the provided frame buffer
|
||||
@ -127,4 +127,4 @@ class CrenelPositionAnimation : animation.animation
|
||||
end
|
||||
end
|
||||
|
||||
return {'crenel_position_animation': CrenelPositionAnimation}
|
||||
return {'crenel_position_animation': CrenelPositionAnimation}
|
||||
|
||||
@ -6,14 +6,14 @@
|
||||
#@ solidify:FireAnimation,weak
|
||||
class FireAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var heat_map # Array storing heat values for each pixel (0-255)
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var heat_map # bytes() buffer storing heat values for each pixel (0-255)
|
||||
var current_colors # bytes() buffer storing ARGB colors (4 bytes per pixel)
|
||||
var last_update # Last update time for flicker timing
|
||||
var random_seed # Seed for random number generation
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": nil},
|
||||
# 'color' for the comet head (32-bit ARGB value), inherited from animation class
|
||||
"intensity": {"min": 0, "max": 255, "default": 180},
|
||||
"flicker_speed": {"min": 1, "max": 20, "default": 8},
|
||||
"flicker_amount": {"min": 0, "max": 255, "default": 100},
|
||||
@ -29,8 +29,8 @@ class FireAnimation : animation.animation
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.heat_map = []
|
||||
self.current_colors = []
|
||||
self.heat_map = bytes() # Use bytes() buffer for efficient 0-255 value storage
|
||||
self.current_colors = bytes() # Use bytes() buffer for ARGB colors (4 bytes per pixel)
|
||||
self.last_update = 0
|
||||
|
||||
# Initialize random seed using engine time
|
||||
@ -40,14 +40,19 @@ class FireAnimation : animation.animation
|
||||
# Initialize buffers based on current strip length
|
||||
def _initialize_buffers()
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
self.heat_map.resize(strip_length)
|
||||
self.current_colors.resize(strip_length)
|
||||
|
||||
# Initialize all pixels to zero heat
|
||||
# Create new bytes() buffer for heat values (1 byte per pixel)
|
||||
self.heat_map.clear()
|
||||
self.heat_map.resize(strip_length)
|
||||
|
||||
# Create new bytes() buffer for colors (4 bytes per pixel: ARGB)
|
||||
self.current_colors.clear()
|
||||
self.current_colors.resize(strip_length * 4)
|
||||
|
||||
# Initialize all pixels to zero heat and black color (0xFF000000)
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.heat_map[i] = 0
|
||||
self.current_colors[i] = 0xFF000000 # Black with full alpha
|
||||
self.current_colors.set(i * 4, 0xFF000000, -4) # Black with full alpha
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
@ -77,6 +82,9 @@ class FireAnimation : animation.animation
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
# Check if it's time to update the fire simulation
|
||||
# Update frequency is based on flicker_speed (Hz)
|
||||
var flicker_speed = self.flicker_speed # Cache parameter value
|
||||
@ -99,8 +107,8 @@ class FireAnimation : animation.animation
|
||||
var color_param = self.color
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure buffers are correct size
|
||||
if size(self.heat_map) != strip_length
|
||||
# Ensure buffers are correct size (bytes() uses .size() method)
|
||||
if self.heat_map.size() != strip_length || self.current_colors.size() != strip_length * 4
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
@ -122,7 +130,13 @@ class FireAnimation : animation.animation
|
||||
var k = strip_length - 1
|
||||
while k >= 2
|
||||
var heat_avg = (self.heat_map[k-1] + self.heat_map[k-2] + self.heat_map[k-2]) / 3
|
||||
self.heat_map[k] = heat_avg
|
||||
# Ensure the result is an integer in valid range (0-255)
|
||||
if heat_avg < 0
|
||||
heat_avg = 0
|
||||
elif heat_avg > 255
|
||||
heat_avg = 255
|
||||
end
|
||||
self.heat_map[k] = int(heat_avg)
|
||||
k -= 1
|
||||
end
|
||||
end
|
||||
@ -130,7 +144,11 @@ class FireAnimation : animation.animation
|
||||
# Step 3: Randomly ignite new 'sparks' of heat near the bottom
|
||||
if self._random_range(255) < sparking_rate
|
||||
var spark_pos = self._random_range(7) # Sparks only in bottom 7 pixels
|
||||
var spark_heat = self._random_range(95) + 160 # Heat between 160-255
|
||||
var spark_heat = self._random_range(95) + 160 # Heat between 160-254
|
||||
# Ensure spark heat is in valid range (should already be, but be explicit)
|
||||
if spark_heat > 255
|
||||
spark_heat = 255
|
||||
end
|
||||
if spark_pos < strip_length
|
||||
self.heat_map[spark_pos] = spark_heat
|
||||
end
|
||||
@ -205,7 +223,7 @@ class FireAnimation : animation.animation
|
||||
end
|
||||
end
|
||||
|
||||
self.current_colors[i] = color
|
||||
self.current_colors.set(i * 4, color, -4)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
@ -229,7 +247,7 @@ class FireAnimation : animation.animation
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
frame.set_pixel_color(i, self.current_colors.get(i * 4, -4))
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
@ -9,7 +9,7 @@ class TwinkleAnimation : animation.animation
|
||||
|
||||
# Non-parameter instance variables only
|
||||
var twinkle_states # Array storing twinkle state for each pixel
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var current_colors # bytes() buffer storing ARGB colors (4 bytes per pixel)
|
||||
var last_update # Last update time for timing
|
||||
var random_seed # Seed for random number generation
|
||||
|
||||
@ -32,7 +32,7 @@ class TwinkleAnimation : animation.animation
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.twinkle_states = []
|
||||
self.current_colors = []
|
||||
self.current_colors = bytes() # Use bytes() buffer for ARGB colors (4 bytes per pixel)
|
||||
self.last_update = 0
|
||||
|
||||
# Initialize random seed using engine time
|
||||
@ -48,13 +48,16 @@ class TwinkleAnimation : animation.animation
|
||||
|
||||
# Resize arrays
|
||||
self.twinkle_states.resize(strip_length)
|
||||
self.current_colors.resize(strip_length)
|
||||
|
||||
# Create new bytes() buffer for colors (4 bytes per pixel: ARGB)
|
||||
self.current_colors.clear()
|
||||
self.current_colors.resize(strip_length * 4)
|
||||
|
||||
# Initialize all pixels to off state
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.twinkle_states[i] = 0 # 0 = off, >0 = brightness level
|
||||
self.current_colors[i] = 0x00000000 # Transparent (alpha = 0)
|
||||
self.current_colors.set(i * 4, 0x00000000, -4) # Transparent (alpha = 0)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
@ -133,14 +136,14 @@ class TwinkleAnimation : animation.animation
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure arrays are properly sized
|
||||
if size(self.twinkle_states) != strip_length
|
||||
if size(self.twinkle_states) != strip_length || self.current_colors.size() != strip_length * 4
|
||||
self._initialize_arrays()
|
||||
end
|
||||
|
||||
# Step 1: Fade existing twinkles by reducing alpha
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
var current_color = self.current_colors[i]
|
||||
var current_color = self.current_colors.get(i * 4, -4)
|
||||
var alpha = (current_color >> 24) & 0xFF
|
||||
|
||||
if alpha > 0
|
||||
@ -149,12 +152,12 @@ class TwinkleAnimation : animation.animation
|
||||
if alpha <= fade_amount
|
||||
# Star has faded completely - reset to transparent
|
||||
self.twinkle_states[i] = 0
|
||||
self.current_colors[i] = 0x00000000
|
||||
self.current_colors.set(i * 4, 0x00000000, -4)
|
||||
else
|
||||
# Reduce alpha while keeping RGB components unchanged
|
||||
var new_alpha = alpha - fade_amount
|
||||
var rgb = current_color & 0x00FFFFFF # Keep RGB, clear alpha
|
||||
self.current_colors[i] = (new_alpha << 24) | rgb
|
||||
self.current_colors.set(i * 4, (new_alpha << 24) | rgb, -4)
|
||||
end
|
||||
end
|
||||
i += 1
|
||||
@ -181,7 +184,7 @@ class TwinkleAnimation : animation.animation
|
||||
|
||||
# Create new star with full-brightness color and variable alpha
|
||||
self.twinkle_states[j] = 1 # Mark as active (non-zero)
|
||||
self.current_colors[j] = (star_alpha << 24) | (r << 16) | (g << 8) | b
|
||||
self.current_colors.set(j * 4, (star_alpha << 24) | (r << 16) | (g << 8) | b, -4)
|
||||
end
|
||||
end
|
||||
j += 1
|
||||
@ -204,7 +207,7 @@ class TwinkleAnimation : animation.animation
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure arrays are properly sized
|
||||
if size(self.twinkle_states) != strip_length
|
||||
if size(self.twinkle_states) != strip_length || self.current_colors.size() != strip_length * 4
|
||||
self._initialize_arrays()
|
||||
end
|
||||
|
||||
@ -213,7 +216,7 @@ class TwinkleAnimation : animation.animation
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
var color = self.current_colors[i]
|
||||
var color = self.current_colors.get(i * 4, -4)
|
||||
# Only set pixels that have some alpha (are visible)
|
||||
if (color >> 24) & 0xFF > 0
|
||||
frame.set_pixel_color(i, color)
|
||||
|
||||
@ -19,6 +19,9 @@ class AnimationEngine
|
||||
# Performance optimization
|
||||
var render_needed # Whether a render pass is needed
|
||||
|
||||
# Sequence iteration tracking (stack-based for nested sequences)
|
||||
var iteration_stack # Stack of iteration numbers for nested sequences
|
||||
|
||||
# Initialize the animation engine for a specific LED strip
|
||||
def init(strip)
|
||||
if strip == nil
|
||||
@ -40,6 +43,9 @@ class AnimationEngine
|
||||
self.time_ms = 0
|
||||
self.fast_loop_closure = nil
|
||||
self.render_needed = false
|
||||
|
||||
# Initialize iteration tracking stack
|
||||
self.iteration_stack = []
|
||||
end
|
||||
|
||||
# Run the animation engine
|
||||
@ -454,6 +460,46 @@ class AnimationEngine
|
||||
self.strip = nil
|
||||
end
|
||||
|
||||
# Sequence iteration tracking methods
|
||||
|
||||
# Push a new iteration context onto the stack
|
||||
# Called when a sequence starts repeating
|
||||
#
|
||||
# @param iteration_number: int - The current iteration number (0-based)
|
||||
def push_iteration_context(iteration_number)
|
||||
self.iteration_stack.push(iteration_number)
|
||||
end
|
||||
|
||||
# Pop the current iteration context from the stack
|
||||
# Called when a sequence finishes repeating
|
||||
def pop_iteration_context()
|
||||
if size(self.iteration_stack) > 0
|
||||
return self.iteration_stack.pop()
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# Update the current iteration number in the top context
|
||||
# Called when a sequence advances to the next iteration
|
||||
#
|
||||
# @param iteration_number: int - The new iteration number (0-based)
|
||||
def update_current_iteration(iteration_number)
|
||||
if size(self.iteration_stack) > 0
|
||||
self.iteration_stack[-1] = iteration_number
|
||||
end
|
||||
end
|
||||
|
||||
# Get the current iteration number from the innermost sequence context
|
||||
# Used by IterationNumberProvider to return the current iteration
|
||||
#
|
||||
# @return int|nil - Current iteration number (0-based) or nil if not in sequence
|
||||
def get_current_iteration_number()
|
||||
if size(self.iteration_stack) > 0
|
||||
return self.iteration_stack[-1]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
return f"AnimationEngine(running={self.is_running}, animations={size(self.animations)}, width={self.width})"
|
||||
|
||||
@ -90,6 +90,11 @@ class SequenceManager
|
||||
self.current_iteration = 0
|
||||
self.is_running = true
|
||||
|
||||
# Push iteration context to engine stack if this is a repeat sequence
|
||||
if self.is_repeat_sequence
|
||||
self.engine.push_iteration_context(self.current_iteration)
|
||||
end
|
||||
|
||||
# Start executing if we have steps
|
||||
if size(self.steps) > 0
|
||||
# Execute all consecutive closure steps at the beginning atomically
|
||||
@ -120,6 +125,11 @@ class SequenceManager
|
||||
if self.is_running
|
||||
self.is_running = false
|
||||
|
||||
# Pop iteration context from engine stack if this is a repeat sequence
|
||||
if self.is_repeat_sequence
|
||||
self.engine.pop_iteration_context()
|
||||
end
|
||||
|
||||
# Stop any currently playing animations
|
||||
if self.step_index < size(self.steps)
|
||||
var current_step = self.steps[self.step_index]
|
||||
@ -347,6 +357,11 @@ class SequenceManager
|
||||
def complete_iteration(current_time)
|
||||
self.current_iteration += 1
|
||||
|
||||
# Update iteration context in engine stack if this is a repeat sequence
|
||||
if self.is_repeat_sequence
|
||||
self.engine.update_current_iteration(self.current_iteration)
|
||||
end
|
||||
|
||||
# Resolve repeat count (may be a function)
|
||||
var resolved_repeat_count = self.get_resolved_repeat_count()
|
||||
|
||||
@ -376,6 +391,11 @@ class SequenceManager
|
||||
else
|
||||
# All iterations complete
|
||||
self.is_running = false
|
||||
|
||||
# Pop iteration context from engine stack if this is a repeat sequence
|
||||
if self.is_repeat_sequence
|
||||
self.engine.pop_iteration_context()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -68,7 +68,13 @@ class DSLLexer
|
||||
elif self.is_digit(ch)
|
||||
self.scan_number()
|
||||
elif ch == '"' || ch == "'"
|
||||
self.scan_string(ch)
|
||||
# Check for triple quotes
|
||||
if (ch == '"' && self.peek() == '"' && self.peek_ahead(1) == '"') ||
|
||||
(ch == "'" && self.peek() == "'" && self.peek_ahead(1) == "'")
|
||||
self.scan_triple_quoted_string(ch)
|
||||
else
|
||||
self.scan_string(ch)
|
||||
end
|
||||
elif ch == '$'
|
||||
self.scan_variable_reference()
|
||||
else
|
||||
@ -268,6 +274,49 @@ class DSLLexer
|
||||
end
|
||||
end
|
||||
|
||||
# Scan triple-quoted string literal (for berry code blocks)
|
||||
def scan_triple_quoted_string(quote_char)
|
||||
var start_pos = self.position - 1 # Include first opening quote
|
||||
var start_column = self.column - 1
|
||||
var value = ""
|
||||
|
||||
# Consume the two remaining opening quotes
|
||||
self.advance() # second quote
|
||||
self.advance() # third quote
|
||||
|
||||
# Look for the closing triple quotes
|
||||
while !self.at_end()
|
||||
var ch = self.peek()
|
||||
|
||||
# Check for closing triple quotes
|
||||
if ch == quote_char &&
|
||||
self.peek_ahead(1) == quote_char &&
|
||||
self.peek_ahead(2) == quote_char
|
||||
# Found closing triple quotes - consume them
|
||||
self.advance() # first closing quote
|
||||
self.advance() # second closing quote
|
||||
self.advance() # third closing quote
|
||||
break
|
||||
end
|
||||
|
||||
# Regular character - add to value
|
||||
ch = self.advance()
|
||||
if ch == '\n'
|
||||
self.line += 1
|
||||
self.column = 1
|
||||
end
|
||||
value += ch
|
||||
end
|
||||
|
||||
# Check if we reached end without finding closing quotes
|
||||
if self.at_end() && !(self.source[self.position-3..self.position-1] == quote_char + quote_char + quote_char)
|
||||
self.add_error("Unterminated triple-quoted string literal")
|
||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, value, self.position - start_pos)
|
||||
else
|
||||
self.add_token(3 #-animation_dsl.Token.STRING-#, value, self.position - start_pos)
|
||||
end
|
||||
end
|
||||
|
||||
# Scan variable reference ($identifier)
|
||||
def scan_variable_reference()
|
||||
var start_pos = self.position - 1 # Include $
|
||||
@ -419,6 +468,14 @@ class DSLLexer
|
||||
return self.source[self.position + 1]
|
||||
end
|
||||
|
||||
# Peek ahead by n characters without advancing
|
||||
def peek_ahead(n)
|
||||
if self.position + n >= size(self.source)
|
||||
return ""
|
||||
end
|
||||
return self.source[self.position + n]
|
||||
end
|
||||
|
||||
# Check if current character matches expected and advance if so
|
||||
def match(expected)
|
||||
if self.at_end() || self.source[self.position] != expected
|
||||
|
||||
62
lib/libesp32/berry_animation/src/dsl/named_colors.be
Normal file
62
lib/libesp32/berry_animation/src/dsl/named_colors.be
Normal file
@ -0,0 +1,62 @@
|
||||
# Named Colors Module for Animation DSL
|
||||
# Provides color name to ARGB value mappings for the DSL transpiler
|
||||
|
||||
# Static color mapping for named colors (helps with solidification)
|
||||
# Maps color names to ARGB hex values (0xAARRGGBB format)
|
||||
# All colors have full alpha (0xFF) except transparent
|
||||
var named_colors = {
|
||||
# Primary colors
|
||||
"red": "0xFFFF0000", # Pure red
|
||||
"green": "0xFF008000", # HTML/CSS standard green (darker, more readable)
|
||||
"blue": "0xFF0000FF", # Pure blue
|
||||
|
||||
# Achromatic colors
|
||||
"white": "0xFFFFFFFF", # Pure white
|
||||
"black": "0xFF000000", # Pure black
|
||||
"gray": "0xFF808080", # Medium gray
|
||||
"grey": "0xFF808080", # Alternative spelling
|
||||
"silver": "0xFFC0C0C0", # Light gray
|
||||
|
||||
# Secondary colors
|
||||
"yellow": "0xFFFFFF00", # Pure yellow (red + green)
|
||||
"cyan": "0xFF00FFFF", # Pure cyan (green + blue)
|
||||
"magenta": "0xFFFF00FF", # Pure magenta (red + blue)
|
||||
|
||||
# Extended web colors
|
||||
"orange": "0xFFFFA500", # Orange
|
||||
"purple": "0xFF800080", # Purple (darker magenta)
|
||||
"pink": "0xFFFFC0CB", # Light pink
|
||||
"lime": "0xFF00FF00", # Pure green (HTML/CSS lime = full intensity)
|
||||
"navy": "0xFF000080", # Dark blue
|
||||
"olive": "0xFF808000", # Dark yellow-green
|
||||
"maroon": "0xFF800000", # Dark red
|
||||
"teal": "0xFF008080", # Dark cyan
|
||||
"aqua": "0xFF00FFFF", # Same as cyan
|
||||
"fuchsia": "0xFFFF00FF", # Same as magenta
|
||||
|
||||
# Precious metals
|
||||
"gold": "0xFFFFD700", # Metallic gold
|
||||
|
||||
# Natural colors
|
||||
"brown": "0xFFA52A2A", # Saddle brown
|
||||
"tan": "0xFFD2B48C", # Light brown/beige
|
||||
"beige": "0xFFF5F5DC", # Very light brown
|
||||
"ivory": "0xFFFFFFF0", # Off-white with yellow tint
|
||||
"snow": "0xFFFFFAFA", # Off-white with slight blue tint
|
||||
|
||||
# Flower/nature colors
|
||||
"indigo": "0xFF4B0082", # Deep blue-purple
|
||||
"violet": "0xFFEE82EE", # Light purple
|
||||
"crimson": "0xFFDC143C", # Deep red
|
||||
"coral": "0xFFFF7F50", # Orange-pink
|
||||
"salmon": "0xFFFA8072", # Pink-orange
|
||||
"khaki": "0xFFF0E68C", # Pale yellow-brown
|
||||
"plum": "0xFFDDA0DD", # Light purple
|
||||
"orchid": "0xFFDA70D6", # Medium purple
|
||||
"turquoise": "0xFF40E0D0", # Blue-green
|
||||
|
||||
# Special
|
||||
"transparent": "0x00000000" # Fully transparent (alpha = 0)
|
||||
}
|
||||
|
||||
return {"named_colors": named_colors}
|
||||
601
lib/libesp32/berry_animation/src/dsl/symbol_table.be
Normal file
601
lib/libesp32/berry_animation/src/dsl/symbol_table.be
Normal file
@ -0,0 +1,601 @@
|
||||
# Symbol Table Classes for DSL Transpiler
|
||||
# Enhanced symbol caching and management for the Animation DSL
|
||||
|
||||
# Symbol table entry class for enhanced symbol caching
|
||||
#@ solidify:SymbolEntry,weak
|
||||
class SymbolEntry
|
||||
# Type constants
|
||||
static var TYPE_PALETTE_CONSTANT = 1
|
||||
static var TYPE_PALETTE = 2
|
||||
static var TYPE_CONSTANT = 3
|
||||
static var TYPE_MATH_FUNCTION = 4
|
||||
static var TYPE_USER_FUNCTION = 5
|
||||
static var TYPE_VALUE_PROVIDER_CONSTRUCTOR = 6
|
||||
static var TYPE_VALUE_PROVIDER = 7
|
||||
static var TYPE_ANIMATION_CONSTRUCTOR = 8
|
||||
static var TYPE_ANIMATION = 9
|
||||
static var TYPE_COLOR_CONSTRUCTOR = 10
|
||||
static var TYPE_COLOR = 11
|
||||
static var TYPE_VARIABLE = 12
|
||||
static var TYPE_SEQUENCE = 13
|
||||
static var TYPE_TEMPLATE = 14
|
||||
|
||||
var name # Symbol name
|
||||
var type # Symbol type (int constant)
|
||||
var instance # Actual instance (for validation) or nil
|
||||
var takes_args # Boolean: whether this symbol takes arguments
|
||||
var arg_type # "positional", "named", or "none"
|
||||
var is_builtin # Boolean: whether this is a built-in symbol from animation module
|
||||
var is_dangerous # Boolean: whether calling this symbol creates a new instance (dangerous in computed expressions)
|
||||
var param_types # Map of parameter names to types (for templates and user functions)
|
||||
|
||||
def init(name, typ, instance, is_builtin)
|
||||
self.name = name
|
||||
self.type = typ
|
||||
self.instance = instance
|
||||
self.is_builtin = is_builtin != nil ? is_builtin : false
|
||||
self.takes_args = false
|
||||
self.arg_type = "none"
|
||||
self.is_dangerous = false
|
||||
self.param_types = {}
|
||||
|
||||
# Auto-detect argument characteristics and danger level based on type
|
||||
self._detect_arg_characteristics()
|
||||
self._detect_danger_level()
|
||||
end
|
||||
|
||||
# Detect if this symbol takes arguments and what type
|
||||
def _detect_arg_characteristics()
|
||||
if self.type == self.TYPE_PALETTE_CONSTANT || self.type == self.TYPE_PALETTE || self.type == self.TYPE_CONSTANT
|
||||
# Palette objects and constants don't take arguments
|
||||
self.takes_args = false
|
||||
self.arg_type = "none"
|
||||
elif self.type == self.TYPE_MATH_FUNCTION
|
||||
# Math functions like max, min take positional arguments
|
||||
self.takes_args = true
|
||||
self.arg_type = "positional"
|
||||
elif self.type == self.TYPE_USER_FUNCTION
|
||||
# User functions take positional arguments (engine + user args)
|
||||
self.takes_args = true
|
||||
self.arg_type = "positional"
|
||||
elif self.type == self.TYPE_VALUE_PROVIDER_CONSTRUCTOR || self.type == self.TYPE_ANIMATION_CONSTRUCTOR || self.type == self.TYPE_COLOR_CONSTRUCTOR
|
||||
# Constructor functions take named arguments
|
||||
self.takes_args = true
|
||||
self.arg_type = "named"
|
||||
else
|
||||
# Instances, variables, sequences, templates don't take arguments when referenced
|
||||
self.takes_args = false
|
||||
self.arg_type = "none"
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if this symbol is dangerous (creates new instances when called)
|
||||
def _detect_danger_level()
|
||||
if self.type == self.TYPE_VALUE_PROVIDER_CONSTRUCTOR
|
||||
# Value provider constructors create new instances - dangerous in computed expressions
|
||||
self.is_dangerous = true
|
||||
elif self.type == self.TYPE_ANIMATION_CONSTRUCTOR
|
||||
# Animation constructors create new instances - dangerous in computed expressions
|
||||
self.is_dangerous = true
|
||||
elif self.type == self.TYPE_COLOR_CONSTRUCTOR
|
||||
# Color provider constructors create new instances - dangerous in computed expressions
|
||||
self.is_dangerous = true
|
||||
else
|
||||
# Constants, math functions, variables, instances, user functions, etc. are safe
|
||||
self.is_dangerous = false
|
||||
end
|
||||
end
|
||||
|
||||
# Check if this symbol is a bytes() instance (for palettes)
|
||||
def is_bytes_instance()
|
||||
return (self.type == self.TYPE_PALETTE_CONSTANT || self.type == self.TYPE_PALETTE) && self.instance != nil && isinstance(self.instance, bytes)
|
||||
end
|
||||
|
||||
# Check if this symbol is a math function
|
||||
def is_math_function()
|
||||
return self.type == self.TYPE_MATH_FUNCTION
|
||||
end
|
||||
|
||||
# Check if this symbol is a user function
|
||||
def is_user_function()
|
||||
return self.type == self.TYPE_USER_FUNCTION
|
||||
end
|
||||
|
||||
|
||||
# Check if this symbol is a value provider constructor
|
||||
def is_value_provider_constructor()
|
||||
return self.type == self.TYPE_VALUE_PROVIDER_CONSTRUCTOR
|
||||
end
|
||||
|
||||
# Check if this symbol is a value provider instance
|
||||
def is_value_provider_instance()
|
||||
return self.type == self.TYPE_VALUE_PROVIDER
|
||||
end
|
||||
|
||||
# Check if this symbol is an animation constructor
|
||||
def is_animation_constructor()
|
||||
return self.type == self.TYPE_ANIMATION_CONSTRUCTOR
|
||||
end
|
||||
|
||||
# Check if this symbol is an animation instance
|
||||
def is_animation_instance()
|
||||
return self.type == self.TYPE_ANIMATION
|
||||
end
|
||||
|
||||
# Check if this symbol is a color constructor
|
||||
def is_color_constructor()
|
||||
return self.type == self.TYPE_COLOR_CONSTRUCTOR
|
||||
end
|
||||
|
||||
# Check if this symbol is a color instance
|
||||
def is_color_instance()
|
||||
return self.type == self.TYPE_COLOR
|
||||
end
|
||||
|
||||
# Check if this symbol takes positional arguments
|
||||
def takes_positional_args()
|
||||
return self.takes_args && self.arg_type == "positional"
|
||||
end
|
||||
|
||||
# Check if this symbol takes named arguments
|
||||
def takes_named_args()
|
||||
return self.takes_args && self.arg_type == "named"
|
||||
end
|
||||
|
||||
# Check if this symbol is dangerous (creates new instances when called)
|
||||
def is_dangerous_call()
|
||||
return self.is_dangerous
|
||||
end
|
||||
|
||||
# Set parameter types for templates and user functions
|
||||
def set_param_types(param_types)
|
||||
self.param_types = param_types != nil ? param_types : {}
|
||||
end
|
||||
|
||||
# Get parameter types
|
||||
def get_param_types()
|
||||
return self.param_types
|
||||
end
|
||||
|
||||
# Convert type constant to string for debugging
|
||||
def type_to_string()
|
||||
if self.type == self.TYPE_PALETTE_CONSTANT return "palette_constant"
|
||||
elif self.type == self.TYPE_PALETTE return "palette"
|
||||
elif self.type == self.TYPE_CONSTANT return "constant"
|
||||
elif self.type == self.TYPE_MATH_FUNCTION return "math_function"
|
||||
elif self.type == self.TYPE_USER_FUNCTION return "user_function"
|
||||
elif self.type == self.TYPE_VALUE_PROVIDER_CONSTRUCTOR return "value_provider_constructor"
|
||||
elif self.type == self.TYPE_VALUE_PROVIDER return "value_provider"
|
||||
elif self.type == self.TYPE_ANIMATION_CONSTRUCTOR return "animation_constructor"
|
||||
elif self.type == self.TYPE_ANIMATION return "animation"
|
||||
elif self.type == self.TYPE_COLOR_CONSTRUCTOR return "color_constructor"
|
||||
elif self.type == self.TYPE_COLOR return "color"
|
||||
elif self.type == self.TYPE_VARIABLE return "variable"
|
||||
elif self.type == self.TYPE_SEQUENCE return "sequence"
|
||||
elif self.type == self.TYPE_TEMPLATE return "template"
|
||||
else return f"unknown({self.type})"
|
||||
end
|
||||
end
|
||||
|
||||
# Get the resolved symbol reference for code generation
|
||||
def get_reference()
|
||||
# Generate appropriate reference based on whether it's built-in
|
||||
if self.is_builtin
|
||||
# Special handling for math functions
|
||||
if self.type == self.TYPE_MATH_FUNCTION
|
||||
return f"animation._math.{self.name}"
|
||||
else
|
||||
return f"animation.{self.name}"
|
||||
end
|
||||
else
|
||||
# User-defined symbols get underscore suffix
|
||||
return f"{self.name}_"
|
||||
end
|
||||
end
|
||||
|
||||
# String representation for debugging
|
||||
def tostring()
|
||||
import string
|
||||
|
||||
var instance_str = "nil"
|
||||
if self.instance != nil
|
||||
var instance_type = type(self.instance)
|
||||
if instance_type == "instance"
|
||||
instance_str = f"<{classname(self.instance)}>"
|
||||
else
|
||||
instance_str = f"<{instance_type}:{str(self.instance)}>"
|
||||
end
|
||||
end
|
||||
|
||||
var param_types_str = ""
|
||||
if size(self.param_types) > 0
|
||||
var params_list = ""
|
||||
var first = true
|
||||
for key : self.param_types.keys()
|
||||
if !first
|
||||
params_list += ","
|
||||
end
|
||||
params_list += f"{key}:{self.param_types[key]}"
|
||||
first = false
|
||||
end
|
||||
param_types_str = f" params=[{params_list}]"
|
||||
end
|
||||
|
||||
return f"SymbolEntry(name='{self.name}', type='{self.type_to_string()}', instance={instance_str}, " +
|
||||
f"takes_args={self.takes_args}, arg_type='{self.arg_type}', " +
|
||||
f"is_builtin={self.is_builtin}, is_dangerous={self.is_dangerous}{param_types_str})"
|
||||
end
|
||||
|
||||
# Create a symbol entry for a palette constant (built-in like PALETTE_RAINBOW)
|
||||
static def create_palette_constant(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_PALETTE_CONSTANT, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a palette instance (user-defined)
|
||||
static def create_palette_instance(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_PALETTE, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for an integer constant
|
||||
static def create_constant(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_CONSTANT, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a math function
|
||||
static def create_math_function(name, is_builtin)
|
||||
return _class(name, _class.TYPE_MATH_FUNCTION, nil, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a user function
|
||||
static def create_user_function(name, is_builtin)
|
||||
return _class(name, _class.TYPE_USER_FUNCTION, nil, is_builtin)
|
||||
end
|
||||
|
||||
|
||||
# Create a symbol entry for a value provider constructor (built-in like triangle, smooth)
|
||||
static def create_value_provider_constructor(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_VALUE_PROVIDER_CONSTRUCTOR, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a value provider instance (user-defined)
|
||||
static def create_value_provider_instance(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_VALUE_PROVIDER, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for an animation constructor (built-in like solid, pulsating_animation)
|
||||
static def create_animation_constructor(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_ANIMATION_CONSTRUCTOR, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for an animation instance (user-defined)
|
||||
static def create_animation_instance(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_ANIMATION, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a color constructor (built-in like color_cycle, breathe_color)
|
||||
static def create_color_constructor(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_COLOR_CONSTRUCTOR, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a color instance (user-defined)
|
||||
static def create_color_instance(name, instance, is_builtin)
|
||||
return _class(name, _class.TYPE_COLOR, instance, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a variable
|
||||
static def create_variable(name, is_builtin)
|
||||
return _class(name, _class.TYPE_VARIABLE, nil, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a sequence
|
||||
static def create_sequence(name, is_builtin)
|
||||
return _class(name, _class.TYPE_SEQUENCE, nil, is_builtin)
|
||||
end
|
||||
|
||||
# Create a symbol entry for a template
|
||||
static def create_template(name, is_builtin)
|
||||
return _class(name, _class.TYPE_TEMPLATE, nil, is_builtin)
|
||||
end
|
||||
end
|
||||
|
||||
# Mock engine class for parameter validation during transpilation
|
||||
class MockEngine
|
||||
var time_ms
|
||||
|
||||
def init()
|
||||
self.time_ms = 0
|
||||
end
|
||||
|
||||
def get_strip_length()
|
||||
return 30 # Default strip length for validation
|
||||
end
|
||||
end
|
||||
|
||||
# Enhanced symbol table class for holistic symbol management and caching
|
||||
#@ solidify:SymbolTable,weak
|
||||
class SymbolTable
|
||||
var entries # Map of name -> SymbolEntry
|
||||
var mock_engine # MockEngine for validation
|
||||
|
||||
def init()
|
||||
self.entries = {}
|
||||
self.mock_engine = animation_dsl.MockEngine()
|
||||
end
|
||||
|
||||
# Dynamically detect and cache symbol type when first encountered
|
||||
def _detect_and_cache_symbol(name)
|
||||
if self.entries.contains(name)
|
||||
return self.entries[name] # Already cached
|
||||
end
|
||||
|
||||
try
|
||||
import introspect
|
||||
|
||||
# Check for named colors first (from animation_dsl.named_colors)
|
||||
if animation_dsl.named_colors.contains(name)
|
||||
var entry = animation_dsl._symbol_entry.create_color_instance(name, nil, true) # true = is_builtin
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
# Check for special built-in functions like 'log'
|
||||
if name == "log"
|
||||
var entry = animation_dsl._symbol_entry.create_user_function("log", true) # true = is_builtin
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
|
||||
# Check for user functions (they might not be in animation module directly)
|
||||
if animation.is_user_function(name)
|
||||
var entry = animation_dsl._symbol_entry.create_user_function(name, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
# Check for math functions (they are in animation._math, not directly in animation)
|
||||
if introspect.contains(animation._math, name)
|
||||
var entry = animation_dsl._symbol_entry.create_math_function(name, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
# Check if it exists in animation module
|
||||
if introspect.contains(animation, name)
|
||||
var obj = animation.(name)
|
||||
var obj_type = type(obj)
|
||||
|
||||
# Detect palette objects (bytes() instances)
|
||||
if isinstance(obj, bytes)
|
||||
var entry = animation_dsl._symbol_entry.create_palette_constant(name, obj, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
# Detect integer constants (like LINEAR, SINE, COSINE, etc.)
|
||||
if obj_type == "int"
|
||||
var entry = animation_dsl._symbol_entry.create_constant(name, obj, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
# Detect constructors (functions/classes that create instances)
|
||||
if obj_type == "function" || obj_type == "class"
|
||||
try
|
||||
var instance = obj(self.mock_engine)
|
||||
if isinstance(instance, animation.color_provider)
|
||||
# Color providers are a subclass of value providers, check them first
|
||||
var entry = animation_dsl._symbol_entry.create_color_constructor(name, instance, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
elif isinstance(instance, animation.value_provider)
|
||||
var entry = animation_dsl._symbol_entry.create_value_provider_constructor(name, instance, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
elif isinstance(instance, animation.animation)
|
||||
var entry = animation_dsl._symbol_entry.create_animation_constructor(name, instance, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
end
|
||||
except .. as e, msg
|
||||
# If instance creation fails, it might still be a valid function
|
||||
# but not a constructor we can validate
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# If not found in animation module, return nil (will be handled as user-defined)
|
||||
return nil
|
||||
|
||||
except .. as e, msg
|
||||
# If detection fails, return nil
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# Add a symbol entry to the table (with conflict detection) - returns the entry
|
||||
def add(name, entry)
|
||||
# First check if there's a built-in symbol with this name
|
||||
var builtin_entry = self._detect_and_cache_symbol(name)
|
||||
if builtin_entry != nil && builtin_entry.type != entry.type
|
||||
raise "symbol_redefinition_error", f"Cannot define '{name}' as {entry.type_to_string()} - it conflicts with built-in {builtin_entry.type_to_string()}"
|
||||
end
|
||||
|
||||
# Check existing user-defined symbols
|
||||
var existing = self.entries.find(name)
|
||||
if existing != nil
|
||||
# Check if it's the same type
|
||||
if existing.type != entry.type
|
||||
raise "symbol_redefinition_error", f"Cannot redefine symbol '{name}' as {entry.type_to_string()} - it's already defined as {existing.type_to_string()}"
|
||||
end
|
||||
# If same type, allow update (for cases like reassignment)
|
||||
end
|
||||
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
# Check if a symbol exists (with dynamic detection)
|
||||
def contains(name)
|
||||
if self.entries.contains(name)
|
||||
return true
|
||||
end
|
||||
|
||||
# Try to detect and cache it
|
||||
var entry = self._detect_and_cache_symbol(name)
|
||||
return entry != nil
|
||||
end
|
||||
|
||||
# Get a symbol entry (with dynamic detection)
|
||||
def get(name)
|
||||
var entry = self.entries.find(name)
|
||||
if entry != nil
|
||||
return entry
|
||||
end
|
||||
|
||||
# Try to detect and cache it
|
||||
return self._detect_and_cache_symbol(name)
|
||||
end
|
||||
|
||||
# Get symbol reference for code generation (with dynamic detection)
|
||||
def get_reference(name)
|
||||
# Try to get from cache or detect dynamically (includes named colors)
|
||||
var entry = self.get(name)
|
||||
if entry != nil
|
||||
# For builtin color entries, return the actual color value directly
|
||||
if entry.is_builtin && entry.type == animation_dsl._symbol_entry.TYPE_COLOR
|
||||
return animation_dsl.named_colors[name]
|
||||
end
|
||||
return entry.get_reference()
|
||||
end
|
||||
|
||||
# Default to user-defined format
|
||||
return f"{name}_"
|
||||
end
|
||||
|
||||
# Check if symbol exists (including named colors, with dynamic detection)
|
||||
def symbol_exists(name)
|
||||
# Use proper discovery through _detect_and_cache_symbol via contains()
|
||||
return self.contains(name)
|
||||
end
|
||||
|
||||
# Create and register a palette instance symbol (user-defined)
|
||||
def create_palette(name, instance)
|
||||
var entry = animation_dsl._symbol_entry.create_palette_instance(name, instance, false)
|
||||
return self.add(name, entry)
|
||||
end
|
||||
|
||||
# Create and register a color instance symbol (user-defined)
|
||||
def create_color(name, instance)
|
||||
var entry = animation_dsl._symbol_entry.create_color_instance(name, instance, false)
|
||||
return self.add(name, entry)
|
||||
end
|
||||
|
||||
# Create and register an animation instance symbol (user-defined)
|
||||
def create_animation(name, instance)
|
||||
var entry = animation_dsl._symbol_entry.create_animation_instance(name, instance, false)
|
||||
return self.add(name, entry)
|
||||
end
|
||||
|
||||
# Create and register a value provider instance symbol (user-defined)
|
||||
def create_value_provider(name, instance)
|
||||
var entry = animation_dsl._symbol_entry.create_value_provider_instance(name, instance, false)
|
||||
return self.add(name, entry)
|
||||
end
|
||||
|
||||
# Create and register a variable symbol (user-defined)
|
||||
def create_variable(name)
|
||||
var entry = animation_dsl._symbol_entry.create_variable(name, false)
|
||||
return self.add(name, entry)
|
||||
end
|
||||
|
||||
# Create and register a sequence symbol (user-defined)
|
||||
def create_sequence(name)
|
||||
var entry = animation_dsl._symbol_entry.create_sequence(name, false)
|
||||
return self.add(name, entry)
|
||||
end
|
||||
|
||||
# Create and register a template symbol (user-defined)
|
||||
def create_template(name, param_types)
|
||||
var entry = animation_dsl._symbol_entry.create_template(name, false)
|
||||
entry.set_param_types(param_types != nil ? param_types : {})
|
||||
return self.add(name, entry)
|
||||
end
|
||||
|
||||
|
||||
# Register a user function (detected at runtime)
|
||||
def register_user_function(name)
|
||||
if !self.contains(name)
|
||||
var entry = animation_dsl._symbol_entry.create_user_function(name, false)
|
||||
self.add(name, entry)
|
||||
end
|
||||
end
|
||||
|
||||
# Generic create function that can specify name/type/instance/builtin directly
|
||||
def create_generic(name, typ, instance, is_builtin)
|
||||
var entry = animation_dsl._symbol_entry(name, typ, instance, is_builtin != nil ? is_builtin : false)
|
||||
return self.add(name, entry)
|
||||
end
|
||||
|
||||
# Get the type of a symbol
|
||||
def get_type(name)
|
||||
var entry = self.get(name)
|
||||
return entry != nil ? entry.type_to_string() : nil
|
||||
end
|
||||
|
||||
# Check if symbol takes arguments
|
||||
def takes_args(name)
|
||||
var entry = self.get(name)
|
||||
return entry != nil ? entry.takes_args : false
|
||||
end
|
||||
|
||||
# Check if symbol takes positional arguments
|
||||
def takes_positional_args(name)
|
||||
var entry = self.get(name)
|
||||
return entry != nil ? entry.takes_positional_args() : false
|
||||
end
|
||||
|
||||
# Check if symbol takes named arguments
|
||||
def takes_named_args(name)
|
||||
var entry = self.get(name)
|
||||
return entry != nil ? entry.takes_named_args() : false
|
||||
end
|
||||
|
||||
# Get instance for validation
|
||||
def get_instance(name)
|
||||
var entry = self.get(name)
|
||||
return entry != nil ? entry.instance : nil
|
||||
end
|
||||
|
||||
# Check if symbol is dangerous (creates new instances when called)
|
||||
def is_dangerous(name)
|
||||
var entry = self.get(name)
|
||||
return entry != nil ? entry.is_dangerous_call() : false
|
||||
end
|
||||
|
||||
# Helper method to get named color value (uses proper discovery)
|
||||
def _get_named_color_value(color_name)
|
||||
var entry = self.get(color_name) # This will trigger _detect_and_cache_symbol if needed
|
||||
if entry != nil && entry.is_builtin && entry.type == animation_dsl._symbol_entry.TYPE_COLOR
|
||||
return animation_dsl.named_colors[color_name]
|
||||
end
|
||||
return "0xFFFFFFFF" # Default fallback
|
||||
end
|
||||
|
||||
# Debug method to list all symbols
|
||||
def list_symbols()
|
||||
var result = []
|
||||
for name : self.entries.keys()
|
||||
var entry = self.entries[name]
|
||||
result.push(f"{name}: {entry.type_to_string()}")
|
||||
end
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
# Return module exports
|
||||
return {
|
||||
"_symbol_entry": SymbolEntry,
|
||||
"_symbol_table": SymbolTable,
|
||||
"MockEngine": MockEngine
|
||||
}
|
||||
@ -27,12 +27,12 @@ class Token
|
||||
|
||||
static var statement_keywords = [
|
||||
"strip", "set", "color", "palette", "animation",
|
||||
"sequence", "function", "zone", "on", "run", "template", "param", "import"
|
||||
"sequence", "function", "zone", "on", "run", "template", "param", "import", "berry"
|
||||
]
|
||||
|
||||
static var keywords = [
|
||||
# Configuration keywords
|
||||
"strip", "set", "import",
|
||||
"strip", "set", "import", "berry",
|
||||
|
||||
# Definition keywords
|
||||
"color", "palette", "animation", "sequence", "function", "zone", "template", "param", "type",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -37,7 +37,6 @@ class BreatheColorProvider : animation.oscillator_value
|
||||
|
||||
# Handle parameter changes - no need to sync oscillator min/max since they're fixed
|
||||
def on_param_changed(name, value)
|
||||
super(self).on_param_changed(name, value)
|
||||
# Only handle curve_factor changes for oscillator form
|
||||
if name == "curve_factor"
|
||||
# For curve_factor = 1, use pure cosine
|
||||
@ -95,10 +94,10 @@ class BreatheColorProvider : animation.oscillator_value
|
||||
var green = (current_base_color >> 8) & 0xFF
|
||||
var blue = current_base_color & 0xFF
|
||||
|
||||
# Apply brightness scaling
|
||||
red = (red * brightness) / 255
|
||||
green = (green * brightness) / 255
|
||||
blue = (blue * brightness) / 255
|
||||
# Apply brightness scaling using tasmota.scale_uint
|
||||
red = tasmota.scale_uint(red, 0, 255, 0, brightness)
|
||||
green = tasmota.scale_uint(green, 0, 255, 0, brightness)
|
||||
blue = tasmota.scale_uint(blue, 0, 255, 0, brightness)
|
||||
|
||||
# Reconstruct color
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue
|
||||
|
||||
@ -198,21 +198,10 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
return self._get_color_at_index(color_index)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
# String representation of the provider
|
||||
def tostring()
|
||||
try
|
||||
var mode = self.cycle_period == 0 ? "manual" : "auto"
|
||||
var palette_size = self._get_palette_size()
|
||||
return f"ColorCycleColorProvider(palette_size={palette_size}, cycle_period={self.cycle_period}, mode={mode}, current_index={self.current_index})"
|
||||
except ..
|
||||
return "ColorCycleColorProvider(uninitialized)"
|
||||
end
|
||||
return f"ColorCycleColorProvider(palette_size={self._get_palette_size()}, cycle_period={self.cycle_period}, mode={self.cycle_period ? 'manual' :: 'auto'}, current_index={self.current_index})"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
return {'color_cycle': ColorCycleColorProvider}
|
||||
return {'color_cycle': ColorCycleColorProvider}
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
# IterationNumberProvider - ValueProvider that returns current sequence iteration number
|
||||
#
|
||||
# This provider returns the current iteration number (0-based) for the innermost
|
||||
# sequence context, or nil if not called within a sequence.
|
||||
#
|
||||
# The iteration number is tracked by the animation engine using a stack-based
|
||||
# approach to handle nested sequences properly.
|
||||
#
|
||||
# Usage:
|
||||
# set iteration = iteration_number()
|
||||
# animation pulse = pulsating_animation(color=red, period=2s)
|
||||
# pulse.opacity = iteration * 50 + 100 # Brightness increases with each iteration
|
||||
#
|
||||
# In sequences:
|
||||
# sequence demo {
|
||||
# repeat 5 times {
|
||||
# play pulse for 1s
|
||||
# # iteration will be 0, 1, 2, 3, 4 for each repeat
|
||||
# }
|
||||
# }
|
||||
|
||||
#@ solidify:IterationNumberProvider,weak
|
||||
class IterationNumberProvider : animation.value_provider
|
||||
# Static parameter definitions (no parameters needed)
|
||||
static var PARAMS = {}
|
||||
|
||||
# Produce the current iteration number from the animation engine
|
||||
#
|
||||
# @param name: string - Parameter name being requested (ignored)
|
||||
# @param time_ms: int - Current time in milliseconds (ignored)
|
||||
# @return int|nil - Current iteration number (0-based) or nil if not in sequence
|
||||
def produce_value(name, time_ms)
|
||||
# Get the current iteration number from the engine's sequence stack
|
||||
return self.engine.get_current_iteration_number()
|
||||
end
|
||||
|
||||
# String representation for debugging
|
||||
#
|
||||
# @return string - Human-readable description of the provider
|
||||
def tostring()
|
||||
var current_iteration = self.engine.get_current_iteration_number()
|
||||
if current_iteration != nil
|
||||
return f"IterationNumberProvider(current: {current_iteration})"
|
||||
else
|
||||
return "IterationNumberProvider(not_in_sequence)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {'iteration_number': IterationNumberProvider}
|
||||
@ -137,12 +137,13 @@ class OscillatorValueProvider : animation.value_provider
|
||||
elif form == animation.EASE_IN
|
||||
# Quadratic ease-in: starts slow, accelerates
|
||||
var t = tasmota.scale_uint(past_with_phase, 0, duration - 1, 0, 255) # 0..255
|
||||
var eased = (t * t) / 255 # t^2 scaled back to 0..255
|
||||
var eased = tasmota.scale_int(t * t, 0, 255 * 255, 0, 255) # t^2 scaled back to 0..255
|
||||
self.value = tasmota.scale_int(eased, 0, 255, min_value, max_value)
|
||||
elif form == animation.EASE_OUT
|
||||
# Quadratic ease-out: starts fast, decelerates
|
||||
var t = tasmota.scale_uint(past_with_phase, 0, duration - 1, 0, 255) # 0..255
|
||||
var eased = 255 - ((255 - t) * (255 - t)) / 255 # 1 - (1-t)^2 scaled to 0..255
|
||||
var inv_t = 255 - t
|
||||
var eased = 255 - tasmota.scale_int(inv_t * inv_t, 0, 255 * 255, 0, 255) # 1 - (1-t)^2 scaled to 0..255
|
||||
self.value = tasmota.scale_int(eased, 0, 255, min_value, max_value)
|
||||
elif form == animation.ELASTIC
|
||||
# Elastic easing: overshoots and oscillates like a spring
|
||||
@ -157,12 +158,12 @@ class OscillatorValueProvider : animation.value_provider
|
||||
var decay = tasmota.scale_uint(255 - t, 0, 255, 255, 32) # Exponential decay approximation
|
||||
var freq_angle = tasmota.scale_uint(t, 0, 255, 0, 32767 * 6) # High frequency oscillation
|
||||
var oscillation = tasmota.sine_int(freq_angle % 32767) # -4096 to 4096
|
||||
var elastic_offset = (oscillation * decay) / 4096 # Scale oscillation by decay
|
||||
var elastic_offset = tasmota.scale_int(oscillation * decay, -4096 * 255, 4096 * 255, -255, 255) # Scale oscillation by decay
|
||||
var base_progress = tasmota.scale_int(t, 0, 255, 0, max_value - min_value)
|
||||
self.value = min_value + base_progress + elastic_offset
|
||||
# Clamp to reasonable bounds to prevent extreme overshoots
|
||||
var value_range = max_value - min_value
|
||||
var max_overshoot = value_range / 4 # Allow 25% overshoot
|
||||
var max_overshoot = tasmota.scale_int(value_range, 0, 4, 0, 1) # Allow 25% overshoot
|
||||
if self.value > max_value + max_overshoot self.value = max_value + max_overshoot end
|
||||
if self.value < min_value - max_overshoot self.value = min_value - max_overshoot end
|
||||
end
|
||||
@ -174,15 +175,18 @@ class OscillatorValueProvider : animation.value_provider
|
||||
# Simplified bounce with 3 segments for better behavior
|
||||
if t < 128 # First big bounce (0-50% of time)
|
||||
var segment_t = tasmota.scale_uint(t, 0, 127, 0, 255)
|
||||
bounced_t = 255 - ((255 - segment_t) * (255 - segment_t)) / 255 # Ease-out curve
|
||||
var inv_segment = 255 - segment_t
|
||||
bounced_t = 255 - tasmota.scale_int(inv_segment * inv_segment, 0, 255 * 255, 0, 255) # Ease-out curve
|
||||
elif t < 192 # Second smaller bounce (50-75% of time)
|
||||
var segment_t = tasmota.scale_uint(t - 128, 0, 63, 0, 255)
|
||||
var bounce_val = 255 - ((255 - segment_t) * (255 - segment_t)) / 255
|
||||
bounced_t = (bounce_val * 128) / 255 # Scale to 50% height
|
||||
var inv_segment = 255 - segment_t
|
||||
var bounce_val = 255 - tasmota.scale_int(inv_segment * inv_segment, 0, 255 * 255, 0, 255)
|
||||
bounced_t = tasmota.scale_int(bounce_val, 0, 255, 0, 128) # Scale to 50% height
|
||||
else # Final settle (75-100% of time)
|
||||
var segment_t = tasmota.scale_uint(t - 192, 0, 63, 0, 255)
|
||||
var bounce_val = 255 - ((255 - segment_t) * (255 - segment_t)) / 255
|
||||
bounced_t = 255 - ((255 - bounce_val) * 64) / 255 # Settle towards full value
|
||||
var inv_segment = 255 - segment_t
|
||||
var bounce_val = 255 - tasmota.scale_int(inv_segment * inv_segment, 0, 255 * 255, 0, 255)
|
||||
bounced_t = 255 - tasmota.scale_int(255 - bounce_val, 0, 255, 0, 64) # Settle towards full value
|
||||
end
|
||||
|
||||
self.value = tasmota.scale_int(bounced_t, 0, 255, min_value, max_value)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,323 @@
|
||||
# DSL Berry Code Blocks Test Suite
|
||||
# Tests for berry code block functionality in SimpleDSLTranspiler
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota" lib/libesp32/berry_animation/src/tests/dsl_berry_code_blocks_test.be
|
||||
|
||||
import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test basic berry code block with triple braces
|
||||
def test_basic_berry_block_triple_braces()
|
||||
print("Testing basic berry code block with triple braces...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'var test_var = 42\n' +
|
||||
'print("Hello from berry block")\n' +
|
||||
'"""\n' +
|
||||
'color red_custom = 0xFF0000\n'
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
assert(string.find(berry_code, "# Berry code block") >= 0, "Should have berry code block comment")
|
||||
assert(string.find(berry_code, "var test_var = 42") >= 0, "Should include berry code verbatim")
|
||||
assert(string.find(berry_code, 'print("Hello from berry block")') >= 0, "Should include print statement")
|
||||
assert(string.find(berry_code, "# End berry code block") >= 0, "Should have end comment")
|
||||
assert(string.find(berry_code, "var red_custom_ = 0xFFFF0000") >= 0, "Should continue with DSL after berry block")
|
||||
|
||||
print("✓ Basic berry code block (triple braces) test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test basic berry code block with braces inside
|
||||
def test_basic_berry_block_with_braces()
|
||||
print("Testing basic berry code block with braces inside...")
|
||||
|
||||
var dsl_source = "berry '''\n" +
|
||||
"var test_var = 100\n" +
|
||||
"if test_var > 50\n" +
|
||||
" print('Value is greater than 50')\n" +
|
||||
"end\n" +
|
||||
"'''\n" +
|
||||
"color blue_custom = 0x0000FF\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
assert(string.find(berry_code, "# Berry code block") >= 0, "Should have berry code block comment")
|
||||
assert(string.find(berry_code, "var test_var = 100") >= 0, "Should include berry code verbatim")
|
||||
assert(string.find(berry_code, "print('Value is greater than 50')") >= 0, "Should include print statement")
|
||||
assert(string.find(berry_code, "# End berry code block") >= 0, "Should have end comment")
|
||||
|
||||
print("✓ Basic berry code block (single quotes) test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test multiple berry code blocks
|
||||
def test_multiple_berry_blocks()
|
||||
print("Testing multiple berry code blocks...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'var global_var = 50\n' +
|
||||
'def helper_function(x)\n' +
|
||||
' return x * 2\n' +
|
||||
'end\n' +
|
||||
'"""\n' +
|
||||
'color green_custom = 0x00FF00\n' +
|
||||
"berry '''\n" +
|
||||
"var result = helper_function(global_var)\n" +
|
||||
"print('Result:', result)\n" +
|
||||
"'''\n" +
|
||||
"set col = 0xFF8800"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
|
||||
# Check first berry block
|
||||
var first_block_pos = string.find(berry_code, "var global_var = 50")
|
||||
assert(first_block_pos >= 0, "Should include first berry block")
|
||||
|
||||
# Check second berry block
|
||||
var second_block_pos = string.find(berry_code, "var result = helper_function(global_var)")
|
||||
assert(second_block_pos >= 0, "Should include second berry block")
|
||||
assert(second_block_pos > first_block_pos, "Second block should come after first block")
|
||||
|
||||
# Check DSL continues to work
|
||||
assert(string.find(berry_code, "var green_custom_ = 0xFF00FF00") >= 0, "Should process DSL after berry blocks")
|
||||
|
||||
print("✓ Multiple berry code blocks test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test berry code block with complex content
|
||||
def test_complex_berry_content()
|
||||
print("Testing berry code block with complex content...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'import math\n' +
|
||||
'import string\n' +
|
||||
'\n' +
|
||||
'# Complex function with multiple features\n' +
|
||||
'def calculate_dynamic_value(base, factor)\n' +
|
||||
' var result = math.sin(factor) * base\n' +
|
||||
' return int(result + 100)\n' +
|
||||
'end\n' +
|
||||
'\n' +
|
||||
'var config = {\n' +
|
||||
' "brightness": 200,\n' +
|
||||
' "speed": 1.5\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'print("Complex berry block initialized")\n' +
|
||||
'"""\n' +
|
||||
'color purple_custom = 0x800080\n' +
|
||||
'animation pulse = pulsating_animation(color=purple_custom, period=2s)\n' +
|
||||
'run pulse'
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
assert(string.find(berry_code, "import math") >= 0, "Should include import statements")
|
||||
assert(string.find(berry_code, "import string") >= 0, "Should include multiple imports")
|
||||
assert(string.find(berry_code, "def calculate_dynamic_value(base, factor)") >= 0, "Should include function definition")
|
||||
assert(string.find(berry_code, 'var config = {') >= 0, "Should include complex data structures")
|
||||
assert(string.find(berry_code, '"brightness": 200,') >= 0, "Should preserve map syntax")
|
||||
assert(string.find(berry_code, 'print("Complex berry block initialized")') >= 0, "Should include print statement")
|
||||
|
||||
print("✓ Complex berry content test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test berry code block interacting with DSL objects
|
||||
def test_berry_dsl_interaction()
|
||||
print("Testing berry code block interaction with DSL objects...")
|
||||
|
||||
var dsl_source = 'color red_custom = 0xFF0000\n' +
|
||||
'animation test_anim = pulsating_animation(color=red_custom, period=3s)\n' +
|
||||
'berry """\n' +
|
||||
'# Modify DSL-generated animation\n' +
|
||||
'test_anim_.opacity = 150\n' +
|
||||
'test_anim_.priority = 10\n' +
|
||||
'print("Animation modified via berry code")\n' +
|
||||
'"""\n' +
|
||||
'run test_anim'
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
assert(string.find(berry_code, "var test_anim_ = animation.pulsating_animation(engine)") >= 0, "Should create animation")
|
||||
assert(string.find(berry_code, "test_anim_.opacity = 150") >= 0, "Should modify animation opacity")
|
||||
assert(string.find(berry_code, "test_anim_.priority = 10") >= 0, "Should modify animation priority")
|
||||
assert(string.find(berry_code, 'print("Animation modified via berry code")') >= 0, "Should include print statement")
|
||||
|
||||
print("✓ Berry-DSL interaction test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test berry code block with inline comments
|
||||
def test_berry_block_with_inline_comment()
|
||||
print("Testing berry code block with inline comment...")
|
||||
|
||||
var dsl_source = 'berry """ # This is an inline comment\n' +
|
||||
'var test_value = 123 # Berry code block # This is an inline comment\n' +
|
||||
'"""\n' +
|
||||
'color yellow_custom = 0xFFFF00'
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
assert(string.find(berry_code, "# Berry code block # This is an inline comment") >= 0, "Should preserve inline comment")
|
||||
assert(string.find(berry_code, "var test_value = 123") >= 0, "Should include berry code")
|
||||
|
||||
print("✓ Berry block with inline comment test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test error handling - missing string after berry keyword
|
||||
def test_error_missing_string()
|
||||
print("Testing error handling for missing string after berry keyword...")
|
||||
|
||||
var dsl_source = 'berry\n' +
|
||||
'print("This should cause an error")\n' +
|
||||
'color test_color = 0xFF0000'
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(false, "Should raise compilation error")
|
||||
except "dsl_compilation_error"
|
||||
# ok
|
||||
end
|
||||
|
||||
print("✓ Error handling (missing string) test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test error handling - invalid token after berry keyword
|
||||
def test_error_invalid_token()
|
||||
print("Testing error handling for invalid token after berry keyword...")
|
||||
|
||||
var dsl_source = 'berry 123\n' +
|
||||
'color test_color = 0xFF0000'
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(false, "Should raise compilation error")
|
||||
except "dsl_compilation_error"
|
||||
# ok
|
||||
end
|
||||
|
||||
print("✓ Error handling (invalid token) test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test execution of berry code blocks
|
||||
def test_berry_block_execution()
|
||||
print("Testing execution of berry code blocks...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'var execution_test = "Berry code executed successfully"\n' +
|
||||
'print(execution_test)\n' +
|
||||
'"""\n' +
|
||||
'color cyan_custom = 0x00FFFF\n' +
|
||||
'animation my_anim = solid(color=cyan_custom)'
|
||||
'run my_anim'
|
||||
|
||||
# Test compilation
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile successfully")
|
||||
|
||||
# Test execution (this will actually run the berry code)
|
||||
try
|
||||
animation_dsl.execute(dsl_source)
|
||||
print("✓ Berry block execution test passed")
|
||||
return true
|
||||
except .. as e, m
|
||||
print("✗ Berry block execution failed:", e, m)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Test berry code blocks with multiline strings and complex syntax
|
||||
def test_multiline_complex_syntax()
|
||||
print("Testing berry code blocks with multiline strings and complex syntax...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'# Test multiline strings and complex syntax\n' +
|
||||
'var multiline_string = "This is a\\n" +\n' +
|
||||
' "multiline string\\n" +\n' +
|
||||
' "with multiple lines"\n' +
|
||||
'\n' +
|
||||
'def complex_function(a, b, c)\n' +
|
||||
' if a > b\n' +
|
||||
' return a + c\n' +
|
||||
' else\n' +
|
||||
' return b + c\n' +
|
||||
' end\n' +
|
||||
'end\n' +
|
||||
'\n' +
|
||||
'var result = complex_function(10, 5, 3)\n' +
|
||||
'print("Complex function result:", result)\n' +
|
||||
'"""\n' +
|
||||
'color magenta_custom = 0xFF00FF\n'
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
assert(string.find(berry_code, 'var multiline_string = "This is a\\n"') >= 0, "Should preserve multiline string syntax")
|
||||
assert(string.find(berry_code, "def complex_function(a, b, c)") >= 0, "Should include function definition")
|
||||
assert(string.find(berry_code, "if a > b") >= 0, "Should include conditional logic")
|
||||
assert(string.find(berry_code, "var result = complex_function(10, 5, 3)") >= 0, "Should include function call")
|
||||
|
||||
print("✓ Multiline complex syntax test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_all_berry_block_tests()
|
||||
print("=== DSL Berry Code Blocks Test Suite ===")
|
||||
print("")
|
||||
|
||||
var tests = [
|
||||
test_basic_berry_block_triple_braces,
|
||||
test_basic_berry_block_with_braces,
|
||||
test_multiple_berry_blocks,
|
||||
test_complex_berry_content,
|
||||
test_berry_dsl_interaction,
|
||||
test_berry_block_with_inline_comment,
|
||||
test_error_missing_string,
|
||||
test_error_invalid_token,
|
||||
test_berry_block_execution,
|
||||
test_multiline_complex_syntax
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
var total = size(tests)
|
||||
|
||||
for test_func : tests
|
||||
try
|
||||
if test_func()
|
||||
passed += 1
|
||||
end
|
||||
except .. as e, m
|
||||
print("✗ Test failed with exception:", e, m)
|
||||
end
|
||||
print("")
|
||||
end
|
||||
|
||||
print("=== Berry Code Blocks Test Results ===")
|
||||
print(f"Passed: {passed}/{total}")
|
||||
|
||||
if passed == total
|
||||
print("All berry code block tests passed! ✓")
|
||||
return true
|
||||
else
|
||||
print("Some berry code block tests failed! ✗")
|
||||
raise "test_failed"
|
||||
end
|
||||
end
|
||||
|
||||
# Run the tests
|
||||
return run_all_berry_block_tests()
|
||||
@ -0,0 +1,313 @@
|
||||
# DSL Berry Code Blocks Integration Test Suite
|
||||
# Tests for real-world integration scenarios with berry code blocks
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota" lib/libesp32/berry_animation/src/tests/dsl_berry_integration_test.be
|
||||
|
||||
import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test berry code blocks with mathematical calculations
|
||||
def test_mathematical_integration()
|
||||
print("Testing berry code blocks with mathematical calculations...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'import math\n' +
|
||||
'def calculate_period(base_period, frequency)\n' +
|
||||
' return int(base_period / frequency)\n' +
|
||||
'end\n' +
|
||||
'def calculate_opacity(brightness_percent)\n' +
|
||||
' return int(brightness_percent * 2.55)\n' +
|
||||
'end\n' +
|
||||
'"""\n' +
|
||||
'color wave_color = 0x0080FF\n' +
|
||||
'animation wave1 = pulsating_animation(color=wave_color, period=2s)\n' +
|
||||
'animation wave2 = pulsating_animation(color=wave_color, period=3s)\n' +
|
||||
'berry """\n' +
|
||||
'wave1_.period = calculate_period(4000, 2.0) # 2000ms\n' +
|
||||
'wave1_.opacity = calculate_opacity(80) # 204\n' +
|
||||
'wave2_.period = calculate_period(6000, 1.5) # 4000ms\n' +
|
||||
'wave2_.opacity = calculate_opacity(60) # 153\n' +
|
||||
'"""\n' +
|
||||
'run wave1\n' +
|
||||
'run wave2'
|
||||
|
||||
# Test compilation
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile successfully")
|
||||
|
||||
# Verify mathematical functions are included
|
||||
assert(string.find(berry_code, "def calculate_period(base_period, frequency)") >= 0, "Should include period calculation function")
|
||||
assert(string.find(berry_code, "def calculate_opacity(brightness_percent)") >= 0, "Should include opacity calculation function")
|
||||
|
||||
# Verify function calls are included
|
||||
assert(string.find(berry_code, "wave1_.period = calculate_period(4000, 2.0)") >= 0, "Should include period calculation call")
|
||||
assert(string.find(berry_code, "wave1_.opacity = calculate_opacity(80)") >= 0, "Should include opacity calculation call")
|
||||
|
||||
# Test execution
|
||||
try
|
||||
animation_dsl.execute(dsl_source)
|
||||
print("✓ Mathematical integration test passed")
|
||||
return true
|
||||
except .. as e
|
||||
print("✗ Mathematical integration test failed:", e)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Test berry code blocks with configuration management
|
||||
def test_configuration_management()
|
||||
print("Testing berry code blocks with configuration management...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'# Global configuration object\n' +
|
||||
'var config = {\n' +
|
||||
' "brightness": 200,\n' +
|
||||
' "speed_multiplier": 1.5,\n' +
|
||||
' "color_intensity": 0.8,\n' +
|
||||
' "debug": true\n' +
|
||||
'}\n' +
|
||||
'def log_debug(message)\n' +
|
||||
' if config["debug"]\n' +
|
||||
' print("[DEBUG]", message)\n' +
|
||||
' end\n' +
|
||||
'end\n' +
|
||||
'def apply_config_to_animation(anim, name)\n' +
|
||||
' anim.opacity = config["brightness"]\n' +
|
||||
' log_debug("Applied config to " + name)\n' +
|
||||
'end\n' +
|
||||
'"""\n' +
|
||||
'color fire_red = 0xFF4500\n' +
|
||||
'color ocean_blue = 0x006994\n' +
|
||||
'animation fire_anim = pulsating_animation(color=fire_red, period=2s)\n' +
|
||||
'animation ocean_anim = pulsating_animation(color=ocean_blue, period=3s)\n' +
|
||||
'berry """\n' +
|
||||
'apply_config_to_animation(fire_anim_, "fire_animation")\n' +
|
||||
'apply_config_to_animation(ocean_anim_, "ocean_animation")\n' +
|
||||
'log_debug("Configuration applied to all animations")\n' +
|
||||
'"""\n' +
|
||||
'run fire_anim\n' +
|
||||
'run ocean_anim'
|
||||
|
||||
# Test compilation
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile successfully")
|
||||
|
||||
# Verify configuration object is included
|
||||
assert(string.find(berry_code, 'var config = {') >= 0, "Should include config object")
|
||||
assert(string.find(berry_code, '"brightness": 200,') >= 0, "Should include brightness setting")
|
||||
|
||||
# Verify helper functions are included
|
||||
assert(string.find(berry_code, "def log_debug(message)") >= 0, "Should include debug function")
|
||||
assert(string.find(berry_code, "def apply_config_to_animation(anim, name)") >= 0, "Should include config application function")
|
||||
|
||||
# Test execution
|
||||
try
|
||||
animation_dsl.execute(dsl_source)
|
||||
print("✓ Configuration management test passed")
|
||||
return true
|
||||
except .. as e
|
||||
print("✗ Configuration management test failed:", e)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Test berry code blocks with dynamic animation creation
|
||||
def test_dynamic_animation_creation()
|
||||
print("Testing berry code blocks with dynamic animation creation...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'var animation_counter = 0\n' +
|
||||
'def create_numbered_animation(base_color, period_ms)\n' +
|
||||
' animation_counter += 1\n' +
|
||||
' var anim = animation.pulsating_animation(engine)\n' +
|
||||
' anim.color = base_color\n' +
|
||||
' anim.period = period_ms\n' +
|
||||
' anim.priority = animation_counter\n' +
|
||||
' engine.add(anim)\n' +
|
||||
' print("Created animation #" + str(animation_counter))\n' +
|
||||
' return anim\n' +
|
||||
'end\n' +
|
||||
'"""\n' +
|
||||
'color red_custom = 0xFF0000\n' +
|
||||
'color green_custom = 0x00FF00\n' +
|
||||
'color blue_custom = 0x0000FF\n' +
|
||||
'berry """\n' +
|
||||
'create_numbered_animation(0xFFFF0000, 2000) # Red\n' +
|
||||
'create_numbered_animation(0xFF00FF00, 2500) # Green\n' +
|
||||
'create_numbered_animation(0xFF0000FF, 3000) # Blue\n' +
|
||||
'print("Created " + str(animation_counter) + " dynamic animations")\n' +
|
||||
'"""'
|
||||
|
||||
# Test compilation
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile successfully")
|
||||
|
||||
# Verify dynamic creation function is included
|
||||
assert(string.find(berry_code, "def create_numbered_animation(base_color, period_ms)") >= 0, "Should include dynamic creation function")
|
||||
assert(string.find(berry_code, "var anim = animation.pulsating_animation(engine)") >= 0, "Should create animations dynamically")
|
||||
assert(string.find(berry_code, "engine.add(anim)") >= 0, "Should add animations to engine")
|
||||
|
||||
# Test execution
|
||||
try
|
||||
animation_dsl.execute(dsl_source)
|
||||
print("✓ Dynamic animation creation test passed")
|
||||
return true
|
||||
except .. as e
|
||||
print("✗ Dynamic animation creation test failed:", e)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Test berry code blocks with state management
|
||||
def test_state_management()
|
||||
print("Testing berry code blocks with state management...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'# State management system\n' +
|
||||
'var state = {\n' +
|
||||
' "current_mode": "normal",\n' +
|
||||
' "brightness_level": 255,\n' +
|
||||
' "animation_count": 0\n' +
|
||||
'}\n' +
|
||||
'def set_mode(mode)\n' +
|
||||
' state["current_mode"] = mode\n' +
|
||||
' print("Mode changed to:", mode)\n' +
|
||||
'end\n' +
|
||||
'def get_brightness_for_mode()\n' +
|
||||
' if state["current_mode"] == "dim"\n' +
|
||||
' return 100\n' +
|
||||
' elif state["current_mode"] == "bright"\n' +
|
||||
' return 255\n' +
|
||||
' else\n' +
|
||||
' return 180\n' +
|
||||
' end\n' +
|
||||
'end\n' +
|
||||
'"""\n' +
|
||||
'color status_color = 0x00FFFF\n' +
|
||||
'animation status_anim = pulsating_animation(color=status_color, period=2s)\n' +
|
||||
'berry """\n' +
|
||||
'set_mode("bright")\n' +
|
||||
'status_anim_.opacity = get_brightness_for_mode()\n' +
|
||||
'state["animation_count"] = 1\n' +
|
||||
'print("State:", state)\n' +
|
||||
'"""\n' +
|
||||
'run status_anim'
|
||||
|
||||
# Test compilation
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile successfully")
|
||||
|
||||
# Verify state management is included
|
||||
assert(string.find(berry_code, 'var state = {') >= 0, "Should include state object")
|
||||
assert(string.find(berry_code, "def set_mode(mode)") >= 0, "Should include mode setter")
|
||||
assert(string.find(berry_code, "def get_brightness_for_mode()") >= 0, "Should include brightness getter")
|
||||
|
||||
# Test execution
|
||||
try
|
||||
animation_dsl.execute(dsl_source)
|
||||
print("✓ State management test passed")
|
||||
return true
|
||||
except .. as e
|
||||
print("✗ State management test failed:", e)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Test berry code blocks with error handling
|
||||
def test_error_handling_integration()
|
||||
print("Testing berry code blocks with error handling...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'def safe_divide(a, b)\n' +
|
||||
' try\n' +
|
||||
' return a / b\n' +
|
||||
' except .. as e\n' +
|
||||
' print("Division error:", e)\n' +
|
||||
' return 1.0\n' +
|
||||
' end\n' +
|
||||
'end\n' +
|
||||
'def safe_set_period(anim, period)\n' +
|
||||
' try\n' +
|
||||
' if period > 0\n' +
|
||||
' anim.period = period\n' +
|
||||
' print("Period set to:", period)\n' +
|
||||
' else\n' +
|
||||
' print("Invalid period, using default")\n' +
|
||||
' anim.period = 2000\n' +
|
||||
' end\n' +
|
||||
' except .. as e\n' +
|
||||
' print("Error setting period:", e)\n' +
|
||||
' end\n' +
|
||||
'end\n' +
|
||||
'"""\n' +
|
||||
'color safe_color = 0xFF8000\n' +
|
||||
'animation safe_anim = pulsating_animation(color=safe_color, period=1s)\n' +
|
||||
'berry """\n' +
|
||||
'var calculated_period = safe_divide(4000, 2) # Should work\n' +
|
||||
'safe_set_period(safe_anim_, int(calculated_period * 1000))\n' +
|
||||
'"""\n' +
|
||||
'run safe_anim'
|
||||
|
||||
# Test compilation
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile successfully")
|
||||
|
||||
# Verify error handling functions are included
|
||||
assert(string.find(berry_code, "def safe_divide(a, b)") >= 0, "Should include safe division function")
|
||||
assert(string.find(berry_code, "def safe_set_period(anim, period)") >= 0, "Should include safe period setter")
|
||||
assert(string.find(berry_code, "try") >= 0, "Should include try-catch blocks")
|
||||
|
||||
# Test execution
|
||||
try
|
||||
animation_dsl.execute(dsl_source)
|
||||
print("✓ Error handling integration test passed")
|
||||
return true
|
||||
except .. as e
|
||||
print("✗ Error handling integration test failed:", e)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Run all integration tests
|
||||
def run_all_berry_integration_tests()
|
||||
print("=== DSL Berry Code Blocks Integration Test Suite ===")
|
||||
print("")
|
||||
|
||||
var tests = [
|
||||
test_mathematical_integration,
|
||||
test_configuration_management,
|
||||
test_dynamic_animation_creation,
|
||||
test_state_management,
|
||||
test_error_handling_integration
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
var total = size(tests)
|
||||
|
||||
for test_func : tests
|
||||
try
|
||||
if test_func()
|
||||
passed += 1
|
||||
end
|
||||
except .. as e
|
||||
print("✗ Test failed with exception:", e)
|
||||
end
|
||||
print("")
|
||||
end
|
||||
|
||||
print("=== Berry Integration Test Results ===")
|
||||
print(f"Passed: {passed}/{total}")
|
||||
|
||||
if passed == total
|
||||
print("All berry integration tests passed! ✓")
|
||||
return true
|
||||
else
|
||||
print("Some berry integration tests failed! ✗")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Run the tests
|
||||
return run_all_berry_integration_tests()
|
||||
@ -366,8 +366,8 @@ def test_reserved_name_validation()
|
||||
"color red_custom = 0x800000",
|
||||
"color smooth_custom = 0x808080",
|
||||
# Easing function names are now valid as user-defined names
|
||||
"animation smooth = solid(color=blue)",
|
||||
"animation linear = solid(color=green)"
|
||||
"animation smooth2 = solid(color=blue)",
|
||||
"animation linear2 = solid(color=green)"
|
||||
]
|
||||
|
||||
for dsl_input : valid_name_tests
|
||||
|
||||
@ -0,0 +1,216 @@
|
||||
# DSL Lexer Triple Quotes Test Suite
|
||||
# Tests for triple-quoted string tokenization in DSLLexer
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota" lib/libesp32/berry_animation/src/tests/dsl_lexer_triple_quotes_test.be
|
||||
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test basic triple-quoted string tokenization with double quotes
|
||||
def test_triple_quotes_double()
|
||||
print("Testing triple-quoted string tokenization (triple quotes)...")
|
||||
|
||||
var source = 'berry """\nHello World\n"""'
|
||||
var lexer = animation_dsl.DSLLexer(source)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
assert(size(tokens) >= 3, "Should have at least 3 tokens (berry, string, EOF)")
|
||||
assert(tokens[0].type == animation_dsl.Token.KEYWORD, "First token should be KEYWORD")
|
||||
assert(tokens[0].value == "berry", "First token should be 'berry'")
|
||||
assert(tokens[1].type == animation_dsl.Token.STRING, "Second token should be STRING")
|
||||
assert(tokens[1].value == "\nHello World\n", "String content should be preserved")
|
||||
|
||||
print("✓ Triple-quoted string (double quotes) tokenization test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test basic triple-quoted string tokenization with single quotes
|
||||
def test_triple_quotes_single()
|
||||
print("Testing triple-quoted string tokenization (single quotes)...")
|
||||
|
||||
var source = "berry '''\nHello World\n'''"
|
||||
var lexer = animation_dsl.DSLLexer(source)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
assert(size(tokens) >= 3, "Should have at least 3 tokens (berry, string, EOF)")
|
||||
assert(tokens[0].type == animation_dsl.Token.KEYWORD, "First token should be KEYWORD")
|
||||
assert(tokens[0].value == "berry", "First token should be 'berry'")
|
||||
assert(tokens[1].type == animation_dsl.Token.STRING, "Second token should be STRING")
|
||||
assert(tokens[1].value == "\nHello World\n", "String content should be preserved")
|
||||
|
||||
print("✓ Triple-quoted string (single quotes) tokenization test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test multiline triple-quoted string
|
||||
def test_multiline_triple_quotes()
|
||||
print("Testing multiline triple-quoted string tokenization...")
|
||||
|
||||
var source = 'berry """\nLine 1\nLine 2\nLine 3\n"""'
|
||||
var lexer = animation_dsl.DSLLexer(source)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
assert(size(tokens) >= 3, "Should have at least 3 tokens")
|
||||
assert(tokens[1].type == animation_dsl.Token.STRING, "Second token should be STRING")
|
||||
|
||||
var expected_content = "\nLine 1\nLine 2\nLine 3\n"
|
||||
assert(tokens[1].value == expected_content, f"String content should be '{expected_content}', got '{tokens[1].value}'")
|
||||
|
||||
print("✓ Multiline triple-quoted string tokenization test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test triple-quoted string with embedded quotes
|
||||
def test_embedded_quotes()
|
||||
print("Testing triple-quoted string with embedded quotes...")
|
||||
|
||||
var source = 'berry """\nprint("Hello")\nvar x = \'world\'\n"""'
|
||||
var lexer = animation_dsl.DSLLexer(source)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
assert(size(tokens) >= 3, "Should have at least 3 tokens")
|
||||
assert(tokens[1].type == animation_dsl.Token.STRING, "Second token should be STRING")
|
||||
|
||||
var expected_content = '\nprint("Hello")\nvar x = \'world\'\n'
|
||||
assert(tokens[1].value == expected_content, f"String content should preserve embedded quotes")
|
||||
|
||||
print("✓ Embedded quotes in triple-quoted string test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test empty triple-quoted string
|
||||
def test_empty_triple_quotes()
|
||||
print("Testing empty triple-quoted string...")
|
||||
|
||||
var source = 'berry """"""'
|
||||
var lexer = animation_dsl.DSLLexer(source)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
assert(size(tokens) >= 3, "Should have at least 3 tokens")
|
||||
assert(tokens[1].type == animation_dsl.Token.STRING, "Second token should be STRING")
|
||||
assert(tokens[1].value == "", "Empty string should have empty value")
|
||||
|
||||
print("✓ Empty triple-quoted string test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test unterminated triple-quoted string (error case)
|
||||
def test_unterminated_triple_quotes()
|
||||
print("Testing unterminated triple-quoted string...")
|
||||
|
||||
var source = 'berry """\nUnterminated string'
|
||||
var lexer = animation_dsl.DSLLexer(source)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
# Should generate an error token
|
||||
var has_error = false
|
||||
for token : tokens
|
||||
if token.type == animation_dsl.Token.ERROR
|
||||
has_error = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
assert(has_error, "Should generate an ERROR token for unterminated string")
|
||||
|
||||
print("✓ Unterminated triple-quoted string test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test triple-quoted string with complex content
|
||||
def test_complex_content()
|
||||
print("Testing triple-quoted string with complex content...")
|
||||
|
||||
var source = 'berry """\n' +
|
||||
'import math\n' +
|
||||
'def func(x)\n' +
|
||||
' return x * 2\n' +
|
||||
'end\n' +
|
||||
'var result = func(21)\n' +
|
||||
'print("Result:", result)\n' +
|
||||
'"""'
|
||||
|
||||
var lexer = animation_dsl.DSLLexer(source)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
assert(size(tokens) >= 3, "Should have at least 3 tokens")
|
||||
assert(tokens[1].type == animation_dsl.Token.STRING, "Second token should be STRING")
|
||||
|
||||
var content = tokens[1].value
|
||||
assert(string.find(content, "import math") >= 0, "Should contain import statement")
|
||||
assert(string.find(content, "def func(x)") >= 0, "Should contain function definition")
|
||||
assert(string.find(content, "return x * 2") >= 0, "Should contain function body")
|
||||
assert(string.find(content, 'print("Result:", result)') >= 0, "Should contain print statement")
|
||||
|
||||
print("✓ Complex content triple-quoted string test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test mixed quote types (should not interfere)
|
||||
def test_mixed_quote_types()
|
||||
print("Testing that triple quotes don't interfere with regular quotes...")
|
||||
|
||||
var source = 'color red = 0xFF0000\nberry """\ntest\n"""\nvar x = "normal string"'
|
||||
var lexer = animation_dsl.DSLLexer(source)
|
||||
var tokens = lexer.tokenize()
|
||||
|
||||
# Find the normal string token
|
||||
var normal_string_found = false
|
||||
for token : tokens
|
||||
if token.type == animation_dsl.Token.STRING && token.value == "normal string"
|
||||
normal_string_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
assert(normal_string_found, "Should still tokenize normal strings correctly")
|
||||
|
||||
print("✓ Mixed quote types test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all lexer triple quotes tests
|
||||
def run_all_lexer_triple_quotes_tests()
|
||||
print("=== DSL Lexer Triple Quotes Test Suite ===")
|
||||
print("")
|
||||
|
||||
var tests = [
|
||||
test_triple_quotes_double,
|
||||
test_triple_quotes_single,
|
||||
test_multiline_triple_quotes,
|
||||
test_embedded_quotes,
|
||||
test_empty_triple_quotes,
|
||||
test_unterminated_triple_quotes,
|
||||
test_complex_content,
|
||||
test_mixed_quote_types
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
var total = size(tests)
|
||||
|
||||
for test_func : tests
|
||||
try
|
||||
if test_func()
|
||||
passed += 1
|
||||
end
|
||||
except .. as e, m
|
||||
print("✗ Test failed with exception:", e, m)
|
||||
end
|
||||
print("")
|
||||
end
|
||||
|
||||
print("=== Lexer Triple Quotes Test Results ===")
|
||||
print(f"Passed: {passed}/{total}")
|
||||
|
||||
if passed == total
|
||||
print("All lexer triple quotes tests passed! ✓")
|
||||
return true
|
||||
else
|
||||
print("Some lexer triple quotes tests failed! ✗")
|
||||
raise "test_failed"
|
||||
end
|
||||
end
|
||||
|
||||
# Run the tests
|
||||
return run_all_lexer_triple_quotes_tests()
|
||||
@ -214,7 +214,7 @@ class DSLParameterValidationTest
|
||||
end
|
||||
|
||||
# Check that the generated code contains the expected object reference
|
||||
if string.find(berry_code, "self.resolve(red_eye_, 'pos')") == -1
|
||||
if string.find(berry_code, "animation.resolve(red_eye_, 'pos')") == -1
|
||||
raise "generation_error", "Generated code should contain object property reference"
|
||||
end
|
||||
end
|
||||
@ -336,7 +336,7 @@ class DSLParameterValidationTest
|
||||
end
|
||||
|
||||
# Check that the generated code contains the expected computed expression
|
||||
if string.find(berry_code, "self.resolve(strip_len_) - self.resolve(red_eye_, 'pos')") == -1
|
||||
if string.find(berry_code, "animation.resolve(strip_len_) - animation.resolve(red_eye_, 'pos')") == -1
|
||||
raise "generation_error", "Generated code should contain computed object property reference"
|
||||
end
|
||||
end
|
||||
@ -383,7 +383,7 @@ class DSLParameterValidationTest
|
||||
return true
|
||||
else
|
||||
print("✗ Some DSL parameter validation tests failed!")
|
||||
return false
|
||||
raise "test_failed"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# DSL Reset/Restart Test Suite
|
||||
# Tests for reset and restart functionality in sequences
|
||||
# DSL Restart Test Suite
|
||||
# Tests for restart functionality in sequences
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota def log(x) print(x) end" lib/libesp32/berry_animation/src/tests/dsl_reset_restart_test.be
|
||||
@ -8,32 +8,6 @@ import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test basic reset functionality
|
||||
def test_reset_basic()
|
||||
print("Testing basic reset functionality...")
|
||||
|
||||
var dsl_source = "set osc_val = triangle(min_value=0, max_value=10, duration=2s)\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset osc_val\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for reset")
|
||||
assert(string.find(berry_code, "animation.triangle(engine)") >= 0, "Should generate triangle oscillator")
|
||||
assert(string.find(berry_code, "push_closure_step") >= 0, "Should generate closure step for reset")
|
||||
assert(string.find(berry_code, "osc_val_.start(engine.time_ms)") >= 0, "Should call start() method")
|
||||
|
||||
print("✓ Basic reset test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test basic restart functionality
|
||||
def test_restart_basic()
|
||||
print("Testing basic restart functionality...")
|
||||
@ -60,9 +34,9 @@ def test_restart_basic()
|
||||
return true
|
||||
end
|
||||
|
||||
# Test reset/restart with different value provider types
|
||||
def test_reset_restart_different_providers()
|
||||
print("Testing reset/restart with different value provider types...")
|
||||
# Test restart with different value provider types
|
||||
def test_restart_different_providers()
|
||||
print("Testing restart with different value provider types...")
|
||||
|
||||
var dsl_source = "set triangle_val = triangle(min_value=0, max_value=29, duration=5s)\n" +
|
||||
"set cosine_val = cosine_osc(min_value=0, max_value=29, duration=5s)\n" +
|
||||
@ -71,11 +45,11 @@ def test_reset_restart_different_providers()
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 500ms\n" +
|
||||
" reset triangle_val\n" +
|
||||
" restart triangle_val\n" +
|
||||
" wait 200ms\n" +
|
||||
" restart cosine_val\n" +
|
||||
" wait 200ms\n" +
|
||||
" reset sine_val\n" +
|
||||
" restart sine_val\n" +
|
||||
" play test_anim for 500ms\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
@ -84,11 +58,11 @@ def test_reset_restart_different_providers()
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for multiple providers")
|
||||
assert(string.find(berry_code, "triangle_val_.start(engine.time_ms)") >= 0, "Should reset triangle")
|
||||
assert(string.find(berry_code, "triangle_val_.start(engine.time_ms)") >= 0, "Should restart triangle")
|
||||
assert(string.find(berry_code, "cosine_val_.start(engine.time_ms)") >= 0, "Should restart cosine")
|
||||
assert(string.find(berry_code, "sine_val_.start(engine.time_ms)") >= 0, "Should reset sine")
|
||||
assert(string.find(berry_code, "sine_val_.start(engine.time_ms)") >= 0, "Should restart sine")
|
||||
|
||||
# Count the number of closure steps - should be 3 (one for each reset/restart)
|
||||
# Count the number of closure steps - should be 3 (one for each restart)
|
||||
var closure_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
@ -97,15 +71,15 @@ def test_reset_restart_different_providers()
|
||||
closure_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(closure_count == 3, f"Should have 3 closure steps for reset/restart, found {closure_count}")
|
||||
assert(closure_count == 3, f"Should have 3 closure steps for estart, found {closure_count}")
|
||||
|
||||
print("✓ Different providers test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test reset/restart with animations
|
||||
def test_reset_restart_animations()
|
||||
print("Testing reset/restart with animations...")
|
||||
# Test restart with animations
|
||||
def test_restart_animations()
|
||||
print("Testing restart with animations...")
|
||||
|
||||
var dsl_source = "set osc_val = triangle(min_value=0, max_value=10, duration=2s)\n" +
|
||||
"animation pulse_anim = pulsating_animation(color=red, period=3s)\n" +
|
||||
@ -113,7 +87,7 @@ def test_reset_restart_animations()
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play pulse_anim for 1s\n" +
|
||||
" reset pulse_anim\n" +
|
||||
" restart pulse_anim\n" +
|
||||
" play solid_anim for 1s\n" +
|
||||
" restart solid_anim\n" +
|
||||
" play pulse_anim for 1s\n" +
|
||||
@ -123,11 +97,11 @@ def test_reset_restart_animations()
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for animation reset/restart")
|
||||
assert(string.find(berry_code, "pulse_anim_.start(engine.time_ms)") >= 0, "Should reset pulse animation")
|
||||
assert(berry_code != nil, "Should generate Berry code for animation restart")
|
||||
assert(string.find(berry_code, "pulse_anim_.start(engine.time_ms)") >= 0, "Should restart pulse animation")
|
||||
assert(string.find(berry_code, "solid_anim_.start(engine.time_ms)") >= 0, "Should restart solid animation")
|
||||
|
||||
# Count the number of closure steps - should be 2 (one for each reset/restart)
|
||||
# Count the number of closure steps - should be 2 (one for each restart)
|
||||
var closure_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
@ -136,15 +110,15 @@ def test_reset_restart_animations()
|
||||
closure_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(closure_count == 2, f"Should have 2 closure steps for animation reset/restart, found {closure_count}")
|
||||
assert(closure_count == 2, f"Should have 2 closure steps for animation restart, found {closure_count}")
|
||||
|
||||
print("✓ Animation reset/restart test passed")
|
||||
print("✓ Animation restart test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test reset/restart in repeat blocks
|
||||
def test_reset_restart_in_repeat()
|
||||
print("Testing reset/restart in repeat blocks...")
|
||||
# Test restart in repeat blocks
|
||||
def test_restart_in_repeat()
|
||||
print("Testing restart in repeat blocks...")
|
||||
|
||||
var dsl_source = "set osc_val = triangle(min_value=0, max_value=10, duration=1s)\n" +
|
||||
"animation test_anim = solid(color=yellow)\n" +
|
||||
@ -152,7 +126,7 @@ def test_reset_restart_in_repeat()
|
||||
"sequence demo {\n" +
|
||||
" repeat 3 times {\n" +
|
||||
" play test_anim for 500ms\n" +
|
||||
" reset osc_val\n" +
|
||||
" restart osc_val\n" +
|
||||
" wait 200ms\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
@ -161,11 +135,11 @@ def test_reset_restart_in_repeat()
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for reset in repeat")
|
||||
assert(berry_code != nil, "Should generate Berry code for restart in repeat")
|
||||
assert(string.find(berry_code, "push_repeat_subsequence") >= 0, "Should generate repeat block")
|
||||
assert(string.find(berry_code, "osc_val_.start(engine.time_ms)") >= 0, "Should reset in repeat block")
|
||||
assert(string.find(berry_code, "osc_val_.start(engine.time_ms)") >= 0, "Should restart in repeat block")
|
||||
|
||||
print("✓ Reset/restart in repeat test passed")
|
||||
print("✓ Restart in repeat test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
@ -177,7 +151,7 @@ def test_error_undefined_provider()
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset undefined_provider\n" +
|
||||
" restart undefined_provider\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
@ -203,7 +177,7 @@ def test_error_non_value_provider()
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset my_color\n" +
|
||||
" restart my_color\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
@ -226,9 +200,10 @@ def test_error_animation_not_provider()
|
||||
|
||||
var dsl_source = "animation my_anim = solid(color=blue)\n" +
|
||||
"\n" +
|
||||
"set myvar = 2\n"
|
||||
"sequence demo {\n" +
|
||||
" play my_anim for 1s\n" +
|
||||
" restart my_anim\n" +
|
||||
" restart myvar\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
@ -254,7 +229,7 @@ def test_error_variable_not_provider()
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset my_var\n" +
|
||||
" restart my_var\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
@ -271,11 +246,11 @@ def test_error_variable_not_provider()
|
||||
return true
|
||||
end
|
||||
|
||||
# Test complex scenario with multiple resets/restarts
|
||||
# Test complex scenario with multiple restarts
|
||||
def test_complex_scenario()
|
||||
print("Testing complex scenario with multiple resets/restarts...")
|
||||
print("Testing complex scenario with multiple restarts...")
|
||||
|
||||
var dsl_source = "# Complex cylon eye with reset functionality\n" +
|
||||
var dsl_source = "# Complex cylon eye with restart functionality\n" +
|
||||
"set strip_len = strip_length()\n" +
|
||||
"palette eye_palette = [ red, yellow, green, violet ]\n" +
|
||||
"color eye_color = color_cycle(palette=eye_palette, cycle_period=0)\n" +
|
||||
@ -293,7 +268,7 @@ def test_complex_scenario()
|
||||
"sequence cylon_eye {\n" +
|
||||
" play red_eye for 3s\n" +
|
||||
" red_eye.pos = triangle_val\n" +
|
||||
" reset triangle_val\n" +
|
||||
" restart triangle_val\n" +
|
||||
" play red_eye for 3s\n" +
|
||||
" red_eye.pos = cosine_val\n" +
|
||||
" restart cosine_val\n" +
|
||||
@ -305,10 +280,10 @@ def test_complex_scenario()
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should compile complex scenario")
|
||||
assert(string.find(berry_code, "triangle_val_.start(engine.time_ms)") >= 0, "Should reset triangle_val")
|
||||
assert(string.find(berry_code, "triangle_val_.start(engine.time_ms)") >= 0, "Should restart triangle_val")
|
||||
assert(string.find(berry_code, "cosine_val_.start(engine.time_ms)") >= 0, "Should restart cosine_val")
|
||||
|
||||
# Should have multiple closure steps: 2 assignments + 2 resets + 1 color advance = 5 total
|
||||
# Should have multiple closure steps: 2 assignments + 2 restarts + 1 color advance = 5 total
|
||||
var closure_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
@ -323,16 +298,16 @@ def test_complex_scenario()
|
||||
return true
|
||||
end
|
||||
|
||||
# Test that reset/restart works with comments
|
||||
def test_reset_restart_with_comments()
|
||||
print("Testing reset/restart with comments...")
|
||||
# Test that restart works with comments
|
||||
def test_restart_with_comments()
|
||||
print("Testing restart with comments...")
|
||||
|
||||
var dsl_source = "set osc_val = triangle(min_value=0, max_value=10, duration=2s) # Triangle oscillator\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset osc_val # Reset the oscillator\n" +
|
||||
" restart osc_val # Restart the oscillator\n" +
|
||||
" restart osc_val # Restart it again\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
"}\n" +
|
||||
@ -342,10 +317,10 @@ def test_reset_restart_with_comments()
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code with comments")
|
||||
assert(string.find(berry_code, "# Reset the oscillator") >= 0, "Should preserve reset comment")
|
||||
assert(string.find(berry_code, "# Restart the oscillator") >= 0, "Should preserve restart comment")
|
||||
assert(string.find(berry_code, "# Restart it again") >= 0, "Should preserve restart comment")
|
||||
|
||||
# Should have 2 closure steps for reset and restart
|
||||
# Should have 2 closure steps for restart
|
||||
var closure_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
@ -356,42 +331,40 @@ def test_reset_restart_with_comments()
|
||||
end
|
||||
assert(closure_count == 2, f"Should have 2 closure steps, found {closure_count}")
|
||||
|
||||
print("✓ Reset/restart with comments test passed")
|
||||
print("✓ Restart with comments test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_all_reset_restart_tests()
|
||||
print("Starting DSL Reset/Restart Tests...")
|
||||
def run_all_restart_tests()
|
||||
print("Starting DSL Restart Tests...")
|
||||
|
||||
test_reset_basic()
|
||||
test_restart_basic()
|
||||
test_reset_restart_different_providers()
|
||||
test_reset_restart_in_repeat()
|
||||
test_restart_different_providers()
|
||||
test_restart_in_repeat()
|
||||
test_error_undefined_provider()
|
||||
test_error_non_value_provider()
|
||||
test_error_animation_not_provider()
|
||||
test_error_variable_not_provider()
|
||||
test_complex_scenario()
|
||||
test_reset_restart_with_comments()
|
||||
test_restart_with_comments()
|
||||
|
||||
print("\n🎉 All DSL Reset/Restart tests passed!")
|
||||
print("\n🎉 All DSL Restart tests passed!")
|
||||
return true
|
||||
end
|
||||
|
||||
# Execute tests
|
||||
run_all_reset_restart_tests()
|
||||
run_all_restart_tests()
|
||||
|
||||
return {
|
||||
"run_all_reset_restart_tests": run_all_reset_restart_tests,
|
||||
"test_reset_basic": test_reset_basic,
|
||||
"run_all_estart_tests": run_all_restart_tests,
|
||||
"test_restart_basic": test_restart_basic,
|
||||
"test_reset_restart_different_providers": test_reset_restart_different_providers,
|
||||
"test_reset_restart_in_repeat": test_reset_restart_in_repeat,
|
||||
"test_restart_different_providers": test_restart_different_providers,
|
||||
"test_restart_in_repeat": test_restart_in_repeat,
|
||||
"test_error_undefined_provider": test_error_undefined_provider,
|
||||
"test_error_non_value_provider": test_error_non_value_provider,
|
||||
"test_error_animation_not_provider": test_error_animation_not_provider,
|
||||
"test_error_variable_not_provider": test_error_variable_not_provider,
|
||||
"test_complex_scenario": test_complex_scenario,
|
||||
"test_reset_restart_with_comments": test_reset_restart_with_comments
|
||||
"test_restart_with_comments": test_restart_with_comments
|
||||
}
|
||||
@ -603,15 +603,14 @@ def test_error_handling()
|
||||
# Expected behavior
|
||||
end
|
||||
|
||||
# Test undefined references - simplified transpiler uses runtime resolution
|
||||
# Test undefined references - should raise exception
|
||||
var undefined_ref_dsl = "animation test = undefined_pattern"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(undefined_ref_dsl)
|
||||
# Simplified transpiler uses runtime resolution, so this should compile
|
||||
assert(berry_code != nil, "Should compile with runtime resolution")
|
||||
assert(false, "Should have raised exception for undefined identifier")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(false, "Should not raise exception for undefined references: " + msg)
|
||||
# Expected behavior - undefined identifiers should raise exceptions
|
||||
end
|
||||
|
||||
print("✓ Error handling test passed")
|
||||
@ -1114,6 +1113,141 @@ def test_invalid_sequence_commands()
|
||||
return true
|
||||
end
|
||||
|
||||
# Test template-only transpilation
|
||||
def test_template_only_transpilation()
|
||||
print("Testing template-only transpilation...")
|
||||
|
||||
# Test single template definition
|
||||
var single_template_dsl = "template pulse_effect {\n" +
|
||||
" param base_color type color\n" +
|
||||
" param duration\n" +
|
||||
" param brightness type number\n" +
|
||||
" \n" +
|
||||
" animation pulse = pulsating_animation(\n" +
|
||||
" color=base_color\n" +
|
||||
" period=duration\n" +
|
||||
" )\n" +
|
||||
" pulse.opacity = brightness\n" +
|
||||
" run pulse\n" +
|
||||
"}"
|
||||
|
||||
var single_code = animation_dsl.compile(single_template_dsl)
|
||||
assert(single_code != nil, "Should compile single template")
|
||||
|
||||
# Should NOT contain engine initialization
|
||||
assert(string.find(single_code, "var engine = animation.init_strip()") < 0, "Should NOT generate engine initialization for template-only file")
|
||||
|
||||
# Should NOT contain engine.run()
|
||||
assert(string.find(single_code, "engine.run()") < 0, "Should NOT generate engine.run() for template-only file")
|
||||
|
||||
# Should contain template function definition
|
||||
assert(string.find(single_code, "def pulse_effect_template(engine, base_color_, duration_, brightness_)") >= 0, "Should generate template function")
|
||||
|
||||
# Should contain function registration
|
||||
assert(string.find(single_code, "animation.register_user_function('pulse_effect', pulse_effect_template)") >= 0, "Should register template function")
|
||||
|
||||
# Test multiple templates
|
||||
var multiple_templates_dsl = "template pulse_effect {\n" +
|
||||
" param base_color type color\n" +
|
||||
" param duration\n" +
|
||||
" \n" +
|
||||
" animation pulse = pulsating_animation(\n" +
|
||||
" color=base_color\n" +
|
||||
" period=duration\n" +
|
||||
" )\n" +
|
||||
" run pulse\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"template blink_red {\n" +
|
||||
" param speed\n" +
|
||||
" \n" +
|
||||
" animation blink = pulsating_animation(\n" +
|
||||
" color=red\n" +
|
||||
" period=speed\n" +
|
||||
" )\n" +
|
||||
" \n" +
|
||||
" run blink\n" +
|
||||
"}"
|
||||
|
||||
var multiple_code = animation_dsl.compile(multiple_templates_dsl)
|
||||
assert(multiple_code != nil, "Should compile multiple templates")
|
||||
|
||||
# Should NOT contain engine initialization or run
|
||||
assert(string.find(multiple_code, "var engine = animation.init_strip()") < 0, "Should NOT generate engine initialization for multiple templates")
|
||||
assert(string.find(multiple_code, "engine.run()") < 0, "Should NOT generate engine.run() for multiple templates")
|
||||
|
||||
# Should contain both template functions
|
||||
assert(string.find(multiple_code, "def pulse_effect_template(") >= 0, "Should generate first template function")
|
||||
assert(string.find(multiple_code, "def blink_red_template(") >= 0, "Should generate second template function")
|
||||
|
||||
# Should contain both registrations
|
||||
assert(string.find(multiple_code, "animation.register_user_function('pulse_effect'") >= 0, "Should register first template")
|
||||
assert(string.find(multiple_code, "animation.register_user_function('blink_red'") >= 0, "Should register second template")
|
||||
|
||||
print("✓ Template-only transpilation test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test mixed template and DSL transpilation
|
||||
def test_mixed_template_dsl_transpilation()
|
||||
print("Testing mixed template and DSL transpilation...")
|
||||
|
||||
# Test template with regular DSL (should generate engine initialization and run)
|
||||
var mixed_dsl = "template pulse_effect {\n" +
|
||||
" param base_color type color\n" +
|
||||
" param duration\n" +
|
||||
" \n" +
|
||||
" animation pulse = pulsating_animation(\n" +
|
||||
" color=base_color\n" +
|
||||
" period=duration\n" +
|
||||
" )\n" +
|
||||
" run pulse\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"color my_red = 0xFF0000\n" +
|
||||
"animation test_anim = solid(color=my_red)\n" +
|
||||
"run test_anim"
|
||||
|
||||
var mixed_code = animation_dsl.compile(mixed_dsl)
|
||||
assert(mixed_code != nil, "Should compile mixed template and DSL")
|
||||
|
||||
# Should contain engine initialization because of non-template DSL
|
||||
assert(string.find(mixed_code, "var engine = animation.init_strip()") >= 0, "Should generate engine initialization for mixed content")
|
||||
|
||||
# Should contain engine.run() because of run statement
|
||||
assert(string.find(mixed_code, "engine.run()") >= 0, "Should generate engine.run() for mixed content")
|
||||
|
||||
# Should contain template function
|
||||
assert(string.find(mixed_code, "def pulse_effect_template(") >= 0, "Should generate template function")
|
||||
|
||||
# Should contain regular DSL elements
|
||||
assert(string.find(mixed_code, "var my_red_ = 0xFFFF0000") >= 0, "Should generate color definition")
|
||||
assert(string.find(mixed_code, "var test_anim_ = animation.solid(engine)") >= 0, "Should generate animation definition")
|
||||
|
||||
# Test template with property assignment (should generate engine initialization)
|
||||
var template_with_property_dsl = "template pulse_effect {\n" +
|
||||
" param base_color type color\n" +
|
||||
" \n" +
|
||||
" animation pulse = pulsating_animation(color=base_color, period=2s)\n" +
|
||||
" run pulse\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"test_anim.opacity = 128"
|
||||
|
||||
var property_code = animation_dsl.compile(template_with_property_dsl)
|
||||
assert(property_code != nil, "Should compile template with property assignment")
|
||||
|
||||
# Should generate engine initialization because of property assignment
|
||||
assert(string.find(property_code, "var engine = animation.init_strip()") >= 0, "Should generate engine initialization for property assignment")
|
||||
|
||||
# Should NOT generate engine.run() because no run statement
|
||||
assert(string.find(property_code, "engine.run()") < 0, "Should NOT generate engine.run() without run statement")
|
||||
|
||||
print("✓ Mixed template and DSL transpilation test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_dsl_transpiler_tests()
|
||||
print("=== DSL Transpiler Test Suite ===")
|
||||
@ -1141,7 +1275,9 @@ def run_dsl_transpiler_tests()
|
||||
test_easing_keywords,
|
||||
test_animation_type_checking,
|
||||
test_color_type_checking,
|
||||
test_invalid_sequence_commands
|
||||
test_invalid_sequence_commands,
|
||||
test_template_only_transpilation,
|
||||
test_mixed_template_dsl_transpilation
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
|
||||
@ -0,0 +1,188 @@
|
||||
# DSL Undefined Identifier Test
|
||||
# Tests that undefined identifiers in function calls are properly caught and reported
|
||||
#
|
||||
# This test covers the fix for process_primary_expression() where undefined identifiers
|
||||
# in function call contexts should raise appropriate error messages.
|
||||
|
||||
import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test class to verify undefined identifier error handling
|
||||
class DSLUndefinedIdentifierTest
|
||||
var test_results
|
||||
|
||||
def init()
|
||||
self.test_results = []
|
||||
end
|
||||
|
||||
# Helper method to run a test case
|
||||
def run_test(test_name, test_func)
|
||||
try
|
||||
test_func()
|
||||
self.test_results.push(f"✓ {test_name}")
|
||||
return true
|
||||
except .. as e, msg
|
||||
self.test_results.push(f"✗ {test_name}: {msg}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Test undefined function in animation definition
|
||||
# Note: This will be caught by animation factory validation, not our new check
|
||||
def test_undefined_function_in_animation()
|
||||
var dsl_code =
|
||||
"animation test = undefined_function(color=red)\n"
|
||||
"run test"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
raise "compilation_error", "Should have failed with undefined function error"
|
||||
except "dsl_compilation_error" as e, msg
|
||||
# This will be caught by animation factory validation
|
||||
if string.find(msg, "Animation factory function 'undefined_function' does not exist") == -1
|
||||
raise "wrong_error", f"Expected animation factory error, got: {msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Test undefined function in color definition
|
||||
# Note: This will be caught by color provider validation, not our new check
|
||||
def test_undefined_function_in_color()
|
||||
var dsl_code =
|
||||
"color test_color = undefined_color_provider(period=2s)\n"
|
||||
"animation test = solid(color=test_color)\n"
|
||||
"run test"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
raise "compilation_error", "Should have failed with undefined function error"
|
||||
except "dsl_compilation_error" as e, msg
|
||||
# This will be caught by color provider validation
|
||||
if string.find(msg, "Color provider factory") == -1 && string.find(msg, "does not exist") == -1
|
||||
raise "wrong_error", f"Expected color provider factory error, got: {msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Test undefined function in property assignment
|
||||
def test_undefined_function_in_property()
|
||||
var dsl_code =
|
||||
"animation test = solid(color=red)\n"
|
||||
"test.opacity = undefined_value_provider(min_value=0, max_value=255)\n"
|
||||
"run test"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
raise "compilation_error", "Should have failed with undefined function error"
|
||||
except "dsl_compilation_error" as e, msg
|
||||
if string.find(msg, "Unknown function or identifier 'undefined_value_provider'") == -1
|
||||
raise "wrong_error", f"Expected undefined function error, got: {msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Test undefined function in computed expression
|
||||
def test_undefined_function_in_computed_expression()
|
||||
var dsl_code =
|
||||
"animation test = solid(color=red)\n"
|
||||
"test.opacity = undefined_function() + 100\n"
|
||||
"run test"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
raise "compilation_error", "Should have failed with undefined function error"
|
||||
except "dsl_compilation_error" as e, msg
|
||||
if string.find(msg, "Unknown function or identifier 'undefined_function'") == -1
|
||||
raise "wrong_error", f"Expected undefined function error, got: {msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Test that valid functions still work (regression test)
|
||||
def test_valid_functions_still_work()
|
||||
var dsl_code =
|
||||
"set osc = triangle(min_value=50, max_value=255, duration=2s)\n"
|
||||
"animation test = solid(color=red)\n"
|
||||
"test.opacity = osc\n"
|
||||
"run test"
|
||||
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "Valid functions should compile successfully"
|
||||
end
|
||||
|
||||
# Check that the generated code contains expected elements
|
||||
if string.find(berry_code, "animation.triangle") == -1
|
||||
raise "generation_error", "Generated code should contain animation.triangle"
|
||||
end
|
||||
end
|
||||
|
||||
# Test undefined identifier in regular context (not function call)
|
||||
def test_undefined_identifier_regular_context()
|
||||
var dsl_code =
|
||||
"animation test = solid(color=undefined_color)\n"
|
||||
"run test"
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
raise "compilation_error", "Should have failed with undefined identifier error"
|
||||
except "dsl_compilation_error" as e, msg
|
||||
if string.find(msg, "Unknown identifier 'undefined_color'") == -1
|
||||
raise "wrong_error", f"Expected undefined identifier error, got: {msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_all_tests()
|
||||
print("=== DSL Undefined Identifier Test Suite ===")
|
||||
|
||||
var tests = [
|
||||
["Undefined function in animation definition", / -> self.test_undefined_function_in_animation()],
|
||||
["Undefined function in color definition", / -> self.test_undefined_function_in_color()],
|
||||
["Undefined function in property assignment", / -> self.test_undefined_function_in_property()],
|
||||
["Undefined function in computed expression", / -> self.test_undefined_function_in_computed_expression()],
|
||||
["Valid functions still work (regression)", / -> self.test_valid_functions_still_work()],
|
||||
["Undefined identifier in regular context", / -> self.test_undefined_identifier_regular_context()]
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
var total = size(tests)
|
||||
|
||||
for test_info : tests
|
||||
var test_name = test_info[0]
|
||||
var test_func = test_info[1]
|
||||
|
||||
try
|
||||
if self.run_test(test_name, test_func)
|
||||
passed += 1
|
||||
end
|
||||
except .. as error_type, error_message
|
||||
print(f"✗ Test crashed: {error_type} - {error_message}")
|
||||
end
|
||||
end
|
||||
|
||||
print(f"\n=== Test Results: {passed}/{total} passed ===")
|
||||
|
||||
for result : self.test_results
|
||||
print(result)
|
||||
end
|
||||
|
||||
return passed == total
|
||||
end
|
||||
end
|
||||
|
||||
# Run the tests
|
||||
var test_suite = DSLUndefinedIdentifierTest()
|
||||
var success = test_suite.run_all_tests()
|
||||
|
||||
if success
|
||||
print("\n🎉 All undefined identifier tests passed!")
|
||||
else
|
||||
print("\n❌ Some undefined identifier tests failed!")
|
||||
raise "test_failed"
|
||||
end
|
||||
|
||||
return success
|
||||
@ -165,93 +165,103 @@ class DSLValueProviderValidationTest
|
||||
end
|
||||
end
|
||||
|
||||
# Test strip_length arithmetic expression (should be wrapped with animation.resolve)
|
||||
# Test strip_length arithmetic expression (should fail compilation due to dangerous pattern)
|
||||
def test_strip_length_arithmetic_expression()
|
||||
var dsl_code = "set strip_len3 = (strip_length() + 1) / 2"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "strip_length arithmetic expression should compile successfully"
|
||||
var compilation_failed = false
|
||||
var error_message = ""
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
compilation_failed = true
|
||||
end
|
||||
except "dsl_compilation_error" as e, msg
|
||||
compilation_failed = true
|
||||
error_message = msg
|
||||
end
|
||||
|
||||
# Check that it generates closure with animation.resolve wrapper
|
||||
if string.find(berry_code, "create_closure_value") == -1
|
||||
raise "generation_error", "Arithmetic expression should create closure"
|
||||
if !compilation_failed
|
||||
raise "validation_error", "strip_length in arithmetic expression should cause compilation to fail due to dangerous pattern"
|
||||
end
|
||||
|
||||
# Most importantly, check that strip_length is wrapped with animation.resolve
|
||||
if string.find(berry_code, "animation.resolve(animation.strip_length(engine))") == -1
|
||||
raise "generation_error", "strip_length in arithmetic should be wrapped with animation.resolve()"
|
||||
end
|
||||
|
||||
# Check the complete expected pattern
|
||||
var expected_pattern = "def (engine) return (animation.resolve(animation.strip_length(engine)) + 1) / 2 end"
|
||||
if string.find(berry_code, expected_pattern) == -1
|
||||
raise "generation_error", f"Generated code should contain expected pattern: {expected_pattern}"
|
||||
# Check that the error message mentions the dangerous pattern
|
||||
if string.find(error_message, "cannot be used in computed expressions") == -1
|
||||
raise "error_message_error", f"Error message should mention computed expressions restriction, got: {error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Test complex strip_length arithmetic
|
||||
# Test complex strip_length arithmetic (should fail compilation due to dangerous pattern)
|
||||
def test_strip_length_complex_arithmetic()
|
||||
var dsl_code = "set complex = (strip_length() + 5) * 2 - strip_length() / 4"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "Complex strip_length arithmetic should compile successfully"
|
||||
end
|
||||
var compilation_failed = false
|
||||
var error_message = ""
|
||||
|
||||
# Check that both strip_length calls are wrapped with animation.resolve
|
||||
var resolve_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
var found_pos = string.find(berry_code, "animation.resolve(animation.strip_length(engine))", pos)
|
||||
if found_pos == -1
|
||||
break
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
compilation_failed = true
|
||||
end
|
||||
resolve_count += 1
|
||||
pos = found_pos + 1
|
||||
except "dsl_compilation_error" as e, msg
|
||||
compilation_failed = true
|
||||
error_message = msg
|
||||
end
|
||||
|
||||
if resolve_count != 2
|
||||
raise "generation_error", f"Expected 2 animation.resolve() calls for strip_length, found {resolve_count}"
|
||||
if !compilation_failed
|
||||
raise "validation_error", "Complex strip_length arithmetic should cause compilation to fail due to dangerous pattern"
|
||||
end
|
||||
|
||||
# Check that the error message mentions the dangerous pattern
|
||||
if string.find(error_message, "cannot be used in computed expressions") == -1
|
||||
raise "error_message_error", f"Error message should mention computed expressions restriction, got: {error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Test mixed user variables and strip_length
|
||||
# Test mixed user variables and strip_length (should fail due to dangerous pattern)
|
||||
def test_mixed_variables_and_strip_length()
|
||||
var dsl_code = "set val1 = 10\nset mixed = val1 + strip_length() * 2"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "Mixed variables and strip_length should compile successfully"
|
||||
var compilation_failed = false
|
||||
var error_message = ""
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
compilation_failed = true
|
||||
end
|
||||
except "dsl_compilation_error" as e, msg
|
||||
compilation_failed = true
|
||||
error_message = msg
|
||||
end
|
||||
|
||||
# Check that both val1_ and strip_length are properly resolved
|
||||
if string.find(berry_code, "animation.resolve(val1_)") == -1
|
||||
raise "generation_error", "User variable val1_ should be wrapped with animation.resolve()"
|
||||
if !compilation_failed
|
||||
raise "validation_error", "Mixed variables and strip_length should cause compilation to fail due to dangerous pattern"
|
||||
end
|
||||
|
||||
if string.find(berry_code, "animation.resolve(animation.strip_length(engine))") == -1
|
||||
raise "generation_error", "strip_length should be wrapped with animation.resolve()"
|
||||
# Check that the error message mentions the dangerous pattern
|
||||
if string.find(error_message, "cannot be used in computed expressions") == -1
|
||||
raise "error_message_error", f"Error message should mention computed expressions restriction, got: {error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Test strip_length in property assignment
|
||||
# Test strip_length in property assignment (currently allowed due to anonymous function wrapper)
|
||||
def test_strip_length_in_property_assignment()
|
||||
var dsl_code = "animation test = solid(color=red)\ntest.opacity = strip_length() / 2"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "strip_length in property assignment should compile successfully"
|
||||
raise "compilation_error", "strip_length in property assignment should compile (anonymous function wrapper bypasses dangerous pattern detection)"
|
||||
end
|
||||
|
||||
# Check that property assignment creates closure with resolved strip_length
|
||||
if string.find(berry_code, "test_.opacity = animation.create_closure_value") == -1
|
||||
raise "generation_error", "Property assignment with arithmetic should create closure"
|
||||
# Check that it generates an anonymous function wrapper
|
||||
if string.find(berry_code, "def (engine)") == -1
|
||||
raise "generation_error", "Property assignment should generate anonymous function wrapper"
|
||||
end
|
||||
|
||||
if string.find(berry_code, "animation.resolve(animation.strip_length(engine))") == -1
|
||||
raise "generation_error", "strip_length in property assignment should be wrapped with animation.resolve()"
|
||||
if string.find(berry_code, "animation.strip_length(engine)") == -1
|
||||
raise "generation_error", "Anonymous function should contain strip_length call"
|
||||
end
|
||||
end
|
||||
|
||||
@ -275,74 +285,61 @@ class DSLValueProviderValidationTest
|
||||
end
|
||||
end
|
||||
|
||||
# Test edge case: strip_length with parentheses
|
||||
# Test edge case: strip_length with parentheses (should fail due to dangerous pattern)
|
||||
def test_strip_length_with_parentheses()
|
||||
var dsl_code = "set result = (strip_length()) * 2"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "strip_length with parentheses should compile successfully"
|
||||
end
|
||||
var compilation_failed = false
|
||||
var error_message = ""
|
||||
|
||||
# Check that strip_length is still properly resolved even with extra parentheses
|
||||
if string.find(berry_code, "animation.resolve(animation.strip_length(engine))") == -1
|
||||
raise "generation_error", "strip_length with parentheses should be wrapped with animation.resolve()"
|
||||
end
|
||||
end
|
||||
|
||||
# Test edge case: multiple strip_length calls in same expression
|
||||
def test_multiple_strip_length_calls()
|
||||
var dsl_code = "set ratio = strip_length() / (strip_length() + 10)"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "Multiple strip_length calls should compile successfully"
|
||||
end
|
||||
|
||||
# Count the number of animation.resolve(animation.strip_length(engine)) calls
|
||||
var resolve_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
var found_pos = string.find(berry_code, "animation.resolve(animation.strip_length(engine))", pos)
|
||||
if found_pos == -1
|
||||
break
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
compilation_failed = true
|
||||
end
|
||||
resolve_count += 1
|
||||
pos = found_pos + 1
|
||||
except "dsl_compilation_error" as e, msg
|
||||
compilation_failed = true
|
||||
error_message = msg
|
||||
end
|
||||
|
||||
if resolve_count != 2
|
||||
raise "generation_error", f"Expected 2 resolved strip_length calls, found {resolve_count}"
|
||||
if !compilation_failed
|
||||
raise "validation_error", "strip_length with parentheses in arithmetic should cause compilation to fail due to dangerous pattern"
|
||||
end
|
||||
|
||||
# Check that the error message mentions the dangerous pattern
|
||||
if string.find(error_message, "cannot be used in computed expressions") == -1
|
||||
raise "error_message_error", f"Error message should mention computed expressions restriction, got: {error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Test edge case: strip_length in nested expressions
|
||||
# Test edge case: strip_length in nested expressions (should fail due to dangerous pattern)
|
||||
def test_strip_length_nested_expressions()
|
||||
var dsl_code = "set nested = ((strip_length() + 1) * 2) / (strip_length() - 1)"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "Nested strip_length expressions should compile successfully"
|
||||
end
|
||||
var compilation_failed = false
|
||||
var error_message = ""
|
||||
|
||||
# Both strip_length calls should be resolved
|
||||
var resolve_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
var found_pos = string.find(berry_code, "animation.resolve(animation.strip_length(engine))", pos)
|
||||
if found_pos == -1
|
||||
break
|
||||
try
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
compilation_failed = true
|
||||
end
|
||||
resolve_count += 1
|
||||
pos = found_pos + 1
|
||||
except "dsl_compilation_error" as e, msg
|
||||
compilation_failed = true
|
||||
error_message = msg
|
||||
end
|
||||
|
||||
if resolve_count != 2
|
||||
raise "generation_error", f"Expected 2 resolved strip_length calls in nested expression, found {resolve_count}"
|
||||
if !compilation_failed
|
||||
raise "validation_error", "Nested strip_length expressions should cause compilation to fail due to dangerous pattern"
|
||||
end
|
||||
|
||||
# Check that the error message mentions the dangerous pattern
|
||||
if string.find(error_message, "cannot be used in computed expressions") == -1
|
||||
raise "error_message_error", f"Error message should mention computed expressions restriction, got: {error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Test that the fix doesn't affect strip_length when not in arithmetic
|
||||
# Test that strip_length works in non-arithmetic contexts (direct assignment without closure)
|
||||
def test_strip_length_non_arithmetic_contexts()
|
||||
var dsl_code = "animation test = solid(color=red)\ntest.opacity = strip_length()"
|
||||
|
||||
@ -351,14 +348,57 @@ class DSLValueProviderValidationTest
|
||||
raise "compilation_error", "strip_length in non-arithmetic context should compile successfully"
|
||||
end
|
||||
|
||||
# In non-arithmetic context, strip_length should be used directly without resolve
|
||||
if string.find(berry_code, "test_.opacity = animation.strip_length(engine)") == -1
|
||||
raise "generation_error", "strip_length in non-arithmetic context should be used directly"
|
||||
# In property assignment context, strip_length should be assigned directly without closure wrapping
|
||||
# since it's a value provider instance without additional computation
|
||||
if string.find(berry_code, "def (engine)") != -1
|
||||
raise "generation_error", "strip_length in property assignment should NOT use anonymous function wrapper when used alone"
|
||||
end
|
||||
|
||||
# Should not contain animation.resolve for this case
|
||||
if string.find(berry_code, "animation.resolve(animation.strip_length(engine))") != -1
|
||||
raise "generation_error", "strip_length in non-arithmetic context should not be wrapped with resolve"
|
||||
if string.find(berry_code, "animation.strip_length(engine)") == -1
|
||||
raise "generation_error", "Should contain direct strip_length call"
|
||||
end
|
||||
|
||||
# Should not contain animation.resolve since it's not in a computed expression
|
||||
if string.find(berry_code, "animation.resolve") != -1
|
||||
raise "generation_error", "strip_length should not be wrapped with resolve in this context"
|
||||
end
|
||||
end
|
||||
|
||||
# Test the safe pattern: separate strip_length() call from computation
|
||||
def test_strip_length_safe_pattern()
|
||||
var dsl_code = "set strip_len = strip_length()\nset computed = (strip_len + 1) / 2"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "Safe strip_length pattern should compile successfully"
|
||||
end
|
||||
|
||||
# Check that strip_len is created as a direct call
|
||||
if string.find(berry_code, "var strip_len_ = animation.strip_length(engine)") == -1
|
||||
raise "generation_error", "strip_len should be created as direct call"
|
||||
end
|
||||
|
||||
# Check that computed uses closure with animation.resolve for the variable
|
||||
if string.find(berry_code, "create_closure_value") == -1
|
||||
raise "generation_error", "Computed expression should create closure"
|
||||
end
|
||||
|
||||
if string.find(berry_code, "animation.resolve(strip_len_)") == -1
|
||||
raise "generation_error", "User variable strip_len_ should be wrapped with animation.resolve()"
|
||||
end
|
||||
|
||||
# Should NOT contain direct strip_length calls in the computed expression
|
||||
var computed_line_start = string.find(berry_code, "var computed_ = ")
|
||||
if computed_line_start != -1
|
||||
var computed_line_end = string.find(berry_code, "\n", computed_line_start)
|
||||
if computed_line_end == -1
|
||||
computed_line_end = size(berry_code)
|
||||
end
|
||||
var computed_line = berry_code[computed_line_start..computed_line_end-1]
|
||||
|
||||
if string.find(computed_line, "animation.strip_length(engine)") != -1
|
||||
raise "generation_error", "Computed expression should not contain direct strip_length calls"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -382,9 +422,9 @@ class DSLValueProviderValidationTest
|
||||
["Strip Length in Property Assignment", / -> self.test_strip_length_in_property_assignment()],
|
||||
["No Regression with Regular Expressions", / -> self.test_no_regression_with_regular_expressions()],
|
||||
["Strip Length with Parentheses", / -> self.test_strip_length_with_parentheses()],
|
||||
["Multiple Strip Length Calls", / -> self.test_multiple_strip_length_calls()],
|
||||
["Strip Length Nested Expressions", / -> self.test_strip_length_nested_expressions()],
|
||||
["Strip Length Non-Arithmetic Contexts", / -> self.test_strip_length_non_arithmetic_contexts()]
|
||||
["Strip Length Non-Arithmetic Contexts", / -> self.test_strip_length_non_arithmetic_contexts()],
|
||||
["Strip Length Safe Pattern", / -> self.test_strip_length_safe_pattern()]
|
||||
]
|
||||
|
||||
for test : tests
|
||||
|
||||
@ -130,7 +130,8 @@ def test_dsl_event_compilation()
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"color custom_red = 0xFF0000\n"
|
||||
"on button_press: solid(custom_red)\n"
|
||||
"run solid(custom_red)"
|
||||
"animation anim = solid(color=custom_red)"
|
||||
"run anim"
|
||||
|
||||
var compiled_code = animation_dsl.compile(dsl_code)
|
||||
|
||||
@ -145,8 +146,10 @@ def test_dsl_event_with_parameters()
|
||||
var dsl_code =
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"color custom_blue = 0x0000FF\n"
|
||||
"color custom_red = 0xFF0000\n"
|
||||
"on timer(5s): solid(custom_blue)\n"
|
||||
"run solid(custom_blue)"
|
||||
"animation anim = solid(color=custom_red)"
|
||||
"run anim"
|
||||
|
||||
var compiled_code = animation_dsl.compile(dsl_code)
|
||||
|
||||
|
||||
@ -46,21 +46,17 @@ def test_undefined_variable_exception()
|
||||
print("Testing undefined variable exception behavior...")
|
||||
|
||||
var dsl_code = "animation test = solid(color=undefined_var)"
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
|
||||
assert(berry_code != nil, "Should compile DSL code")
|
||||
|
||||
# Check that undefined variables use direct underscore notation
|
||||
import string
|
||||
assert(string.find(berry_code, "test_.color = undefined_var_") >= 0, "Should use undefined_var_ directly for undefined variable")
|
||||
|
||||
# Verify the generated code fails to compile (due to undefined variable)
|
||||
# This is better than the old behavior - we catch undefined variables at compile time!
|
||||
# The new transpiler behavior is to catch undefined variables at DSL compile time
|
||||
# This is better than the old behavior - we catch errors earlier!
|
||||
try
|
||||
var compiled_code = compile(berry_code)
|
||||
assert(false, "Should have failed to compile due to undefined variable")
|
||||
except .. as e, msg
|
||||
print(f"✓ Correctly failed to compile due to undefined variable: {e}")
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
assert(false, "Should have failed to compile DSL due to undefined variable")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
print(f"✓ Correctly failed to compile DSL due to undefined variable: {e}")
|
||||
# Verify the error message mentions the undefined variable
|
||||
import string
|
||||
assert(string.find(msg, "undefined_var") >= 0, "Error message should mention undefined_var")
|
||||
end
|
||||
|
||||
print("✓ Undefined variable exception test passed")
|
||||
|
||||
@ -589,7 +589,8 @@ def test_alternative_syntax_integration()
|
||||
" 0xFFFF00\n" +
|
||||
"]\n" +
|
||||
"\n" +
|
||||
"animation fire_anim = rich_palette_animation(palette=fire_colors, cycle_period=3s)\n" +
|
||||
"color rich_palette2 = color_cycle(palette=fire_colors, cycle_period=3s)\n"
|
||||
"animation fire_anim = solid(color=rich_palette2)\n" +
|
||||
"\n" +
|
||||
"run fire_anim\n"
|
||||
|
||||
@ -599,7 +600,8 @@ def test_alternative_syntax_integration()
|
||||
# Verify the generated code contains the expected elements
|
||||
import string
|
||||
assert(string.find(berry_code, "var fire_colors_ = bytes(") != -1, "Should contain palette definition")
|
||||
assert(string.find(berry_code, "rich_palette_animation(engine)") != -1, "Should contain animation creation")
|
||||
assert(string.find(berry_code, "color_cycle(engine)") != -1, "Should contain value provider creation")
|
||||
assert(string.find(berry_code, "solid(engine)") != -1, "Should contain animation creation")
|
||||
assert(string.find(berry_code, "fire_colors_") != -1, "Should reference the palette")
|
||||
|
||||
print("✓ Alternative syntax integration test passed")
|
||||
|
||||
@ -36,13 +36,13 @@ def test_basic_symbol_registration()
|
||||
return true
|
||||
end
|
||||
|
||||
# Test forward reference resolution
|
||||
def test_forward_reference_resolution()
|
||||
print("Testing forward reference resolution...")
|
||||
# Test proper symbol ordering (no forward references)
|
||||
def test_proper_symbol_ordering()
|
||||
print("Testing proper symbol ordering...")
|
||||
|
||||
# DSL with forward reference: animation uses color defined later
|
||||
var dsl_source = "animation fire_pattern = solid(color=custom_red)\n" +
|
||||
"color custom_red = 0xFF0000"
|
||||
# DSL with proper ordering: color defined before animation uses it
|
||||
var dsl_source = "color custom_red = 0xFF0000\n" +
|
||||
"animation fire_pattern = solid(color=custom_red)"
|
||||
|
||||
var lexer = animation_dsl.DSLLexer(dsl_source)
|
||||
var tokens = lexer.tokenize()
|
||||
@ -50,20 +50,20 @@ def test_forward_reference_resolution()
|
||||
|
||||
var berry_code = transpiler.transpile()
|
||||
|
||||
# Should resolve the forward reference successfully
|
||||
assert(berry_code != nil, "Should compile with forward reference")
|
||||
assert(!transpiler.has_errors(), "Should resolve forward reference without errors")
|
||||
# Should compile successfully with proper ordering
|
||||
assert(berry_code != nil, "Should compile with proper symbol ordering")
|
||||
assert(!transpiler.has_errors(), "Should have no errors with proper ordering")
|
||||
|
||||
# Check generated code contains both definitions (with underscore suffix)
|
||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should define custom_red color")
|
||||
assert(string.find(berry_code, "var fire_pattern_ = animation.solid(engine)") >= 0, "Should define fire animation")
|
||||
assert(string.find(berry_code, "fire_pattern_.color = custom_red_") >= 0, "Should reference custom_red")
|
||||
|
||||
print("✓ Forward reference resolution test passed")
|
||||
print("✓ Proper symbol ordering test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test undefined reference handling (simplified transpiler uses runtime resolution)
|
||||
# Test undefined reference handling (should fail at transpile time)
|
||||
def test_undefined_reference_handling()
|
||||
print("Testing undefined reference handling...")
|
||||
|
||||
@ -76,22 +76,13 @@ def test_undefined_reference_handling()
|
||||
|
||||
var berry_code = transpiler.transpile()
|
||||
|
||||
# New behavior: transpiler generates direct reference to undefined_color_
|
||||
assert(berry_code != nil, "Should compile with direct reference")
|
||||
assert(!transpiler.has_errors(), "Should have no compile-time errors")
|
||||
# Should detect undefined reference at transpile time
|
||||
assert(transpiler.has_errors(), "Should detect undefined reference error")
|
||||
|
||||
# Check that direct reference is generated (since undefined_color doesn't exist in animation module)
|
||||
assert(string.find(berry_code, "undefined_color_") >= 0, "Should generate runtime resolution")
|
||||
|
||||
# With new behavior, Berry compilation will fail due to undefined variable
|
||||
# This is actually better than runtime errors as it catches issues earlier
|
||||
try
|
||||
var compiled_code = compile(berry_code)
|
||||
assert(false, "Should fail to compile due to undefined variable")
|
||||
except .. as e, msg
|
||||
print(f"✓ Correctly caught undefined variable at compile time: {e}")
|
||||
assert(string.find(str(msg), "undefined_color_") >= 0, "Error should mention undefined variable")
|
||||
end
|
||||
# Check that error message mentions the undefined symbol
|
||||
var error_report = transpiler.get_error_report()
|
||||
assert(string.find(error_report, "undefined_color") >= 0, "Error should mention undefined_color")
|
||||
assert(string.find(error_report, "Unknown identifier") >= 0, "Should be an unknown identifier error")
|
||||
|
||||
print("✓ Undefined reference handling test passed")
|
||||
return true
|
||||
@ -147,14 +138,14 @@ def test_definition_generation()
|
||||
return true
|
||||
end
|
||||
|
||||
# Test complex forward references
|
||||
def test_complex_forward_references()
|
||||
print("Testing complex forward references...")
|
||||
# Test complex symbol dependencies with proper ordering
|
||||
def test_complex_symbol_dependencies()
|
||||
print("Testing complex symbol dependencies...")
|
||||
|
||||
# Complex DSL with multiple forward references
|
||||
var dsl_source = "animation complex_anim = pulsating_animation(color=primary_color, period=3000)\n" +
|
||||
# Complex DSL with proper symbol ordering (no forward references)
|
||||
var dsl_source = "color primary_color = 0xFF8000\n" +
|
||||
"animation complex_anim = pulsating_animation(color=primary_color, period=3000)\n" +
|
||||
"animation gradient_pattern = solid(color=primary_color)\n" +
|
||||
"color primary_color = 0xFF8000\n" +
|
||||
"sequence demo {\n" +
|
||||
" play complex_anim for 5s\n" +
|
||||
"}\n" +
|
||||
@ -166,9 +157,9 @@ def test_complex_forward_references()
|
||||
|
||||
var berry_code = transpiler.transpile()
|
||||
|
||||
# Should resolve all forward references
|
||||
assert(berry_code != nil, "Should compile complex forward references")
|
||||
assert(!transpiler.has_errors(), "Should resolve all forward references")
|
||||
# Should compile successfully with proper ordering
|
||||
assert(berry_code != nil, "Should compile complex dependencies")
|
||||
assert(!transpiler.has_errors(), "Should have no errors with proper ordering")
|
||||
|
||||
# Check all definitions are present (with underscore suffix)
|
||||
assert(string.find(berry_code, "var primary_color_") >= 0, "Should define primary color")
|
||||
@ -176,7 +167,7 @@ def test_complex_forward_references()
|
||||
assert(string.find(berry_code, "var complex_anim_") >= 0, "Should define complex animation")
|
||||
assert(string.find(berry_code, "var demo_ = animation.SequenceManager(engine)") >= 0, "Should define sequence")
|
||||
|
||||
print("✓ Complex forward references test passed")
|
||||
print("✓ Complex symbol dependencies test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
@ -186,11 +177,11 @@ def run_symbol_registry_tests()
|
||||
|
||||
var tests = [
|
||||
test_basic_symbol_registration,
|
||||
test_forward_reference_resolution,
|
||||
test_proper_symbol_ordering,
|
||||
test_undefined_reference_handling,
|
||||
test_builtin_reference_handling,
|
||||
test_definition_generation,
|
||||
test_complex_forward_references
|
||||
test_complex_symbol_dependencies
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
|
||||
357
lib/libesp32/berry_animation/src/tests/symbol_table_test.be
Normal file
357
lib/libesp32/berry_animation/src/tests/symbol_table_test.be
Normal file
@ -0,0 +1,357 @@
|
||||
# Symbol Table Test Suite
|
||||
# Tests for the SymbolEntry and SymbolTable classes
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota def log(x) print(x) end" lib/libesp32/berry_animation/src/tests/symbol_table_test.be
|
||||
|
||||
import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test SymbolEntry creation and basic properties
|
||||
def test_symbol_entry_creation()
|
||||
print("Testing SymbolEntry creation...")
|
||||
|
||||
# Test palette constant entry
|
||||
var palette_entry = animation_dsl._symbol_entry.create_palette_constant("PALETTE_RAINBOW", nil, true)
|
||||
assert(palette_entry.name == "PALETTE_RAINBOW", "Should set name correctly")
|
||||
assert(palette_entry.type == "palette_constant", "Should set type correctly")
|
||||
assert(palette_entry.is_builtin == true, "Should be marked as builtin")
|
||||
assert(palette_entry.takes_args == false, "Palette constants don't take args")
|
||||
assert(palette_entry.is_dangerous_call() == false, "Palette constants are not dangerous")
|
||||
|
||||
# Test animation constructor entry
|
||||
var anim_entry = animation_dsl._symbol_entry.create_animation_constructor("solid", nil, true)
|
||||
assert(anim_entry.type == "animation_constructor", "Should be animation constructor")
|
||||
assert(anim_entry.takes_named_args() == true, "Animation constructors take named args")
|
||||
assert(anim_entry.is_dangerous_call() == true, "Animation constructors are dangerous")
|
||||
|
||||
# Test user function entry
|
||||
var func_entry = animation_dsl._symbol_entry.create_user_function("my_func", false)
|
||||
assert(func_entry.type == "user_function", "Should be user function")
|
||||
assert(func_entry.takes_positional_args() == true, "User functions take positional args")
|
||||
assert(func_entry.is_builtin == false, "Should not be builtin")
|
||||
|
||||
print("✓ SymbolEntry creation test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolEntry reference generation
|
||||
def test_symbol_entry_references()
|
||||
print("Testing SymbolEntry reference generation...")
|
||||
|
||||
# Test builtin reference
|
||||
var builtin_entry = animation_dsl._symbol_entry.create_animation_constructor("solid", nil, true)
|
||||
assert(builtin_entry.get_reference() == "animation.solid", "Should generate builtin reference")
|
||||
|
||||
# Test math function reference
|
||||
var math_entry = animation_dsl._symbol_entry.create_math_function("max", true)
|
||||
assert(math_entry.get_reference() == "animation._math.max", "Should generate math function reference")
|
||||
|
||||
# Test user-defined reference
|
||||
var user_entry = animation_dsl._symbol_entry.create_animation_instance("my_anim", nil, false)
|
||||
assert(user_entry.get_reference() == "my_anim_", "Should generate user reference with underscore")
|
||||
|
||||
print("✓ SymbolEntry reference generation test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolEntry argument detection
|
||||
def test_symbol_entry_argument_detection()
|
||||
print("Testing SymbolEntry argument detection...")
|
||||
|
||||
# Test different types and their argument characteristics
|
||||
var tests = [
|
||||
["palette_constant", false, "none"],
|
||||
["math_function", true, "positional"],
|
||||
["user_function", true, "positional"],
|
||||
["value_provider_constructor", true, "named"],
|
||||
["animation_constructor", true, "named"],
|
||||
["color_constructor", true, "named"],
|
||||
["variable", false, "none"],
|
||||
["sequence", false, "none"]
|
||||
]
|
||||
|
||||
for test : tests
|
||||
var typ = test[0]
|
||||
var expected_takes_args = test[1]
|
||||
var expected_arg_type = test[2]
|
||||
|
||||
var entry = animation_dsl._symbol_entry("test_symbol", typ, nil, false)
|
||||
assert(entry.takes_args == expected_takes_args, f"Type {typ} should have takes_args={expected_takes_args}")
|
||||
assert(entry.arg_type == expected_arg_type, f"Type {typ} should have arg_type={expected_arg_type}")
|
||||
end
|
||||
|
||||
print("✓ SymbolEntry argument detection test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolEntry danger detection
|
||||
def test_symbol_entry_danger_detection()
|
||||
print("Testing SymbolEntry danger detection...")
|
||||
|
||||
# Test dangerous types (constructors)
|
||||
var dangerous_types = ["value_provider_constructor", "animation_constructor", "color_constructor"]
|
||||
for typ : dangerous_types
|
||||
var entry = animation_dsl._symbol_entry("test", typ, nil, true)
|
||||
assert(entry.is_dangerous_call() == true, f"Type {typ} should be dangerous")
|
||||
end
|
||||
|
||||
# Test safe types
|
||||
var safe_types = ["palette_constant", "math_function", "user_function", "variable", "sequence"]
|
||||
for typ : safe_types
|
||||
var entry = animation_dsl._symbol_entry("test", typ, nil, true)
|
||||
assert(entry.is_dangerous_call() == false, f"Type {typ} should be safe")
|
||||
end
|
||||
|
||||
print("✓ SymbolEntry danger detection test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolTable basic operations
|
||||
def test_symbol_table_basic_operations()
|
||||
print("Testing SymbolTable basic operations...")
|
||||
|
||||
var table = animation_dsl._symbol_table()
|
||||
|
||||
# Test adding a symbol
|
||||
var entry = animation_dsl._symbol_entry.create_variable("test_var", false)
|
||||
var added_entry = table.add("test_var", entry)
|
||||
assert(added_entry == entry, "Should return the added entry")
|
||||
|
||||
# Test checking if symbol exists
|
||||
assert(table.contains("test_var") == true, "Should contain added symbol")
|
||||
assert(table.contains("nonexistent") == false, "Should not contain nonexistent symbol")
|
||||
|
||||
# Test getting symbol
|
||||
var retrieved = table.get("test_var")
|
||||
assert(retrieved == entry, "Should retrieve the same entry")
|
||||
|
||||
# Test getting reference
|
||||
var ref = table.get_reference("test_var")
|
||||
assert(ref == "test_var_", "Should generate correct reference")
|
||||
|
||||
print("✓ SymbolTable basic operations test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolTable dynamic detection
|
||||
def test_symbol_table_dynamic_detection()
|
||||
print("Testing SymbolTable dynamic detection...")
|
||||
|
||||
var table = animation_dsl._symbol_table()
|
||||
|
||||
# Test detection of built-in animation constructor
|
||||
assert(table.contains("solid") == true, "Should detect built-in solid function")
|
||||
var solid_entry = table.get("solid")
|
||||
assert(solid_entry != nil, "Should get solid entry")
|
||||
assert(solid_entry.type == "animation_constructor", "Should detect as animation constructor")
|
||||
assert(solid_entry.is_builtin == true, "Should be marked as builtin")
|
||||
|
||||
# Test detection of built-in math function
|
||||
assert(table.contains("max") == true, "Should detect built-in max function")
|
||||
var max_entry = table.get("max")
|
||||
assert(max_entry != nil, "Should get max entry")
|
||||
assert(max_entry.type == "math_function", "Should detect as math function")
|
||||
|
||||
# Test detection of built-in palette
|
||||
assert(table.contains("PALETTE_RAINBOW") == true, "Should detect built-in palette")
|
||||
var palette_entry = table.get("PALETTE_RAINBOW")
|
||||
assert(palette_entry != nil, "Should get palette entry")
|
||||
assert(palette_entry.type == "palette_constant", "Should detect as palette constant")
|
||||
|
||||
print("✓ SymbolTable dynamic detection test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolTable conflict detection
|
||||
def test_symbol_table_conflict_detection()
|
||||
print("Testing SymbolTable conflict detection...")
|
||||
|
||||
var table = animation_dsl._symbol_table()
|
||||
|
||||
# Try to redefine a built-in symbol with different type
|
||||
var user_entry = animation_dsl._symbol_entry.create_variable("solid", false)
|
||||
|
||||
try
|
||||
table.add("solid", user_entry)
|
||||
assert(false, "Should have thrown conflict error")
|
||||
except "symbol_redefinition_error" as e, msg
|
||||
assert(string.find(str(msg), "solid") >= 0, "Error should mention symbol name")
|
||||
assert(string.find(str(msg), "animation_constructor") >= 0, "Error should mention built-in type")
|
||||
end
|
||||
|
||||
# Test that same type redefinition is allowed
|
||||
var anim_entry1 = animation_dsl._symbol_entry.create_animation_instance("my_anim", nil, false)
|
||||
var anim_entry2 = animation_dsl._symbol_entry.create_animation_instance("my_anim", nil, false)
|
||||
|
||||
table.add("my_anim", anim_entry1)
|
||||
table.add("my_anim", anim_entry2) # Should not throw
|
||||
|
||||
print("✓ SymbolTable conflict detection test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolTable creation methods
|
||||
def test_symbol_table_creation_methods()
|
||||
print("Testing SymbolTable creation methods...")
|
||||
|
||||
var table = animation_dsl._symbol_table()
|
||||
|
||||
# Test create_palette
|
||||
var palette_entry = table.create_palette("my_palette", nil)
|
||||
assert(palette_entry.type == "palette", "Should create palette entry")
|
||||
assert(table.contains("my_palette") == true, "Should add to table")
|
||||
|
||||
# Test create_color
|
||||
var color_entry = table.create_color("my_color", nil)
|
||||
assert(color_entry.type == "color", "Should create color entry")
|
||||
assert(table.contains("my_color") == true, "Should add to table")
|
||||
|
||||
# Test create_animation
|
||||
var anim_entry = table.create_animation("my_anim", nil)
|
||||
assert(anim_entry.type == "animation", "Should create animation entry")
|
||||
assert(table.contains("my_anim") == true, "Should add to table")
|
||||
|
||||
# Test create_value_provider
|
||||
var vp_entry = table.create_value_provider("my_vp", nil)
|
||||
assert(vp_entry.type == "value_provider", "Should create value provider entry")
|
||||
assert(table.contains("my_vp") == true, "Should add to table")
|
||||
|
||||
# Test create_variable
|
||||
var var_entry = table.create_variable("my_var")
|
||||
assert(var_entry.type == "variable", "Should create variable entry")
|
||||
assert(table.contains("my_var") == true, "Should add to table")
|
||||
|
||||
# Test create_sequence
|
||||
var seq_entry = table.create_sequence("my_seq")
|
||||
assert(seq_entry.type == "sequence", "Should create sequence entry")
|
||||
assert(table.contains("my_seq") == true, "Should add to table")
|
||||
|
||||
# Test create_template
|
||||
var template_entry = table.create_template("my_template", {"param1": "int", "param2": "string"})
|
||||
assert(template_entry.type == "template", "Should create template entry")
|
||||
assert(template_entry.get_param_types()["param1"] == "int", "Should set parameter types")
|
||||
assert(table.contains("my_template") == true, "Should add to table")
|
||||
|
||||
print("✓ SymbolTable creation methods test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolTable named color handling
|
||||
def test_symbol_table_named_colors()
|
||||
print("Testing SymbolTable named color handling...")
|
||||
|
||||
var table = animation_dsl._symbol_table()
|
||||
|
||||
# Test named color detection
|
||||
assert(table.symbol_exists("red") == true, "Should recognize named color 'red'")
|
||||
assert(table.symbol_exists("blue") == true, "Should recognize named color 'blue'")
|
||||
assert(table.symbol_exists("nonexistent_color") == false, "Should not recognize invalid color")
|
||||
|
||||
# Test named color reference generation
|
||||
var red_ref = table.get_reference("red")
|
||||
assert(red_ref == "0xFFFF0000", "Should generate correct hex value for red")
|
||||
|
||||
var blue_ref = table.get_reference("blue")
|
||||
assert(blue_ref == "0xFF0000FF", "Should generate correct hex value for blue")
|
||||
|
||||
print("✓ SymbolTable named color handling test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test SymbolTable utility methods
|
||||
def test_symbol_table_utility_methods()
|
||||
print("Testing SymbolTable utility methods...")
|
||||
|
||||
var table = animation_dsl._symbol_table()
|
||||
|
||||
# Add some test symbols
|
||||
table.create_animation("test_anim", nil)
|
||||
table.create_variable("test_var")
|
||||
|
||||
# Test get_type
|
||||
assert(table.get_type("test_anim") == "animation", "Should return correct type")
|
||||
assert(table.get_type("solid") == "animation_constructor", "Should return builtin type")
|
||||
assert(table.get_type("nonexistent") == nil, "Should return nil for nonexistent")
|
||||
|
||||
# Test takes_args
|
||||
assert(table.takes_args("solid") == true, "Solid should take args")
|
||||
assert(table.takes_args("test_var") == false, "Variables don't take args")
|
||||
|
||||
# Test takes_named_args
|
||||
assert(table.takes_named_args("solid") == true, "Solid takes named args")
|
||||
assert(table.takes_named_args("max") == false, "Max takes positional args")
|
||||
|
||||
# Test takes_positional_args
|
||||
assert(table.takes_positional_args("max") == true, "Max takes positional args")
|
||||
assert(table.takes_positional_args("solid") == false, "Solid doesn't take positional args")
|
||||
|
||||
# Test is_dangerous
|
||||
assert(table.is_dangerous("solid") == true, "Solid is dangerous (constructor)")
|
||||
assert(table.is_dangerous("max") == false, "Max is not dangerous")
|
||||
assert(table.is_dangerous("test_var") == false, "Variables are not dangerous")
|
||||
|
||||
print("✓ SymbolTable utility methods test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test MockEngine functionality
|
||||
def test_mock_engine()
|
||||
print("Testing MockEngine functionality...")
|
||||
|
||||
var mock = animation_dsl.MockEngine()
|
||||
assert(mock.time_ms == 0, "Should initialize time to 0")
|
||||
assert(mock.get_strip_length() == 30, "Should return default strip length")
|
||||
|
||||
print("✓ MockEngine test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all symbol table tests
|
||||
def run_symbol_table_tests()
|
||||
print("=== Symbol Table Test Suite ===")
|
||||
|
||||
var tests = [
|
||||
test_symbol_entry_creation,
|
||||
test_symbol_entry_references,
|
||||
test_symbol_entry_argument_detection,
|
||||
test_symbol_entry_danger_detection,
|
||||
test_symbol_table_basic_operations,
|
||||
test_symbol_table_dynamic_detection,
|
||||
test_symbol_table_conflict_detection,
|
||||
test_symbol_table_creation_methods,
|
||||
test_symbol_table_named_colors,
|
||||
test_symbol_table_utility_methods,
|
||||
test_mock_engine
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
var total = size(tests)
|
||||
|
||||
for test_func : tests
|
||||
try
|
||||
if test_func()
|
||||
passed += 1
|
||||
else
|
||||
print("✗ Test failed")
|
||||
end
|
||||
except .. as error_type, error_message
|
||||
print("✗ Test crashed: " + str(error_type) + " - " + str(error_message))
|
||||
end
|
||||
print("") # Add spacing between tests
|
||||
end
|
||||
|
||||
print("=== Results: " + str(passed) + "/" + str(total) + " tests passed ===")
|
||||
|
||||
if passed == total
|
||||
print("🎉 All symbol table tests passed!")
|
||||
return true
|
||||
else
|
||||
print("❌ Some symbol table tests failed")
|
||||
raise "test_failed"
|
||||
end
|
||||
end
|
||||
|
||||
# Auto-run tests when file is executed
|
||||
run_symbol_table_tests()
|
||||
@ -8,6 +8,7 @@ import tasmota
|
||||
|
||||
# Import the animation module
|
||||
import animation
|
||||
import user_functions
|
||||
|
||||
# Define a function to run a test file
|
||||
def run_test_file(file_path)
|
||||
@ -56,8 +57,8 @@ def run_all_tests()
|
||||
|
||||
# Animation effect tests
|
||||
"lib/libesp32/berry_animation/src/tests/filled_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/pulse_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/breathe_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/pulse_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/breathe_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/color_cycle_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/color_cycle_bytes_test.be", # Tests ColorCycleColorProvider with bytes palette
|
||||
"lib/libesp32/berry_animation/src/tests/color_cycle_palette_size_test.be", # Tests ColorCycleColorProvider palette_size read-only parameter
|
||||
@ -70,17 +71,17 @@ def run_all_tests()
|
||||
"lib/libesp32/berry_animation/src/tests/beacon_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/gradient_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/noise_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/plasma_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/sparkle_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/plasma_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/sparkle_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/wave_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/palette_pattern_animation_test.be",
|
||||
|
||||
# Motion effects tests
|
||||
"lib/libesp32/berry_animation/src/tests/shift_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/bounce_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/scale_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/jitter_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/motion_effects_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/shift_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/bounce_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/scale_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/jitter_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/motion_effects_test.be",
|
||||
|
||||
# Color and parameter tests
|
||||
"lib/libesp32/berry_animation/src/tests/crenel_position_color_test.be",
|
||||
@ -117,9 +118,14 @@ def run_all_tests()
|
||||
"lib/libesp32/berry_animation/src/tests/palette_dsl_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_parameter_validation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_value_provider_validation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_undefined_identifier_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_newline_syntax_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/test_math_method_transpilation.be",
|
||||
"lib/libesp32/berry_animation/src/tests/test_user_functions_in_computed_parameters.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_berry_code_blocks_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_lexer_triple_quotes_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_berry_integration_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/dsl_restart_test.be",
|
||||
|
||||
# Event system tests
|
||||
"lib/libesp32/berry_animation/src/tests/event_system_test.be"
|
||||
|
||||
@ -111,7 +111,8 @@ def test_is_math_method_function()
|
||||
# Test mathematical methods
|
||||
var math_methods = ["min", "max", "abs", "round", "sqrt", "scale", "sin", "cos"]
|
||||
for method : math_methods
|
||||
if !transpiler.is_math_method(method)
|
||||
var entry = transpiler.symbol_table.get(method)
|
||||
if entry == nil || entry.type != animation_dsl._symbol_entry.TYPE_MATH_FUNCTION
|
||||
print(f" ❌ {method} should be detected as a math method")
|
||||
return false
|
||||
else
|
||||
@ -122,7 +123,8 @@ def test_is_math_method_function()
|
||||
# Test non-mathematical methods
|
||||
var non_math_methods = ["pulsating_animation", "solid", "color_cycle", "unknown_method"]
|
||||
for method : non_math_methods
|
||||
if transpiler.is_math_method(method)
|
||||
var entry = transpiler.symbol_table.get(method)
|
||||
if entry != nil && entry.type == animation_dsl._symbol_entry.TYPE_MATH_FUNCTION
|
||||
print(f" ❌ {method} should NOT be detected as a math method")
|
||||
return false
|
||||
else
|
||||
|
||||
@ -31,13 +31,13 @@ def test_transpilation_case(dsl_code, expected_user_function, test_name)
|
||||
|
||||
print(f" Generated code:\n{generated_code}")
|
||||
|
||||
# Check that user function is called with self.engine
|
||||
var expected_call = f"animation.get_user_function('{expected_user_function}')(self.engine"
|
||||
# Check that user function is called with engine parameter
|
||||
var expected_call = f"animation.get_user_function('{expected_user_function}')(engine"
|
||||
if string.find(generated_code, expected_call) < 0
|
||||
print(f" ❌ Expected to find '{expected_call}' in generated code")
|
||||
return false
|
||||
else
|
||||
print(f" ✅ Found user function call with self.engine: '{expected_call}'")
|
||||
print(f" ✅ Found user function call with engine: '{expected_call}'")
|
||||
end
|
||||
|
||||
# Verify the code compiles
|
||||
@ -87,6 +87,7 @@ def test_user_function_in_computed_parameter()
|
||||
|
||||
# Test case 1: Simple user function in computed parameter
|
||||
var dsl_code1 =
|
||||
"import user_functions\n"
|
||||
"set strip_len = strip_length()\n"
|
||||
"animation test = pulsating_animation(color=red, period=2s)\n"
|
||||
"test.opacity = rand_demo()\n"
|
||||
@ -99,9 +100,10 @@ def test_user_function_in_computed_parameter()
|
||||
|
||||
# Test case 2: User function with mathematical functions
|
||||
var dsl_code2 =
|
||||
"import user_functions\n"
|
||||
"set strip_len = strip_length()\n"
|
||||
"animation test = solid(color=red)\n"
|
||||
"test.brightness = max(100, rand_demo())\n"
|
||||
"test.opacity = max(100, rand_demo())\n"
|
||||
"run test"
|
||||
|
||||
var result2 = test_transpilation_case(dsl_code2, "rand_demo", "User function with mathematical functions")
|
||||
@ -111,9 +113,10 @@ def test_user_function_in_computed_parameter()
|
||||
|
||||
# Test case 3: User function in arithmetic expressions
|
||||
var dsl_code3 =
|
||||
"import user_functions\n"
|
||||
"set strip_len = strip_length()\n"
|
||||
"animation test = solid(color=green)\n"
|
||||
"test.position = abs(rand_demo() - 128) + 64\n"
|
||||
"test.opacity = abs(rand_demo() - 128) + 64\n"
|
||||
"run test"
|
||||
|
||||
var result3 = test_transpilation_case(dsl_code3, "rand_demo", "User function in arithmetic expressions")
|
||||
@ -136,7 +139,8 @@ if test1_result && test2_result
|
||||
print("\n🎉 All tests passed!")
|
||||
print("✅ User functions are correctly detected")
|
||||
print("✅ User functions work correctly in computed parameters")
|
||||
print("✅ User functions are called with self.engine in closure context")
|
||||
print("✅ User functions are called with engine in closure context")
|
||||
else
|
||||
print("\n❌ Some tests failed!")
|
||||
raise "test_failed"
|
||||
end
|
||||
@ -33,7 +33,7 @@ def test_user_function_in_computed_parameters()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_base = solid(color=blue, priority=10)\n"
|
||||
"random_base.opacity = user.rand_demo()\n"
|
||||
"random_base.opacity = rand_demo()\n"
|
||||
"run random_base"
|
||||
|
||||
try
|
||||
@ -57,7 +57,7 @@ def test_user_function_with_math()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_bounded = solid(color=orange, priority=8)\n"
|
||||
"random_bounded.opacity = max(50, min(255, user.rand_demo() + 100))\n"
|
||||
"random_bounded.opacity = max(50, min(255, rand_demo() + 100))\n"
|
||||
"run random_bounded"
|
||||
|
||||
try
|
||||
@ -83,7 +83,7 @@ def test_user_function_in_arithmetic()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_variation = solid(color=purple, priority=15)\n"
|
||||
"random_variation.opacity = abs(user.rand_demo() - 128) + 64\n"
|
||||
"random_variation.opacity = abs(rand_demo() - 128) + 64\n"
|
||||
"run random_variation"
|
||||
|
||||
try
|
||||
@ -109,7 +109,7 @@ def test_complex_user_function_expressions()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_complex = solid(color=white, priority=20)\n"
|
||||
"random_complex.opacity = round((user.rand_demo() + 128) / 2 + abs(user.rand_demo() - 100))\n"
|
||||
"random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))\n"
|
||||
"run random_complex"
|
||||
|
||||
try
|
||||
@ -140,8 +140,8 @@ def test_generated_code_validity()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_multi = solid(color=cyan, priority=12)\n"
|
||||
"random_multi.opacity = user.rand_demo()\n"
|
||||
"random_multi.duration = max(100, user.rand_demo())\n"
|
||||
"random_multi.opacity = rand_demo()\n"
|
||||
"random_multi.duration = max(100, rand_demo())\n"
|
||||
"run random_multi"
|
||||
|
||||
try
|
||||
|
||||
Loading…
Reference in New Issue
Block a user