# DSL Reference - Berry Animation Framework This document provides a comprehensive reference for the Animation DSL (Domain-Specific Language), which allows you to define animations using a declarative syntax with named parameters. ## Module Import The DSL functionality is provided by a separate module: ```berry import animation # Core framework (required) import animation_dsl # DSL compiler and runtime (required for DSL) ``` ## Why Use the DSL? ### Benefits - **Declarative syntax**: Describe what you want, not how to implement it - **Readable code**: Natural language-like syntax - **Rapid prototyping**: Quick iteration on animation ideas - **Event-driven**: Built-in support for interactive animations - **Composition**: Easy layering and sequencing of animations ### When to Use DSL vs Programmatic **Use DSL when:** - Creating complex animation sequences - Building interactive, event-driven animations - Rapid prototyping and experimentation - Non-programmers need to create animations - You want declarative, readable animation definitions **Use programmatic API when:** - Building reusable animation components - Performance is critical (DSL has compilation overhead) - You need fine-grained control over animation logic - Integrating with existing Berry code - Firmware size is constrained (DSL module can be excluded) ## Transpiler Architecture For detailed information about the DSL transpiler's internal architecture, including the core processing flow and expression processing chain, see [Transpiler_Architecture.md](Transpiler_Architecture.md). ## DSL API Functions ### Core Functions #### `animation_dsl.compile(source)` Compiles DSL source code to Berry code without executing it. ```berry var dsl_source = "color red = 0xFF0000\n" "animation red_anim = solid(color=red)\n" "run red_anim" var berry_code = animation_dsl.compile(dsl_source) print(berry_code) # Shows generated Berry code ``` #### `animation_dsl.execute(source)` Compiles and executes DSL source code in one step. ```berry animation_dsl.execute("color blue = 0x0000FF\n" "animation blue_anim = solid(color=blue)\n" "run blue_anim for 5s") ``` #### `animation_dsl.load_file(filename)` Loads DSL source from a file and executes it. ```berry # Create a DSL file var f = open("my_animation.dsl", "w") f.write("color green = 0x00FF00\n" "animation pulse_green = breathe(color=green, period=2s)\n" "run pulse_green") f.close() # Load and execute animation_dsl.load_file("my_animation.dsl") ``` ## DSL Language Overview The Animation DSL uses a declarative syntax with named parameters. All animations are created with an engine-first pattern and parameters are set individually for maximum flexibility. ### Key Syntax Features - **Import statements**: `import module_name` for loading Berry modules - **Named parameters**: All function calls use `name=value` syntax - **Time units**: `2s`, `500ms`, `1m`, `1h` - **Hex colors**: `0xFF0000`, `0x80FF0000` (ARGB) - **Named colors**: `red`, `blue`, `white`, etc. - **Comments**: `# This is a comment` - **Property assignment**: `animation.property = value` - **User functions**: `function_name()` for custom functions ### Basic Structure ```berry # Import statements (optional, for user functions or custom modules) import user_functions # Optional strip configuration strip length 60 # Color definitions color red = 0xFF0000 color blue = 0x0000FF # Animation definitions with named parameters animation pulse_red = breathe(color=red, period=2s) animation comet_blue = comet(color=blue, tail_length=10, speed=1500) # Property assignments with user functions pulse_red.priority = 10 pulse_red.opacity = breathing_effect() comet_blue.direction = -1 # Execution run pulse_red ``` The DSL transpiles to Berry code where each animation gets an engine parameter and named parameters are set individually. ## Symbol Resolution The DSL transpiler uses intelligent symbol resolution at compile time to optimize generated code and eliminate runtime lookups: ### Transpile-Time Symbol Resolution When the DSL encounters an identifier (like `SINE` or `red`), it checks at transpile time whether the symbol exists in the `animation` module using Berry's introspection capabilities: ```berry # If SINE exists in animation module animation wave = wave(waveform=SINE) # Transpiles to: animation.SINE (direct access) # If custom_color doesn't exist in animation module color custom_color = 0xFF0000 animation solid_red = solid(color=custom_color) # Transpiles to: custom_color_ (user-defined variable) ``` ### Benefits - **Performance**: Eliminates runtime symbol lookups for built-in constants - **Error Detection**: Catches undefined symbols at compile time - **Code Clarity**: Generated Berry code clearly shows built-in vs user-defined symbols - **Optimization**: Direct access to animation module symbols is faster ### Symbol Categories **Built-in Symbols** (resolved to `animation.`): - Animation factory functions: `solid`, `breathe`, `comet` - Value providers: `triangle`, `smooth`, `sine`, `static_value` - Color providers: `color_cycle`, `breathe_color`, `rich_palette_color` - Constants: `PALETTE_RAINBOW`, `SINE`, `TRIANGLE`, etc. **User-defined Symbols** (resolved to `_`): - Custom colors: `my_red`, `fire_color` - Custom animations: `pulse_effect`, `rainbow_wave` - Variables: `brightness_level`, `cycle_time` ### Property Assignment Resolution Property assignments also use the same resolution logic: ```berry # Built-in symbol (if 'engine' existed in animation module) engine.brightness = 200 # Would transpile to: animation.engine.brightness = 200 # User-defined symbol my_animation.priority = 10 # Transpiles to: my_animation_.priority = 10 ``` This intelligent resolution ensures optimal performance while maintaining clear separation between framework and user code. ## Import Statement Transpilation The DSL supports importing Berry modules using the `import` keyword, which provides a clean way to load user functions and custom modules. ### Import Syntax ```berry # DSL Import Syntax import user_functions import my_custom_module import math ``` ### Transpilation Behavior Import statements are transpiled directly to Berry import statements with quoted module names: ```berry # DSL Code import user_functions # Transpiles to Berry Code import "user_functions" ``` ### Import Processing 1. **Early Processing**: Import statements are processed early in transpilation 2. **Module Loading**: Imported modules are loaded using standard Berry import mechanism 3. **Function Registration**: User function modules should register functions using `animation.register_user_function()` 4. **No Validation**: The DSL doesn't validate module existence at compile time ### Example Import Workflow **Step 1: Create User Functions Module (`user_functions.be`)** ```berry import animation def rand_demo(engine) import math return math.rand() % 256 end # Register for DSL use animation.register_user_function("rand_demo", rand_demo) ``` **Step 2: Use in DSL** ```berry import user_functions animation test = solid(color=blue) test.opacity = rand_demo() run test ``` **Step 3: Generated Berry Code** ```berry import animation var engine = animation.init_strip() import "user_functions" var test_ = animation.solid(engine) test_.color = 0xFF0000FF test_.opacity = animation.create_closure_value(engine, def (engine) return animation.get_user_function('rand_demo')(engine) end) 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 = breathe(color=red, period=2s) berry """ pulse_.opacity = 200 pulse_.priority = 10 """ # Transpiles to Berry Code var pulse_ = animation.breathe(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 The DSL supports two types of templates: regular templates (functions) and template animations (classes). #### Template Animation Transpilation Template animations create reusable animation classes extending `engine_proxy`: ```berry # DSL Template Animation template animation shutter_effect { param colors type palette nillable true param duration type time min 0 max 3600 default 5 nillable false set strip_len = strip_length() color col = color_cycle(colors=colors, period=0) animation shutter = beacon( color = col beacon_size = strip_len / 2 ) sequence seq repeat forever { play shutter for duration col.next = 1 } run seq } ``` **Transpiles to:** ```berry class shutter_effect_animation : animation.engine_proxy static var PARAMS = animation.enc_params({ "colors": {"type": "palette", "nillable": true}, "duration": {"type": "time", "min": 0, "max": 3600, "default": 5, "nillable": false} }) def init(engine) super(self).init(engine) var strip_len_ = animation.strip_length(engine) var col_ = animation.color_cycle(engine) col_.colors = animation.create_closure_value(engine, def (engine) return self.colors end) col_.period = 0 var shutter_ = animation.beacon(engine) shutter_.color = col_ shutter_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) / 2 end) var seq_ = animation.sequence_manager(engine, -1) .push_play_step(shutter_, animation.resolve(self.duration)) .push_closure_step(def (engine) col_.next = 1 end) self.add(seq_) end end ``` **Key Features:** - Parameters accessed as `self.` and wrapped in closures - Constraints (min, max, default, nillable) encoded in PARAMS - Uses `self.add()` instead of `engine.add()` - Can be instantiated multiple times with different parameters #### Regular Template Transpilation Regular templates generate Berry functions: ```berry # DSL Template template pulse_effect { param color type color param speed animation pulse = breathe(color=color, period=speed) run pulse } ``` **Transpiles to:** ```berry def pulse_effect_template(engine, color_, speed_) var pulse_ = animation.breathe(engine) pulse_.color = color_ pulse_.period = speed_ engine.add(pulse_) end animation.register_user_function('pulse_effect', pulse_effect_template) ``` #### Template vs Template Animation **Template Animation** (`template animation`): - Generates classes extending `engine_proxy` - Parameters accessed as `self.` - Supports parameter constraints (min, max, default, nillable) - Uses `self.add()` for composition - Can be instantiated multiple times **Regular Template** (`template`): - Generates functions - Parameters accessed as `_` - Uses `engine.add()` for execution - Called like functions ### User-Defined Functions Register custom Berry functions for use in DSL. User functions must take `engine` as the first parameter, followed by any user-provided arguments: ```berry # Define custom function in Berry - engine must be first parameter def custom_twinkle(engine, color, count, period) var anim = animation.twinkle(engine) anim.color = color anim.count = count atml:parameter> return anim end # Register the function for DSL use animation.register_user_function("twinkle", custom_twinkle) ``` ```berry # Use in DSL - engine is automatically passed as first argument animation gold_twinkle = twinkle(0xFFD700, 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. For comprehensive examples and best practices, see the **[User Functions Guide](User_Functions.md)**. ### Event System Define event handlers that respond to triggers: ```berry # Define animations for different states color normal = 0x000080 color alert = 0xFF0000 animation normal_state = solid(color=normal) animation alert_state = breathe(color=alert, period=500ms) # Event handlers on button_press { run alert_state for 3s run normal_state } on sensor_trigger { run alert_state for 5s wait 1s run normal_state } # Default state run normal_state ``` ### Nested Function Calls DSL supports nested function calls for complex compositions: ```berry # Nested calls in animation definitions (now supported) animation complex = breathe( color=red, period=2s ) # Nested calls in run statements sequence demo { play breathe(color=blue, period=1s) for 10s } ``` ## Error Handling The DSL compiler validates classes and parameters at transpilation time, catching errors before execution: ```berry var invalid_dsl = "color red = #INVALID_COLOR\n" "animation bad = unknown_function(red)\n" "animation pulse = breathe(invalid_param=123)" try animation_dsl.execute(invalid_dsl) except .. as e print("DSL Error:", e) end ``` ### Transpilation-Time Validation The DSL performs comprehensive validation during compilation: **Animation Factory Validation:** ```berry # Error: Function doesn't exist animation bad = nonexistent_animation(color=red) # Transpiler error: "Animation factory function 'nonexistent_animation' does not exist" # Error: Function exists but doesn't create animation animation bad2 = math_function(value=10) # Transpiler error: "Function 'math_function' does not create an animation instance" ``` **Parameter Validation:** ```berry # Error: Invalid parameter name in constructor animation pulse = breathe(invalid_param=123) # Transpiler error: "Parameter 'invalid_param' is not valid for breathe" # Error: Invalid parameter name in property assignment animation pulse = breathe(color=red, period=2s) pulse.wrong_arg = 15 # Transpiler error: "Animation 'PulseAnimation' does not have parameter 'wrong_arg'" # Error: Parameter constraint violation animation comet = comet(tail_length=-5) # Transpiler error: "Parameter 'tail_length' value -5 violates constraint: min=1" ``` **Color Provider Validation:** ```berry # Error: Color provider doesn't exist color bad = nonexistent_color_provider(period=2s) # Transpiler error: "Color provider factory 'nonexistent_color_provider' does not exist" # Error: Function exists but doesn't create color provider color bad2 = breathe(color=red) # Transpiler error: "Function 'breathe' does not create a color provider instance" ``` **Reference Validation:** ```berry # Error: Undefined color reference animation pulse = breathe(color=undefined_color) # Transpiler error: "Undefined reference: 'undefined_color'" # Error: Undefined animation reference in run statement run nonexistent_animation # Transpiler error: "Undefined reference 'nonexistent_animation' in run" # Error: Undefined animation reference in sequence sequence demo { play nonexistent_animation for 5s } # 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 ``` **Template Parameter Validation:** ```berry # Error: Duplicate parameter names template bad_template { param color type color param color type number # Error: duplicate parameter name } # Transpiler error: "Duplicate parameter name 'color' in template" # Error: Reserved keyword as parameter name template reserved_template { param animation type color # Error: conflicts with reserved keyword } # Transpiler error: "Parameter name 'animation' conflicts with reserved keyword" # Error: Built-in color name as parameter template color_template { param red type number # Error: conflicts with built-in color } # Transpiler error: "Parameter name 'red' conflicts with built-in color name" # Error: Invalid type annotation template type_template { param value type invalid_type # Error: invalid type } # Transpiler error: "Invalid parameter type 'invalid_type'. Valid types are: [...]" # Warning: Unused parameter (compilation succeeds) template unused_template { param used_color type color param unused_param type number # Warning: never used animation test = solid(color=used_color) run test } # Transpiler warning: "Template 'unused_template' parameter 'unused_param' is declared but never used" ``` ### Error Categories - **Syntax errors**: Invalid DSL syntax (lexer/parser errors) - **Factory validation**: Non-existent or invalid animation/color provider factories - **Parameter validation**: Invalid parameter names in constructors or property assignments - **Template validation**: Invalid template parameter names, types, or usage patterns - **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) ### Warning Categories The DSL transpiler also generates **warnings** that don't prevent compilation but indicate potential code quality issues: - **Unused parameters**: Template parameters that are declared but never used in the template body - **Code quality**: Suggestions for better coding practices **Warning Behavior:** - Warnings are included as comments in the generated Berry code - Compilation succeeds even with warnings present - Warnings help maintain code quality without being overly restrictive ## Performance Considerations ### DSL vs Programmatic Performance - **DSL compilation overhead**: ~10-50ms depending on complexity - **Generated code performance**: Identical to hand-written Berry code - **Memory usage**: DSL compiler uses temporary memory during compilation ### Optimization Tips 1. **Compile once, run many times**: ```berry var compiled = animation_dsl.compile(dsl_source) var fn = compile(compiled) # Run multiple times without recompilation fn() # First execution fn() # Subsequent executions are faster ``` 2. **Use programmatic API for performance-critical code**: ```berry # DSL for high-level structure animation_dsl.execute( "sequence main {\n" "play performance_critical_anim for 10s\n" "}\n" "run main" ) # Programmatic for performance-critical animations var performance_critical_anim = animation.create_optimized_animation() ``` ## Integration Examples ### With Tasmota Rules ```berry # In autoexec.be import animation import animation_dsl def handle_rule_trigger(event) if event == "motion" animation_dsl.execute("color alert = 0xFF0000\n" "animation alert_anim = breathe(color=alert, period=500ms)\n" "run alert_anim for 5s") elif event == "door" animation_dsl.execute("color welcome = 0x00FF00\n" "animation welcome_anim = breathe(color=welcome, period=2s)\n" "run welcome_anim for 8s") end end # Register with Tasmota's rule system tasmota.add_rule("motion", handle_rule_trigger) ``` ### With Web Interface ```berry # Create web endpoints for DSL execution import webserver def web_execute_dsl() var dsl_code = webserver.arg("dsl") if dsl_code try animation_dsl.execute(dsl_code) webserver.content_response("DSL executed successfully") except .. as e webserver.content_response(f"DSL Error: {e}") end else webserver.content_response("No DSL code provided") end end webserver.on("/execute_dsl", web_execute_dsl) ``` ## Best Practices 1. **Structure your DSL files**: ```berry # Strip configuration first strip length 60 # Colors next color red = 0xFF0000 color blue = 0x0000FF # Animations with named parameters animation red_solid = solid(color=red) animation pulse_red = breathe(color=red, period=2s) # Property assignments pulse_red.priority = 10 # Sequences sequence demo { play pulse_red for 5s } # Execution last run demo ``` 2. **Use meaningful names**: ```berry # Good color warning_red = 0xFF0000 animation door_alert = breathe(color=warning_red, period=500ms) # Avoid color c1 = 0xFF0000 animation a1 = breathe(color=c1, period=500ms) ``` 3. **Comment your DSL**: ```berry # Security system colors color normal_blue = 0x000080 # Idle state color alert_red = 0xFF0000 # Alert state color success_green = 0x00FF00 # Success state # Main security animation sequence sequence security_demo { play solid(color=normal_blue) for 10s # Normal operation play breathe(color=alert_red, period=500ms) for 3s # Alert play breathe(color=success_green, period=2s) for 5s # Success confirmation } ``` 4. **Organize complex projects**: ```berry # Load DSL modules animation_dsl.load_file("colors.dsl") # Color definitions animation_dsl.load_file("animations.dsl") # Animation library animation_dsl.load_file("sequences.dsl") # Sequence definitions animation_dsl.load_file("main.dsl") # Main execution ``` This completes the DSL reference documentation. The DSL provides a powerful, declarative way to create complex animations while maintaining the option to use the lightweight programmatic API when needed.