Berry animation allow reference to other instances and check parameters (#23843)
This commit is contained in:
parent
a07b293a9b
commit
01b9036e38
@ -16,20 +16,20 @@ var strip_len_ = animation.strip_length(engine)
|
||||
# Create animation with computed values
|
||||
var stream1_ = animation.comet_animation(engine)
|
||||
stream1_.color = 0xFFFF0000
|
||||
stream1_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.abs(self.resolve(strip_len_, param_name, time_ms) / 4)) end) # computed value
|
||||
stream1_.tail_length = animation.create_closure_value(engine, def (self) return self.abs(self.resolve(strip_len_) / 4) end) # computed value
|
||||
stream1_.speed = 1.5
|
||||
stream1_.priority = 10
|
||||
# More complex computed values
|
||||
var base_speed_ = 2.0
|
||||
var stream2_ = animation.comet_animation(engine)
|
||||
stream2_.color = 0xFF0000FF
|
||||
stream2_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) / 8 + (2 * self.resolve(strip_len_, param_name, time_ms)) - 10) end) # computed with addition
|
||||
stream2_.speed = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(base_speed_, param_name, time_ms) * 1.5) end) # computed with multiplication
|
||||
stream2_.tail_length = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) / 8 + (2 * self.resolve(strip_len_)) - 10 end) # computed with addition
|
||||
stream2_.speed = animation.create_closure_value(engine, def (self) return self.resolve(base_speed_) * 1.5 end) # computed with multiplication
|
||||
stream2_.direction = (-1)
|
||||
stream2_.priority = 5
|
||||
# Property assignment with computed values
|
||||
stream1_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) / 5) end)
|
||||
stream2_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) * 4) end)
|
||||
stream1_.tail_length = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) / 5 end)
|
||||
stream2_.opacity = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) * 4 end)
|
||||
# Run both animations
|
||||
engine.add_animation(stream1_)
|
||||
engine.add_animation(stream2_)
|
||||
|
||||
@ -18,7 +18,7 @@ red_eye_.color = 0xFFFF0000
|
||||
red_eye_.pos = (def (engine)
|
||||
var provider = animation.cosine_osc(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) - 2) end)
|
||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
||||
provider.duration = 5000
|
||||
return provider
|
||||
end)(engine)
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: cylon_red_green.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var red_eye_ = animation.beacon_animation(engine)
|
||||
red_eye_.color = 0xFFFF0000
|
||||
red_eye_.pos = (def (engine)
|
||||
var provider = animation.cosine_osc(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
||||
provider.duration = 5000
|
||||
return provider
|
||||
end)(engine)
|
||||
red_eye_.beacon_size = 3 # small 3 pixels eye
|
||||
red_eye_.slew_size = 2 # with 2 pixel shading around
|
||||
red_eye_.priority = 10
|
||||
var green_eye_ = animation.beacon_animation(engine)
|
||||
green_eye_.color = 0xFF008000
|
||||
green_eye_.pos = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - self.resolve(red_eye_, 'pos') end)
|
||||
green_eye_.beacon_size = 3 # small 3 pixels eye
|
||||
green_eye_.slew_size = 2 # with 2 pixel shading around
|
||||
green_eye_.priority = 15 # behind red eye
|
||||
engine.add_animation(red_eye_)
|
||||
engine.add_animation(green_eye_)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
animation red_eye = beacon_animation(
|
||||
color = red
|
||||
pos = cosine_osc(min_value = 0, max_value = strip_len - 2, duration = 5s)
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 10
|
||||
)
|
||||
|
||||
animation green_eye = beacon_animation(
|
||||
color = green
|
||||
pos = strip_len - red_eye.pos
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 15 # behind red eye
|
||||
)
|
||||
|
||||
run red_eye
|
||||
run green_eye
|
||||
|
||||
-#
|
||||
@ -18,31 +18,31 @@ var random_base_ = animation.solid(engine)
|
||||
random_base_.color = 0xFF0000FF
|
||||
random_base_.priority = 10
|
||||
# Use user function in property assignment
|
||||
random_base_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (animation.get_user_function('rand_demo')(self.engine)) end)
|
||||
random_base_.opacity = animation.create_closure_value(engine, def (self) return animation.get_user_function('rand_demo')(self.engine) end)
|
||||
# Example 2: User function with mathematical operations
|
||||
var random_bounded_ = animation.solid(engine)
|
||||
random_bounded_.color = 0xFFFFA500
|
||||
random_bounded_.priority = 8
|
||||
# User function with bounds using math functions
|
||||
random_bounded_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.max(50, self.min(255, animation.get_user_function('rand_demo')(self.engine) + 100))) end)
|
||||
random_bounded_.opacity = animation.create_closure_value(engine, def (self) return self.max(50, self.min(255, animation.get_user_function('rand_demo')(self.engine) + 100)) end)
|
||||
# Example 3: User function in arithmetic expressions
|
||||
var random_variation_ = animation.solid(engine)
|
||||
random_variation_.color = 0xFF800080
|
||||
random_variation_.priority = 15
|
||||
# Mix user function with arithmetic operations
|
||||
random_variation_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.abs(animation.get_user_function('rand_demo')(self.engine) - 128) + 64) end)
|
||||
random_variation_.opacity = animation.create_closure_value(engine, def (self) return self.abs(animation.get_user_function('rand_demo')(self.engine) - 128) + 64 end)
|
||||
# Example 4: User function affecting different properties
|
||||
var random_multi_ = animation.solid(engine)
|
||||
random_multi_.color = 0xFF00FFFF
|
||||
random_multi_.priority = 12
|
||||
# Use user function for multiple properties
|
||||
random_multi_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.max(100, animation.get_user_function('rand_demo')(self.engine))) end)
|
||||
random_multi_.opacity = animation.create_closure_value(engine, def (self) return self.max(100, animation.get_user_function('rand_demo')(self.engine)) end)
|
||||
# Example 5: Complex expression with user function
|
||||
var random_complex_ = animation.solid(engine)
|
||||
random_complex_.color = 0xFFFFFFFF
|
||||
random_complex_.priority = 20
|
||||
# Complex expression with user function and math operations
|
||||
random_complex_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.round((animation.get_user_function('rand_demo')(self.engine) + 128) / 2 + self.abs(animation.get_user_function('rand_demo')(self.engine) - 100))) end)
|
||||
random_complex_.opacity = animation.create_closure_value(engine, def (self) return self.round((animation.get_user_function('rand_demo')(self.engine) + 128) / 2 + self.abs(animation.get_user_function('rand_demo')(self.engine) - 100)) end)
|
||||
# Run all animations to demonstrate the effects
|
||||
engine.add_animation(random_base_)
|
||||
engine.add_animation(random_bounded_)
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
animation red_eye = beacon_animation(
|
||||
color = red
|
||||
pos = cosine_osc(min_value = 0, max_value = strip_len - 2, duration = 5s)
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 10
|
||||
)
|
||||
|
||||
animation green_eye = beacon_animation(
|
||||
color = green
|
||||
pos = strip_len - red_eye.pos
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 15 # behind red eye
|
||||
)
|
||||
|
||||
run red_eye
|
||||
run green_eye
|
||||
@ -767,6 +767,8 @@ The DSL validates class and parameter existence during compilation, catching err
|
||||
- **Parameter Names**: Checks that all named parameters are valid for the specific class
|
||||
- **Parameter Constraints**: Validates parameter values against defined constraints (min/max, enums, types)
|
||||
- **Nested Validation**: Validates parameters in nested function calls and value providers
|
||||
- **Property Assignment Validation**: Validates parameter names in property assignments (e.g., `animation.invalid_param = value`) against the actual class parameters
|
||||
- **Object Reference Validation**: Validates that referenced objects exist in `run` statements and sequence `play` statements
|
||||
|
||||
### Common Errors
|
||||
|
||||
@ -774,15 +776,28 @@ The DSL validates class and parameter existence during compilation, catching err
|
||||
# Invalid: Redefining predefined color
|
||||
color red = 0x800000 # Error: Cannot redefine 'red'
|
||||
|
||||
# Invalid: Unknown parameter
|
||||
# Invalid: Unknown parameter in constructor
|
||||
animation bad = pulsating_animation(invalid_param=123) # Error: Unknown parameter
|
||||
|
||||
# Invalid: Undefined reference
|
||||
# Invalid: Unknown parameter in property assignment
|
||||
animation pulse = pulsating_animation(color=red, period=2s)
|
||||
pulse.wrong_arg = 15 # Error: Parameter 'wrong_arg' not valid for PulseAnimation
|
||||
|
||||
# Invalid: Undefined reference in color definition
|
||||
animation ref = solid(color=undefined_color) # Error: Undefined reference
|
||||
|
||||
# Invalid: Undefined reference in run statement
|
||||
run undefined_animation # Error: Undefined reference 'undefined_animation' in run
|
||||
|
||||
# Invalid: Undefined reference in sequence
|
||||
sequence demo {
|
||||
play undefined_animation for 5s # Error: Undefined reference 'undefined_animation' in sequence play
|
||||
}
|
||||
|
||||
# Valid alternatives
|
||||
color my_red = 0x800000 # OK: Different name
|
||||
animation good = pulsating_animation(color=red, period=2s) # OK: Valid parameters
|
||||
good.priority = 10 # OK: Valid parameter assignment
|
||||
```
|
||||
|
||||
## Formal Grammar (EBNF)
|
||||
|
||||
@ -292,10 +292,15 @@ animation bad2 = math_function(value=10)
|
||||
|
||||
**Parameter Validation:**
|
||||
```berry
|
||||
# Error: Invalid parameter name
|
||||
# Error: Invalid parameter name in constructor
|
||||
animation pulse = pulsating_animation(invalid_param=123)
|
||||
# Transpiler error: "Parameter 'invalid_param' is not valid for pulsating_animation"
|
||||
|
||||
# Error: Invalid parameter name in property assignment
|
||||
animation pulse = pulsating_animation(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_animation(tail_length=-5)
|
||||
# Transpiler error: "Parameter 'tail_length' value -5 violates constraint: min=1"
|
||||
@ -318,16 +323,23 @@ color bad2 = pulsating_animation(color=red)
|
||||
animation pulse = pulsating_animation(color=undefined_color)
|
||||
# Transpiler error: "Undefined reference: 'undefined_color'"
|
||||
|
||||
# Error: Undefined animation reference
|
||||
# Error: Undefined animation reference in run statement
|
||||
run nonexistent_animation
|
||||
# Transpiler error: "Undefined reference: '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"
|
||||
```
|
||||
|
||||
### 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 or constraint violations
|
||||
- **Parameter validation**: Invalid parameter names in constructors or property assignments
|
||||
- **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
|
||||
- **Runtime errors**: Errors during Berry code execution (rare with good validation)
|
||||
|
||||
@ -25,6 +25,7 @@ class SimpleDSLTranspiler
|
||||
var first_statement # Track if we're processing the first statement
|
||||
var strip_initialized # Track if strip was initialized
|
||||
var sequence_names # Track which names are sequences
|
||||
var symbol_table # Track created objects: name -> instance
|
||||
|
||||
# Static color mapping for named colors (helps with solidification)
|
||||
static var named_colors = {
|
||||
@ -52,6 +53,7 @@ class SimpleDSLTranspiler
|
||||
self.first_statement = true # Track if we're processing the first statement
|
||||
self.strip_initialized = false # Track if strip was initialized
|
||||
self.sequence_names = {} # Track which names are sequences
|
||||
self.symbol_table = {} # Track created objects: name -> instance
|
||||
end
|
||||
|
||||
# Main transpilation method - single pass
|
||||
@ -191,14 +193,36 @@ class SimpleDSLTranspiler
|
||||
# Generate the base function call immediately
|
||||
self.add(f"var {name}_ = animation.{func_name}(engine){inline_comment}")
|
||||
|
||||
# Track this symbol in our symbol table
|
||||
var instance = self._create_instance_for_validation(func_name)
|
||||
if instance != nil
|
||||
self.symbol_table[name] = instance
|
||||
end
|
||||
|
||||
# Process named arguments with validation
|
||||
self._process_named_arguments_for_color_provider(f"{name}_", func_name)
|
||||
end
|
||||
else
|
||||
# Check if this is a simple identifier reference before processing
|
||||
var current_tok = self.current()
|
||||
var is_simple_identifier = (current_tok != nil && current_tok.type == animation_dsl.Token.IDENTIFIER &&
|
||||
(self.peek() == nil || self.peek().type != animation_dsl.Token.LEFT_PAREN))
|
||||
var ref_name = is_simple_identifier ? current_tok.value : nil
|
||||
|
||||
# Regular value assignment (simple color value)
|
||||
var value = self.process_value("color")
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f"var {name}_ = {value}{inline_comment}")
|
||||
|
||||
# If this is an identifier reference to another color provider in our symbol table,
|
||||
# add this name to the symbol table as well for compile-time validation
|
||||
if is_simple_identifier && ref_name != nil && self.symbol_table.contains(ref_name)
|
||||
var ref_instance = self.symbol_table[ref_name]
|
||||
# Only copy instances, not sequence markers
|
||||
if type(ref_instance) != "string"
|
||||
self.symbol_table[name] = ref_instance
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -323,15 +347,37 @@ class SimpleDSLTranspiler
|
||||
# Generate the base function call immediately
|
||||
self.add(f"var {name}_ = animation.{func_name}(engine){inline_comment}")
|
||||
|
||||
# Track this symbol in our symbol table
|
||||
var instance = self._create_instance_for_validation(func_name)
|
||||
if instance != nil
|
||||
self.symbol_table[name] = instance
|
||||
end
|
||||
|
||||
# Process named arguments with validation
|
||||
self._process_named_arguments_for_animation(f"{name}_", func_name)
|
||||
end
|
||||
else
|
||||
# Check if this is a simple identifier reference before processing
|
||||
var current_tok = self.current()
|
||||
var is_simple_identifier = (current_tok != nil && current_tok.type == animation_dsl.Token.IDENTIFIER &&
|
||||
(self.peek() == nil || self.peek().type != animation_dsl.Token.LEFT_PAREN))
|
||||
var ref_name = is_simple_identifier ? current_tok.value : nil
|
||||
|
||||
# Regular value assignment (identifier, color, etc.)
|
||||
var value = self.process_value("animation")
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f"var {name}_ = {value}{inline_comment}")
|
||||
|
||||
# If this is an identifier reference to another animation in our symbol table,
|
||||
# add this name to the symbol table as well for compile-time validation
|
||||
if is_simple_identifier && ref_name != nil && self.symbol_table.contains(ref_name)
|
||||
var ref_instance = self.symbol_table[ref_name]
|
||||
# Only copy instances, not sequence markers
|
||||
if type(ref_instance) != "string"
|
||||
self.symbol_table[name] = ref_instance
|
||||
end
|
||||
end
|
||||
|
||||
# Note: For identifier references, type checking happens at runtime via animation.global()
|
||||
end
|
||||
end
|
||||
@ -380,6 +426,10 @@ class SimpleDSLTranspiler
|
||||
# Track that this name is a sequence
|
||||
self.sequence_names[name] = true
|
||||
|
||||
# Also add to symbol table with a special marker for sequences
|
||||
# We use a string marker since sequences don't have real instances
|
||||
self.symbol_table[name] = "sequence"
|
||||
|
||||
self.expect_left_brace()
|
||||
|
||||
# Generate anonymous closure that creates and returns sequence manager
|
||||
@ -431,6 +481,10 @@ class SimpleDSLTranspiler
|
||||
else
|
||||
# This is an identifier reference - sequences need runtime resolution
|
||||
var anim_name = self.expect_identifier()
|
||||
|
||||
# Validate that the referenced object exists
|
||||
self._validate_object_reference(anim_name, "sequence play")
|
||||
|
||||
anim_ref = f"animation.global('{anim_name}_')"
|
||||
end
|
||||
|
||||
@ -486,6 +540,10 @@ class SimpleDSLTranspiler
|
||||
else
|
||||
# This is an identifier reference - sequences need runtime resolution
|
||||
var anim_name = self.expect_identifier()
|
||||
|
||||
# Validate that the referenced object exists
|
||||
self._validate_object_reference(anim_name, "sequence play")
|
||||
|
||||
anim_ref = f"animation.global('{anim_name}_')"
|
||||
end
|
||||
|
||||
@ -518,6 +576,10 @@ class SimpleDSLTranspiler
|
||||
def process_run()
|
||||
self.next() # skip 'run'
|
||||
var name = self.expect_identifier()
|
||||
|
||||
# Validate that the referenced object exists
|
||||
self._validate_object_reference(name, "run")
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
|
||||
# Store run statement for later processing
|
||||
@ -535,6 +597,23 @@ class SimpleDSLTranspiler
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.DOT
|
||||
self.next() # skip '.'
|
||||
var property_name = self.expect_identifier()
|
||||
|
||||
# Validate parameter if we have this object in our symbol table
|
||||
if self.symbol_table.contains(object_name)
|
||||
var instance = self.symbol_table[object_name]
|
||||
|
||||
# Only validate parameters for actual instances, not sequence markers
|
||||
if type(instance) != "string"
|
||||
var class_name = classname(instance)
|
||||
|
||||
# Use the existing parameter validation logic
|
||||
self._validate_single_parameter(class_name, property_name, instance)
|
||||
else
|
||||
# This is a sequence marker - sequences don't have properties
|
||||
self.error(f"Sequences like '{object_name}' do not have properties. Property assignments are only valid for animations and color providers.")
|
||||
end
|
||||
end
|
||||
|
||||
self.expect_assign()
|
||||
var value = self.process_value("property")
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
@ -703,11 +782,46 @@ class SimpleDSLTranspiler
|
||||
return self.process_array_literal()
|
||||
end
|
||||
|
||||
# Identifier - could be color, animation, or variable
|
||||
# Identifier - could be color, animation, variable, or object property reference
|
||||
if tok.type == animation_dsl.Token.IDENTIFIER
|
||||
var name = tok.value
|
||||
self.next()
|
||||
|
||||
# Check if this is an object property reference (identifier.property)
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.DOT
|
||||
self.next() # consume '.'
|
||||
var property_name = self.expect_identifier()
|
||||
|
||||
# Validate that the property exists on the referenced object
|
||||
if self.symbol_table.contains(name)
|
||||
var instance = self.symbol_table[name]
|
||||
# Only validate parameters for actual instances, not sequence markers
|
||||
if type(instance) != "string"
|
||||
var class_name = classname(instance)
|
||||
self._validate_single_parameter(class_name, property_name, instance)
|
||||
else
|
||||
# This is a sequence marker - sequences don't have properties
|
||||
self.error(f"Sequences like '{name}' do not have properties. Property references are only valid for animations and color providers.")
|
||||
return "nil"
|
||||
end
|
||||
end
|
||||
|
||||
# Create a closure that resolves the object property at runtime
|
||||
# Use symbol resolution logic for the object reference
|
||||
import introspect
|
||||
var object_ref = ""
|
||||
if introspect.contains(animation, name)
|
||||
# Symbol exists in animation module, use it directly
|
||||
object_ref = f"animation.{name}"
|
||||
else
|
||||
# Symbol doesn't exist in animation module, use underscore suffix
|
||||
object_ref = f"{name}_"
|
||||
end
|
||||
|
||||
# Return a closure expression that will be wrapped by the caller if needed
|
||||
return f"self.resolve({object_ref}, '{property_name}')"
|
||||
end
|
||||
|
||||
# Check for palette constants
|
||||
import string
|
||||
if string.startswith(name, "PALETTE_")
|
||||
@ -823,7 +937,7 @@ class SimpleDSLTranspiler
|
||||
transformed_expr = string.replace(transformed_expr, " ", " ")
|
||||
end
|
||||
|
||||
var closure_code = f"def (self, param_name, time_ms) return ({transformed_expr}) end"
|
||||
var closure_code = f"def (self) return {transformed_expr} end"
|
||||
|
||||
# Return a closure value provider instance
|
||||
return f"animation.create_closure_value(engine, {closure_code})"
|
||||
@ -848,7 +962,7 @@ class SimpleDSLTranspiler
|
||||
right_expr = string.replace(right_expr, " ", " ")
|
||||
end
|
||||
|
||||
var closure_code = f"def (self, param_name, time_ms) return ({left_expr} {op} {right_expr}) end"
|
||||
var closure_code = f"def (self) return {left_expr} {op} {right_expr} end"
|
||||
|
||||
# Return a closure value provider instance
|
||||
return f"animation.create_closure_value(engine, {closure_code})"
|
||||
@ -947,7 +1061,7 @@ class SimpleDSLTranspiler
|
||||
var end_pos = underscore_pos + 1
|
||||
if end_pos >= size(result) || !self.is_identifier_char(result[end_pos])
|
||||
# Replace the variable with the resolve call
|
||||
var replacement = f"self.resolve({var_name}, param_name, time_ms)"
|
||||
var replacement = f"self.resolve({var_name})"
|
||||
var before = start_pos > 0 ? result[0..start_pos-1] : ""
|
||||
var after = end_pos < size(result) ? result[end_pos..] : ""
|
||||
result = before + replacement + after
|
||||
@ -1019,7 +1133,7 @@ class SimpleDSLTranspiler
|
||||
|
||||
if has_underscore && !has_operators && !has_paren && !has_animation_prefix
|
||||
# This looks like a simple user variable that might be a ValueProvider
|
||||
return f"self.resolve({operand}, param_name, time_ms)"
|
||||
return f"self.resolve({operand})"
|
||||
else
|
||||
# For other expressions (literals, animation module calls, complex expressions), use as-is
|
||||
return operand
|
||||
@ -1401,11 +1515,47 @@ class SimpleDSLTranspiler
|
||||
return f'"{value}"'
|
||||
end
|
||||
|
||||
# Identifier - variable reference
|
||||
# Identifier - variable reference or object property reference
|
||||
if tok.type == animation_dsl.Token.IDENTIFIER
|
||||
var name = tok.value
|
||||
self.next()
|
||||
return f"self.resolve({name}_, param_name, time_ms)"
|
||||
|
||||
# Check if this is an object property reference (identifier.property)
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.DOT
|
||||
self.next() # consume '.'
|
||||
var property_name = self.expect_identifier()
|
||||
|
||||
# Validate that the property exists on the referenced object
|
||||
if self.symbol_table.contains(name)
|
||||
var instance = self.symbol_table[name]
|
||||
# Only validate parameters for actual instances, not sequence markers
|
||||
if type(instance) != "string"
|
||||
var class_name = classname(instance)
|
||||
self._validate_single_parameter(class_name, property_name, instance)
|
||||
else
|
||||
# This is a sequence marker - sequences don't have properties
|
||||
self.error(f"Sequences like '{name}' do not have properties. Property references are only valid for animations and color providers.")
|
||||
return "nil"
|
||||
end
|
||||
end
|
||||
|
||||
# Use symbol resolution logic for the object reference
|
||||
import introspect
|
||||
var object_ref = ""
|
||||
if introspect.contains(animation, name)
|
||||
# Symbol exists in animation module, use it directly
|
||||
object_ref = f"animation.{name}"
|
||||
else
|
||||
# Symbol doesn't exist in animation module, use underscore suffix
|
||||
object_ref = f"{name}_"
|
||||
end
|
||||
|
||||
# Return a resolve call for the object property
|
||||
return f"self.resolve({object_ref}, '{property_name}')"
|
||||
end
|
||||
|
||||
# Regular variable reference
|
||||
return f"self.resolve({name}_)"
|
||||
end
|
||||
|
||||
self.error(f"Unexpected token in expression: {tok.value}")
|
||||
@ -1483,7 +1633,7 @@ class SimpleDSLTranspiler
|
||||
# Create animation instance once for parameter validation
|
||||
var animation_instance = nil
|
||||
if func_name != ""
|
||||
animation_instance = self._create_animation_instance_for_validation(func_name)
|
||||
animation_instance = self._create_instance_for_validation(func_name)
|
||||
end
|
||||
|
||||
while !self.at_end() && !self.check_right_paren()
|
||||
@ -1982,7 +2132,7 @@ class SimpleDSLTranspiler
|
||||
self.expect_left_paren()
|
||||
|
||||
# Create animation instance once for parameter validation
|
||||
var animation_instance = self._create_animation_instance_for_validation(func_name)
|
||||
var animation_instance = self._create_instance_for_validation(func_name)
|
||||
|
||||
while !self.at_end() && !self.check_right_paren()
|
||||
self.skip_whitespace_including_newlines()
|
||||
@ -2051,11 +2201,6 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
end
|
||||
|
||||
# Create animation instance for parameter validation
|
||||
def _create_animation_instance_for_validation(func_name)
|
||||
return self._create_instance_for_validation(func_name)
|
||||
end
|
||||
|
||||
# Validate a single parameter immediately as it's parsed
|
||||
#
|
||||
# @param func_name: string - Name of the animation function
|
||||
@ -2079,6 +2224,38 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
end
|
||||
|
||||
# Validate that a referenced object exists in the symbol table or animation module
|
||||
#
|
||||
# @param object_name: string - Name of the object being referenced
|
||||
# @param context: string - Context where the reference occurs (for error messages)
|
||||
def _validate_object_reference(object_name, context)
|
||||
try
|
||||
import introspect
|
||||
|
||||
# Check if object exists in symbol table (user-defined)
|
||||
if self.symbol_table.contains(object_name)
|
||||
return # Object exists, validation passed
|
||||
end
|
||||
|
||||
# Check if object exists in animation module (built-in)
|
||||
if introspect.contains(animation, object_name)
|
||||
return # Object exists, validation passed
|
||||
end
|
||||
|
||||
# Check if it's a sequence name
|
||||
if self.sequence_names.contains(object_name)
|
||||
return # Sequence exists, validation passed
|
||||
end
|
||||
|
||||
# Object not found - report error
|
||||
self.error(f"Undefined reference '{object_name}' in {context}. Make sure the object is defined before use.")
|
||||
|
||||
except .. as e, msg
|
||||
# If validation fails for any reason, just continue
|
||||
# This ensures the transpiler is robust even if validation has issues
|
||||
end
|
||||
end
|
||||
|
||||
# Validate factory function at transpile time by creating instance and checking type
|
||||
def _validate_factory_function(func_name, expected_base_class)
|
||||
try
|
||||
@ -2223,7 +2400,7 @@ class SimpleDSLTranspiler
|
||||
self.expect_left_paren()
|
||||
|
||||
# Create animation instance once for parameter validation
|
||||
var animation_instance = self._create_animation_instance_for_validation(func_name)
|
||||
var animation_instance = self._create_instance_for_validation(func_name)
|
||||
|
||||
while !self.at_end() && !self.check_right_paren()
|
||||
self.skip_whitespace_including_newlines()
|
||||
|
||||
@ -39,13 +39,19 @@ class ClosureValueProvider : animation.value_provider
|
||||
# This is equivalent to 'resolve_param' but with a shorter name
|
||||
# and available at first dereferencing of method name (hence faster)
|
||||
#
|
||||
# @param value: any - Static value or value provider instance
|
||||
# @param value: any - Static value, value provider instance, or parameterized object
|
||||
# @param param_name: string - Parameter name for specific produce_value() method lookup
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - The resolved value (static or from provider)
|
||||
def resolve(value, param_name, time_ms)
|
||||
# @return any - The resolved value (static, from provider, or from object parameter)
|
||||
def resolve(value, param_name)
|
||||
if animation.is_value_provider(value)
|
||||
return value.produce_value(param_name, time_ms)
|
||||
return value.produce_value(param_name, self.engine.time_ms)
|
||||
elif value != nil && isinstance(value, animation.parameterized_object)
|
||||
# Handle parameterized objects (animations, etc.) by accessing their parameters
|
||||
# Check that param_name is not nil to prevent runtime errors
|
||||
if param_name == nil
|
||||
raise "value_error", "Parameter name cannot be nil when resolving object parameter"
|
||||
end
|
||||
return value.get_param_value(param_name, self.engine.time_ms)
|
||||
else
|
||||
return value
|
||||
end
|
||||
|
||||
@ -38,8 +38,6 @@ class ValueProvider : animation.parameterized_object
|
||||
def produce_value(name, time_ms)
|
||||
return module("undefined") # Default behavior - return undefined
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
# Add a method to check if an object is a value provider
|
||||
|
||||
@ -9598,8 +9598,8 @@ be_local_closure(linear, /* name */
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
// compact class 'ClosureValueProvider' ktab size: 20, total: 33 (saved 104 bytes)
|
||||
static const bvalue be_ktab_class_ClosureValueProvider[20] = {
|
||||
// compact class 'ClosureValueProvider' ktab size: 26, total: 39 (saved 104 bytes)
|
||||
static const bvalue be_ktab_class_ClosureValueProvider[26] = {
|
||||
/* K0 */ be_nested_str_weak(closure),
|
||||
/* K1 */ be_nested_str_weak(_closure),
|
||||
/* K2 */ be_nested_str_weak(math),
|
||||
@ -9620,6 +9620,12 @@ static const bvalue be_ktab_class_ClosureValueProvider[20] = {
|
||||
/* K17 */ be_nested_str_weak(animation),
|
||||
/* K18 */ be_nested_str_weak(is_value_provider),
|
||||
/* K19 */ be_nested_str_weak(produce_value),
|
||||
/* K20 */ be_nested_str_weak(engine),
|
||||
/* K21 */ be_nested_str_weak(time_ms),
|
||||
/* K22 */ be_nested_str_weak(parameterized_object),
|
||||
/* K23 */ be_nested_str_weak(value_error),
|
||||
/* K24 */ be_nested_str_weak(Parameter_X20name_X20cannot_X20be_X20nil_X20when_X20resolving_X20object_X20parameter),
|
||||
/* K25 */ be_nested_str_weak(get_param_value),
|
||||
};
|
||||
|
||||
|
||||
@ -9978,8 +9984,8 @@ be_local_closure(class_ClosureValueProvider_produce_value, /* name */
|
||||
********************************************************************/
|
||||
be_local_closure(class_ClosureValueProvider_resolve, /* name */
|
||||
be_nested_proto(
|
||||
8, /* nstack */
|
||||
4, /* argc */
|
||||
7, /* nstack */
|
||||
3, /* argc */
|
||||
10, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
@ -9989,20 +9995,41 @@ be_local_closure(class_ClosureValueProvider_resolve, /* name */
|
||||
&be_ktab_class_ClosureValueProvider, /* shared constants */
|
||||
be_str_weak(resolve),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[13]) { /* code */
|
||||
0xB8122200, // 0000 GETNGBL R4 K17
|
||||
0x8C100912, // 0001 GETMET R4 R4 K18
|
||||
0x5C180200, // 0002 MOVE R6 R1
|
||||
0x7C100400, // 0003 CALL R4 2
|
||||
0x78120005, // 0004 JMPF R4 #000B
|
||||
0x8C100313, // 0005 GETMET R4 R1 K19
|
||||
0x5C180400, // 0006 MOVE R6 R2
|
||||
0x5C1C0600, // 0007 MOVE R7 R3
|
||||
0x7C100600, // 0008 CALL R4 3
|
||||
0x80040800, // 0009 RET 1 R4
|
||||
0x70020000, // 000A JMP #000C
|
||||
0x80040200, // 000B RET 1 R1
|
||||
0x80000000, // 000C RET 0
|
||||
( &(const binstruction[34]) { /* code */
|
||||
0xB80E2200, // 0000 GETNGBL R3 K17
|
||||
0x8C0C0712, // 0001 GETMET R3 R3 K18
|
||||
0x5C140200, // 0002 MOVE R5 R1
|
||||
0x7C0C0400, // 0003 CALL R3 2
|
||||
0x780E0006, // 0004 JMPF R3 #000C
|
||||
0x8C0C0313, // 0005 GETMET R3 R1 K19
|
||||
0x5C140400, // 0006 MOVE R5 R2
|
||||
0x88180114, // 0007 GETMBR R6 R0 K20
|
||||
0x88180D15, // 0008 GETMBR R6 R6 K21
|
||||
0x7C0C0600, // 0009 CALL R3 3
|
||||
0x80040600, // 000A RET 1 R3
|
||||
0x70020014, // 000B JMP #0021
|
||||
0x4C0C0000, // 000C LDNIL R3
|
||||
0x200C0203, // 000D NE R3 R1 R3
|
||||
0x780E0010, // 000E JMPF R3 #0020
|
||||
0x600C000F, // 000F GETGBL R3 G15
|
||||
0x5C100200, // 0010 MOVE R4 R1
|
||||
0xB8162200, // 0011 GETNGBL R5 K17
|
||||
0x88140B16, // 0012 GETMBR R5 R5 K22
|
||||
0x7C0C0400, // 0013 CALL R3 2
|
||||
0x780E000A, // 0014 JMPF R3 #0020
|
||||
0x4C0C0000, // 0015 LDNIL R3
|
||||
0x1C0C0403, // 0016 EQ R3 R2 R3
|
||||
0x780E0000, // 0017 JMPF R3 #0019
|
||||
0xB0062F18, // 0018 RAISE 1 K23 K24
|
||||
0x8C0C0319, // 0019 GETMET R3 R1 K25
|
||||
0x5C140400, // 001A MOVE R5 R2
|
||||
0x88180114, // 001B GETMBR R6 R0 K20
|
||||
0x88180D15, // 001C GETMBR R6 R6 K21
|
||||
0x7C0C0600, // 001D CALL R3 3
|
||||
0x80040600, // 001E RET 1 R3
|
||||
0x70020000, // 001F JMP #0021
|
||||
0x80040200, // 0020 RET 1 R1
|
||||
0x80000000, // 0021 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -191,7 +191,7 @@ def test_sequence_processing()
|
||||
|
||||
# Test basic sequence
|
||||
var basic_seq_dsl = "color custom_red = 0xFF0000\n" +
|
||||
"animation red_anim = custom_red\n" +
|
||||
"animation red_anim = solid(color=custom_red)\n" +
|
||||
"sequence demo {\n" +
|
||||
" play red_anim for 2s\n" +
|
||||
"}\n" +
|
||||
@ -208,7 +208,7 @@ def test_sequence_processing()
|
||||
|
||||
# Test repeat in sequence
|
||||
var repeat_seq_dsl = "color custom_blue = 0x0000FF\n" +
|
||||
"animation blue_anim = custom_blue\n" +
|
||||
"animation blue_anim = solid(color=custom_blue)\n" +
|
||||
"sequence test {\n" +
|
||||
" repeat 3 times:\n" +
|
||||
" play blue_anim for 1s\n" +
|
||||
@ -277,8 +277,8 @@ def test_property_assignments()
|
||||
print("Testing property assignments...")
|
||||
|
||||
var property_tests = [
|
||||
["color custom_red = 0xFF0000\nanimation red_anim = solid(color=custom_red)\nred_anim.pos = 15",
|
||||
"red_anim_.pos = 15"],
|
||||
["color custom_red = 0xFF0000\nanimation red_anim = solid(color=custom_red)\nred_anim.priority = 15",
|
||||
"red_anim_.priority = 15"],
|
||||
["animation test_anim = solid(color=red)\ntest_anim.opacity = 128",
|
||||
"test_anim_.opacity = 128"],
|
||||
["animation solid_red = solid(color=red)\nanimation pulse_anim = pulsating_animation(color=red, period=2000)\npulse_anim.priority = 5",
|
||||
|
||||
@ -198,6 +198,149 @@ class DSLParameterValidationTest
|
||||
end
|
||||
end
|
||||
|
||||
# Test valid object property references - should compile successfully
|
||||
def test_valid_object_property_references()
|
||||
var dsl_code =
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"animation red_eye = beacon_animation(color=red, pos=10)\n"
|
||||
"animation green_eye = beacon_animation(color=green, pos=red_eye.pos)\n"
|
||||
"run red_eye\n"
|
||||
"run green_eye"
|
||||
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "Valid object property references should compile successfully"
|
||||
end
|
||||
|
||||
# Check that the generated code contains the expected object reference
|
||||
if string.find(berry_code, "self.resolve(red_eye_, 'pos')") == -1
|
||||
raise "generation_error", "Generated code should contain object property reference"
|
||||
end
|
||||
end
|
||||
|
||||
# Test invalid object property references - should fail compilation
|
||||
def test_invalid_object_property_references()
|
||||
var dsl_code =
|
||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
||||
"animation red_eye = beacon_animation(color=red, pos=10)\n"
|
||||
"animation green_eye = beacon_animation(color=green, pos=red_eye.invalid_param)\n"
|
||||
"run red_eye\n"
|
||||
"run green_eye"
|
||||
|
||||
var compilation_failed = false
|
||||
var error_message = ""
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
if berry_code == nil
|
||||
compilation_failed = true
|
||||
end
|
||||
except "dsl_compilation_error" as e, msg
|
||||
compilation_failed = true
|
||||
error_message = msg
|
||||
end
|
||||
|
||||
if !compilation_failed
|
||||
raise "validation_error", "Invalid object property reference should cause compilation to fail"
|
||||
end
|
||||
|
||||
# Check that the error message mentions the invalid parameter
|
||||
if string.find(error_message, "invalid_param") == -1
|
||||
raise "error_message_error", f"Error message should mention 'invalid_param', got: {error_message}"
|
||||
end
|
||||
|
||||
# Check that the error message mentions it's a BeaconAnimation parameter issue
|
||||
if string.find(error_message, "BeaconAnimation") == -1
|
||||
raise "error_message_error", f"Error message should mention 'BeaconAnimation', got: {error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Test object property references in computed expressions - should validate parameters
|
||||
def test_object_property_references_in_expressions()
|
||||
var dsl_code =
|
||||
"set strip_len = strip_length()\n"
|
||||
"animation red_eye = beacon_animation(color=red, pos=10)\n"
|
||||
"animation blue_eye = beacon_animation(color=blue, pos=strip_len - red_eye.nonexistent)\n"
|
||||
"run red_eye\n"
|
||||
"run blue_eye"
|
||||
|
||||
var compilation_failed = false
|
||||
var error_message = ""
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
if berry_code == nil
|
||||
compilation_failed = true
|
||||
end
|
||||
except "dsl_compilation_error" as e, msg
|
||||
compilation_failed = true
|
||||
error_message = msg
|
||||
end
|
||||
|
||||
if !compilation_failed
|
||||
raise "validation_error", "Invalid object property reference in expression should cause compilation to fail"
|
||||
end
|
||||
|
||||
# Check that the error message mentions the invalid parameter
|
||||
if string.find(error_message, "nonexistent") == -1
|
||||
raise "error_message_error", f"Error message should mention 'nonexistent', got: {error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Test sequence property references - should fail with specific error
|
||||
def test_sequence_property_references()
|
||||
var dsl_code =
|
||||
"sequence demo {\n"
|
||||
" play solid(color=red) for 5s\n"
|
||||
"}\n"
|
||||
"animation test = beacon_animation(color=blue, pos=demo.pos)\n"
|
||||
"run test"
|
||||
|
||||
var compilation_failed = false
|
||||
var error_message = ""
|
||||
|
||||
try
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
if berry_code == nil
|
||||
compilation_failed = true
|
||||
end
|
||||
except "dsl_compilation_error" as e, msg
|
||||
compilation_failed = true
|
||||
error_message = msg
|
||||
end
|
||||
|
||||
if !compilation_failed
|
||||
raise "validation_error", "Sequence property reference should cause compilation to fail"
|
||||
end
|
||||
|
||||
# Check that the error message mentions sequences don't have properties
|
||||
if string.find(error_message, "Sequences") == -1 || string.find(error_message, "do not have properties") == -1
|
||||
raise "error_message_error", f"Error message should mention that sequences don't have properties, got: {error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Test valid computed object property references - should compile successfully
|
||||
def test_valid_computed_object_property_references()
|
||||
var dsl_code =
|
||||
"set strip_len = strip_length()\n"
|
||||
"animation red_eye = beacon_animation(color=red, pos=10)\n"
|
||||
"animation blue_eye = beacon_animation(color=blue, pos=strip_len - red_eye.pos)\n"
|
||||
"run red_eye\n"
|
||||
"run blue_eye"
|
||||
|
||||
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||
|
||||
if berry_code == nil
|
||||
raise "compilation_error", "Valid computed object property references should compile successfully"
|
||||
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
|
||||
raise "generation_error", "Generated code should contain computed object property reference"
|
||||
end
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_all_tests()
|
||||
print("Running DSL Parameter Validation Tests...")
|
||||
@ -212,7 +355,12 @@ class DSLParameterValidationTest
|
||||
["Mixed Parameters", / -> self.test_mixed_parameters()],
|
||||
["Nested Function Invalid Parameters", / -> self.test_nested_function_invalid_parameters()],
|
||||
["User Function Not Validated", / -> self.test_user_function_not_validated()],
|
||||
["Multiple Animations Validation", / -> self.test_multiple_animations_validation()]
|
||||
["Multiple Animations Validation", / -> self.test_multiple_animations_validation()],
|
||||
["Valid Object Property References", / -> self.test_valid_object_property_references()],
|
||||
["Invalid Object Property References", / -> self.test_invalid_object_property_references()],
|
||||
["Object Property References in Expressions", / -> self.test_object_property_references_in_expressions()],
|
||||
["Sequence Property References", / -> self.test_sequence_property_references()],
|
||||
["Valid Computed Object Property References", / -> self.test_valid_computed_object_property_references()]
|
||||
]
|
||||
|
||||
for test : tests
|
||||
|
||||
@ -146,7 +146,7 @@ def test_sequences()
|
||||
print("Testing sequences...")
|
||||
|
||||
var dsl_source = "color custom_blue = 0x0000FF\n"
|
||||
"animation blue_anim = custom_blue\n"
|
||||
"animation blue_anim = solid(color=custom_blue)\n"
|
||||
"\n"
|
||||
"sequence test_seq {\n"
|
||||
" play blue_anim for 3s\n"
|
||||
@ -179,9 +179,9 @@ def test_multiple_run_statements()
|
||||
"animation blue_anim = solid(color=custom_blue)\n" +
|
||||
"animation green_anim = solid(color=custom_green)\n" +
|
||||
"\n" +
|
||||
"red_anim.pos = 5\n" +
|
||||
"blue_anim.pos = 15\n" +
|
||||
"green_anim.pos = 25\n" +
|
||||
"red_anim.priority = 5\n" +
|
||||
"blue_anim.priority = 15\n" +
|
||||
"green_anim.priority = 25\n" +
|
||||
"\n" +
|
||||
"run red_anim\n" +
|
||||
"run blue_anim\n" +
|
||||
@ -307,7 +307,7 @@ def test_computed_values()
|
||||
assert(computed_code != nil, "Should compile computed values")
|
||||
|
||||
# Check for single resolve calls (no double wrapping)
|
||||
var expected_single_resolve = "self.abs(self.resolve(strip_len_, param_name, time_ms) / 4)"
|
||||
var expected_single_resolve = "self.abs(self.resolve(strip_len_) / 4)"
|
||||
assert(string.find(computed_code, expected_single_resolve) >= 0, "Should generate single resolve call in computed expression")
|
||||
|
||||
# Check that there are no double resolve calls
|
||||
@ -371,13 +371,13 @@ def test_computed_values()
|
||||
assert(nested_closure_count == 0, f"Should have no nested closures, found {nested_closure_count}")
|
||||
|
||||
# Verify specific complex expression patterns
|
||||
var expected_complex_tail = "self.resolve(strip_len_, param_name, time_ms) / 8 + (2 * self.resolve(strip_len_, param_name, time_ms)) - 10"
|
||||
var expected_complex_tail = "self.resolve(strip_len_) / 8 + (2 * self.resolve(strip_len_)) - 10"
|
||||
assert(string.find(complex_code, expected_complex_tail) >= 0, "Should generate correct complex tail_length expression")
|
||||
|
||||
var expected_complex_speed = "(self.resolve(base_value_, param_name, time_ms) + self.resolve(strip_len_, param_name, time_ms)) * 2.5"
|
||||
var expected_complex_speed = "(self.resolve(base_value_) + self.resolve(strip_len_)) * 2.5"
|
||||
assert(string.find(complex_code, expected_complex_speed) >= 0, "Should generate correct complex speed expression")
|
||||
|
||||
var expected_complex_priority = "self.max(1, self.min(10, self.resolve(strip_len_, param_name, time_ms) / 6))"
|
||||
var expected_complex_priority = "self.max(1, self.min(10, self.resolve(strip_len_) / 6))"
|
||||
assert(string.find(complex_code, expected_complex_priority) >= 0, "Should generate correct complex priority expression with math functions")
|
||||
|
||||
# Test simple expressions that don't need closures
|
||||
@ -658,7 +658,6 @@ def test_property_assignments()
|
||||
|
||||
var dsl_with_properties = "color custom_red = 0xFF0000\n" +
|
||||
"animation red_anim = solid(color=custom_red)\n" +
|
||||
"red_anim.pos = 15\n" +
|
||||
"red_anim.opacity = 128\n" +
|
||||
"red_anim.priority = 10"
|
||||
|
||||
@ -667,7 +666,6 @@ def test_property_assignments()
|
||||
assert(berry_code != nil, "Should generate Berry code with property assignments")
|
||||
|
||||
# Check that property assignments are generated correctly (new behavior: direct underscore access)
|
||||
assert(string.find(berry_code, "red_anim_.pos = 15") >= 0, "Should generate pos property assignment")
|
||||
assert(string.find(berry_code, "red_anim_.opacity = 128") >= 0, "Should generate opacity property assignment")
|
||||
assert(string.find(berry_code, "red_anim_.priority = 10") >= 0, "Should generate priority property assignment")
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
# ValueProvider instances for integer and real parameters.
|
||||
|
||||
import animation
|
||||
import global
|
||||
|
||||
# Test that parameters accept ValueProviders and integers only
|
||||
def test_parameter_accepts_value_providers()
|
||||
@ -197,6 +198,259 @@ def test_type_validation()
|
||||
print("✓ Type validation test passed")
|
||||
end
|
||||
|
||||
# Test DSL compile-time parameter validation
|
||||
def test_dsl_parameter_validation()
|
||||
print("Testing DSL compile-time parameter validation...")
|
||||
|
||||
import animation_dsl
|
||||
|
||||
# Test valid animation parameter
|
||||
var valid_dsl = "animation red_eye = beacon_animation(color = red)\n" +
|
||||
"red_eye.back_color = blue"
|
||||
|
||||
var result = animation_dsl.compile(valid_dsl)
|
||||
assert(result != nil, "Valid parameter should compile successfully")
|
||||
|
||||
# Test invalid animation parameter
|
||||
var invalid_dsl = "animation red_eye = beacon_animation(color = red)\n" +
|
||||
"red_eye.invalid_param = 123"
|
||||
|
||||
try
|
||||
animation_dsl.compile(invalid_dsl)
|
||||
assert(false, "Invalid parameter should cause compilation error")
|
||||
except .. as e
|
||||
# Expected - invalid parameter should be caught
|
||||
assert(true, "Invalid parameter correctly rejected")
|
||||
end
|
||||
|
||||
# Test valid color provider parameter
|
||||
var valid_color_dsl = "color solid_red = static_color(color = red)\n" +
|
||||
"solid_red.color = blue"
|
||||
|
||||
var result2 = animation_dsl.compile(valid_color_dsl)
|
||||
assert(result2 != nil, "Valid color provider parameter should compile successfully")
|
||||
|
||||
# Test invalid color provider parameter
|
||||
var invalid_color_dsl = "color solid_red = static_color(color = red)\n" +
|
||||
"solid_red.invalid_param = 123"
|
||||
|
||||
try
|
||||
animation_dsl.compile(invalid_color_dsl)
|
||||
assert(false, "Invalid color provider parameter should cause compilation error")
|
||||
except .. as e
|
||||
# Expected - invalid parameter should be caught
|
||||
assert(true, "Invalid color provider parameter correctly rejected")
|
||||
end
|
||||
|
||||
# Test unknown objects skip validation (no error)
|
||||
var unknown_dsl = "unknown_object.some_param = 123"
|
||||
var result3 = animation_dsl.compile(unknown_dsl)
|
||||
assert(result3 != nil, "Unknown objects should not cause validation errors")
|
||||
|
||||
print("✓ DSL compile-time parameter validation test passed")
|
||||
end
|
||||
|
||||
# Test DSL object reference validation
|
||||
def test_dsl_object_reference_validation()
|
||||
print("Testing DSL object reference validation...")
|
||||
|
||||
import animation_dsl
|
||||
|
||||
# Test valid run statement
|
||||
var valid_run = "animation red_eye = beacon_animation(color = red)\n" +
|
||||
"run red_eye"
|
||||
|
||||
var result = animation_dsl.compile(valid_run)
|
||||
assert(result != nil, "Valid run statement should compile successfully")
|
||||
|
||||
# Test invalid run statement (undefined object)
|
||||
var invalid_run = "animation red_eye = beacon_animation(color = red)\n" +
|
||||
"run undefined_animation"
|
||||
|
||||
try
|
||||
animation_dsl.compile(invalid_run)
|
||||
assert(false, "Invalid run statement should cause compilation error")
|
||||
except .. as e
|
||||
# Expected - undefined reference should be caught
|
||||
assert(true, "Undefined reference in run statement correctly rejected")
|
||||
end
|
||||
|
||||
# Test valid sequence with play statement
|
||||
var valid_sequence = "animation red_eye = beacon_animation(color = red)\n" +
|
||||
"sequence demo {\n" +
|
||||
" play red_eye for 5s\n" +
|
||||
" wait 1s\n" +
|
||||
"}"
|
||||
|
||||
var result2 = animation_dsl.compile(valid_sequence)
|
||||
assert(result2 != nil, "Valid sequence should compile successfully")
|
||||
|
||||
# Test invalid sequence with undefined play reference
|
||||
var invalid_sequence = "animation red_eye = beacon_animation(color = red)\n" +
|
||||
"sequence demo {\n" +
|
||||
" play undefined_animation for 5s\n" +
|
||||
" wait 1s\n" +
|
||||
"}"
|
||||
|
||||
try
|
||||
animation_dsl.compile(invalid_sequence)
|
||||
assert(false, "Invalid sequence should cause compilation error")
|
||||
except .. as e
|
||||
# Expected - undefined reference should be caught
|
||||
assert(true, "Undefined reference in sequence play statement correctly rejected")
|
||||
end
|
||||
|
||||
print("✓ DSL object reference validation test passed")
|
||||
end
|
||||
|
||||
# Test DSL sequence symbol table registration
|
||||
def test_dsl_sequence_symbol_table_registration()
|
||||
print("Testing DSL sequence symbol table registration...")
|
||||
|
||||
import animation_dsl
|
||||
|
||||
# Test 1: Valid sequence should be registered and runnable
|
||||
var valid_sequence_dsl = "animation red_anim = beacon_animation(color = red)\n" +
|
||||
"sequence demo {\n" +
|
||||
" play red_anim for 2s\n" +
|
||||
"}\n" +
|
||||
"run demo"
|
||||
|
||||
var result = animation_dsl.compile(valid_sequence_dsl)
|
||||
assert(result != nil, "Valid sequence should compile successfully")
|
||||
|
||||
# Test 2: Sequence with invalid animation reference should fail at sequence processing
|
||||
var invalid_anim_sequence_dsl = "animation red_anim = nonexistent_function(color = red)\n" +
|
||||
"sequence demo {\n" +
|
||||
" play red_anim for 2s\n" +
|
||||
"}\n" +
|
||||
"run demo"
|
||||
|
||||
try
|
||||
animation_dsl.compile(invalid_anim_sequence_dsl)
|
||||
assert(false, "Invalid animation reference should cause compilation error")
|
||||
except .. as e
|
||||
# Expected - invalid animation should be caught
|
||||
assert(true, "Invalid animation reference correctly rejected")
|
||||
end
|
||||
|
||||
# Test 3: Sequence with undefined identifier should fail
|
||||
var undefined_identifier_dsl = "sequence demo {\n" +
|
||||
" play undefined_anim for 2s\n" +
|
||||
"}\n" +
|
||||
"run demo"
|
||||
|
||||
try
|
||||
animation_dsl.compile(undefined_identifier_dsl)
|
||||
assert(false, "Undefined identifier in sequence should cause compilation error")
|
||||
except .. as e
|
||||
# Expected - undefined identifier should be caught
|
||||
assert(true, "Undefined identifier in sequence correctly rejected")
|
||||
end
|
||||
|
||||
print("✓ DSL sequence symbol table registration test passed")
|
||||
end
|
||||
|
||||
# Test DSL symbol table mixed types handling
|
||||
def test_dsl_symbol_table_mixed_types()
|
||||
print("Testing DSL symbol table mixed types handling...")
|
||||
|
||||
import animation_dsl
|
||||
|
||||
# Test 1: Valid property assignment on animation (instance in symbol table)
|
||||
var animation_property_dsl = "animation red_anim = beacon_animation(color = red)\n" +
|
||||
"red_anim.back_color = blue"
|
||||
|
||||
var result1 = animation_dsl.compile(animation_property_dsl)
|
||||
assert(result1 != nil, "Animation property assignment should work")
|
||||
|
||||
# Test 2: Valid property assignment on color provider (instance in symbol table)
|
||||
var color_property_dsl = "color solid_red = static_color(color = red)\n" +
|
||||
"solid_red.color = blue"
|
||||
|
||||
var result2 = animation_dsl.compile(color_property_dsl)
|
||||
assert(result2 != nil, "Color provider property assignment should work")
|
||||
|
||||
# Test 3: Invalid property assignment on sequence (string in symbol table)
|
||||
var sequence_property_dsl = "animation red_anim = beacon_animation(color = red)\n" +
|
||||
"sequence demo {\n" +
|
||||
" play red_anim for 2s\n" +
|
||||
"}\n" +
|
||||
"demo.invalid_property = 123"
|
||||
|
||||
try
|
||||
animation_dsl.compile(sequence_property_dsl)
|
||||
assert(false, "Sequence property assignment should cause compilation error")
|
||||
except .. as e
|
||||
# Expected - sequence property assignment should be rejected
|
||||
assert(true, "Sequence property assignment correctly rejected")
|
||||
end
|
||||
|
||||
# Test 4: Mixed symbol table with sequences and instances
|
||||
var mixed_dsl = "animation red_anim = beacon_animation(color = red)\n" +
|
||||
"color solid_blue = static_color(color = blue)\n" +
|
||||
"sequence demo {\n" +
|
||||
" play red_anim for 2s\n" +
|
||||
"}\n" +
|
||||
"red_anim.back_color = solid_blue\n" +
|
||||
"run demo"
|
||||
|
||||
var result4 = animation_dsl.compile(mixed_dsl)
|
||||
assert(result4 != nil, "Mixed symbol table operations should work")
|
||||
|
||||
print("✓ DSL symbol table mixed types handling test passed")
|
||||
end
|
||||
|
||||
# Test DSL identifier reference symbol table registration
|
||||
def test_dsl_identifier_reference_symbol_table()
|
||||
print("Testing DSL identifier reference symbol table registration...")
|
||||
|
||||
import animation_dsl
|
||||
|
||||
# Test 1: Animation reference should be added to symbol table
|
||||
var animation_ref_dsl = "animation solid_red = solid(color=red)\n" +
|
||||
"animation red_anim = solid_red\n" +
|
||||
"sequence demo {\n" +
|
||||
" play red_anim for 2s\n" +
|
||||
"}\n" +
|
||||
"run demo"
|
||||
|
||||
var result1 = animation_dsl.compile(animation_ref_dsl)
|
||||
assert(result1 != nil, "Animation reference should compile successfully")
|
||||
|
||||
# Test 2: Parameter validation on referenced animation
|
||||
var param_validation_dsl = "animation solid_red = solid(color=red)\n" +
|
||||
"animation red_anim = solid_red\n" +
|
||||
"red_anim.color = blue"
|
||||
|
||||
var result2 = animation_dsl.compile(param_validation_dsl)
|
||||
assert(result2 != nil, "Parameter validation on referenced animation should work")
|
||||
|
||||
# Test 3: Color provider reference should be added to symbol table
|
||||
var color_ref_dsl = "color base_red = static_color(color=red)\n" +
|
||||
"color my_red = base_red\n" +
|
||||
"animation red_anim = solid(color=my_red)\n" +
|
||||
"my_red.color = blue"
|
||||
|
||||
var result3 = animation_dsl.compile(color_ref_dsl)
|
||||
assert(result3 != nil, "Color provider reference should work")
|
||||
|
||||
# Test 4: Invalid parameter on referenced animation should fail
|
||||
var invalid_param_dsl = "animation solid_red = solid(color=red)\n" +
|
||||
"animation red_anim = solid_red\n" +
|
||||
"red_anim.invalid_param = 123"
|
||||
|
||||
try
|
||||
animation_dsl.compile(invalid_param_dsl)
|
||||
assert(false, "Invalid parameter on referenced animation should cause compilation error")
|
||||
except .. as e
|
||||
# Expected - invalid parameter should be caught
|
||||
assert(true, "Invalid parameter on referenced animation correctly rejected")
|
||||
end
|
||||
|
||||
print("✓ DSL identifier reference symbol table registration test passed")
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_parameter_validation_tests()
|
||||
print("=== Parameter Validation System Tests ===")
|
||||
@ -207,6 +461,11 @@ def run_parameter_validation_tests()
|
||||
test_range_validation()
|
||||
test_range_validation_with_providers()
|
||||
test_type_validation()
|
||||
test_dsl_parameter_validation()
|
||||
test_dsl_object_reference_validation()
|
||||
test_dsl_sequence_symbol_table_registration()
|
||||
test_dsl_symbol_table_mixed_types()
|
||||
test_dsl_identifier_reference_symbol_table()
|
||||
|
||||
print("=== All parameter validation tests passed! ===")
|
||||
return true
|
||||
|
||||
@ -153,7 +153,7 @@ def test_math_method_transpilation()
|
||||
"set x = 10\n"
|
||||
"set y = 20\n"
|
||||
"animation wave = pulsating_animation(color=blue, period=3s)\n"
|
||||
"wave.brightness = max(min(x, y), sqrt(abs(x - y)))\n"
|
||||
"wave.min_brightness = max(min(x, y), sqrt(abs(x - y)))\n"
|
||||
"run wave"
|
||||
|
||||
var result2 = test_transpilation_case(dsl_code2, ["max", "min", "sqrt", "abs"], "Multiple math functions")
|
||||
@ -165,7 +165,7 @@ def test_math_method_transpilation()
|
||||
var dsl_code3 =
|
||||
"set angle = 45\n"
|
||||
"animation rotate = pulsating_animation(color=green, period=2s)\n"
|
||||
"rotate.brightness = round(sin(angle) * 180 + cos(angle) * 90)\n"
|
||||
"rotate.min_brightness = round(sin(angle) * 180 + cos(angle) * 90)\n"
|
||||
"run rotate"
|
||||
|
||||
var result3 = test_transpilation_case(dsl_code3, ["round", "sin", "cos"], "Complex math expressions")
|
||||
@ -176,7 +176,7 @@ def test_math_method_transpilation()
|
||||
# Test case 4: Ensure non-math functions are NOT prefixed with self.
|
||||
var dsl_code4 =
|
||||
"animation pulse = pulsating_animation(color=red, period=2s)\n"
|
||||
"pulse.brightness = scale(50, 0, 100)\n"
|
||||
"pulse.min_brightness = scale(50, 0, 100)\n"
|
||||
"run pulse"
|
||||
|
||||
var result4 = test_non_math_functions(dsl_code4)
|
||||
|
||||
@ -140,7 +140,7 @@ def test_generated_code_validity()
|
||||
var dsl_code =
|
||||
"animation random_multi = solid(color=cyan, priority=12)\n"
|
||||
"random_multi.opacity = rand_demo()\n"
|
||||
"random_multi.brightness = max(100, rand_demo())\n"
|
||||
"random_multi.duration = max(100, rand_demo())\n"
|
||||
"run random_multi"
|
||||
|
||||
try
|
||||
|
||||
Loading…
Reference in New Issue
Block a user