Berry animation DSL: fix transpilation of complex expressions (#23829)
This commit is contained in:
parent
57d2225a97
commit
9aaed29a54
@ -12,8 +12,8 @@ animation comet_main = comet_animation(
|
||||
color=0xFFFFFF # White head
|
||||
tail_length=10 # tail length
|
||||
speed=2s # speed
|
||||
priority = 7
|
||||
)
|
||||
comet_main.priority = 7
|
||||
|
||||
# Secondary comet in different color, opposite direction
|
||||
animation comet_secondary = comet_animation(
|
||||
@ -21,16 +21,16 @@ animation comet_secondary = comet_animation(
|
||||
tail_length=8 # shorter tail
|
||||
speed=3s # slower speed
|
||||
direction=-1 # other direction
|
||||
priority = 5
|
||||
)
|
||||
comet_secondary.priority = 5
|
||||
|
||||
# Add sparkle trail behind comets but on top of blue background
|
||||
animation comet_sparkles = twinkle_animation(
|
||||
color=0xAAAAFF # Light blue sparkles
|
||||
density=8 # density (moderate sparkles)
|
||||
twinkle_speed=400ms # twinkle speed (quick sparkle)
|
||||
priority = 8
|
||||
)
|
||||
comet_sparkles.priority = 8
|
||||
|
||||
# Start all animations
|
||||
run background
|
||||
|
||||
@ -20,8 +20,8 @@
|
||||
# color=0xFFFFFF # White head
|
||||
# tail_length=10 # tail length
|
||||
# speed=2s # speed
|
||||
# priority = 7
|
||||
# )
|
||||
# comet_main.priority = 7
|
||||
#
|
||||
# # Secondary comet in different color, opposite direction
|
||||
# animation comet_secondary = comet_animation(
|
||||
@ -29,16 +29,16 @@
|
||||
# tail_length=8 # shorter tail
|
||||
# speed=3s # slower speed
|
||||
# direction=-1 # other direction
|
||||
# priority = 5
|
||||
# )
|
||||
# comet_secondary.priority = 5
|
||||
#
|
||||
# # Add sparkle trail behind comets but on top of blue background
|
||||
# animation comet_sparkles = twinkle_animation(
|
||||
# color=0xAAAAFF # Light blue sparkles
|
||||
# density=8 # density (moderate sparkles)
|
||||
# twinkle_speed=400ms # twinkle speed (quick sparkle)
|
||||
# priority = 8
|
||||
# )
|
||||
# comet_sparkles.priority = 8
|
||||
#
|
||||
# # Start all animations
|
||||
# run background
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
# set base_speed = 2.0
|
||||
# animation stream2 = comet_animation(
|
||||
# color=blue
|
||||
# tail_length=strip_len / 8 + 2 # computed with addition
|
||||
# tail_length=strip_len / 8 + (2 * strip_len) -10 # computed with addition
|
||||
# speed=base_speed * 1.5 # computed with multiplication
|
||||
# direction=-1
|
||||
# priority=5
|
||||
@ -51,14 +51,14 @@ var strip_len_ = temp_strip_length_11
|
||||
# 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(self.resolve(strip_len_, param_name, time_ms), param_name, time_ms) / 4)) end) # computed value
|
||||
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_.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) end) # computed with addition
|
||||
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_.direction = (-1)
|
||||
stream2_.priority = 5
|
||||
|
||||
@ -16,7 +16,7 @@ animation stream1 = comet_animation(
|
||||
set base_speed = 2.0
|
||||
animation stream2 = comet_animation(
|
||||
color=blue
|
||||
tail_length=strip_len / 8 + 2 # computed with addition
|
||||
tail_length=strip_len / 8 + (2 * strip_len) -10 # computed with addition
|
||||
speed=base_speed * 1.5 # computed with multiplication
|
||||
direction=-1
|
||||
priority=5
|
||||
|
||||
@ -561,32 +561,32 @@ class SimpleDSLTranspiler
|
||||
|
||||
# Process any value - unified approach
|
||||
def process_value(context)
|
||||
return self.process_expression(context)
|
||||
return self.process_expression(context) # This calls process_additive_expression with is_top_level=true
|
||||
end
|
||||
|
||||
# Process expressions with arithmetic operations
|
||||
def process_expression(context)
|
||||
return self.process_additive_expression(context)
|
||||
return self.process_additive_expression(context, true) # true = top-level expression
|
||||
end
|
||||
|
||||
# Process additive expressions (+ and -)
|
||||
def process_additive_expression(context)
|
||||
var left = self.process_multiplicative_expression(context)
|
||||
def process_additive_expression(context, is_top_level)
|
||||
var left = self.process_multiplicative_expression(context, is_top_level)
|
||||
|
||||
while !self.at_end()
|
||||
var tok = self.current()
|
||||
if tok != nil && (tok.type == animation_dsl.Token.PLUS || tok.type == animation_dsl.Token.MINUS)
|
||||
var op = tok.value
|
||||
self.next() # consume operator
|
||||
var right = self.process_multiplicative_expression(context)
|
||||
var right = self.process_multiplicative_expression(context, false) # sub-expressions are not top-level
|
||||
left = f"{left} {op} {right}"
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the entire expression needs a closure (after building the full expression)
|
||||
if self.is_computed_expression_string(left)
|
||||
# Only create closures at the top level
|
||||
if is_top_level && self.is_computed_expression_string(left)
|
||||
return self.create_computation_closure_from_string(left)
|
||||
else
|
||||
return left
|
||||
@ -594,15 +594,15 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
|
||||
# Process multiplicative expressions (* and /)
|
||||
def process_multiplicative_expression(context)
|
||||
var left = self.process_unary_expression(context)
|
||||
def process_multiplicative_expression(context, is_top_level)
|
||||
var left = self.process_unary_expression(context, is_top_level)
|
||||
|
||||
while !self.at_end()
|
||||
var tok = self.current()
|
||||
if tok != nil && (tok.type == animation_dsl.Token.MULTIPLY || tok.type == animation_dsl.Token.DIVIDE)
|
||||
var op = tok.value
|
||||
self.next() # consume operator
|
||||
var right = self.process_unary_expression(context)
|
||||
var right = self.process_unary_expression(context, false) # sub-expressions are not top-level
|
||||
left = f"{left} {op} {right}"
|
||||
else
|
||||
break
|
||||
@ -613,7 +613,7 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
|
||||
# Process unary expressions (- and +)
|
||||
def process_unary_expression(context)
|
||||
def process_unary_expression(context, is_top_level)
|
||||
var tok = self.current()
|
||||
if tok == nil
|
||||
self.error("Expected value")
|
||||
@ -623,21 +623,21 @@ class SimpleDSLTranspiler
|
||||
# Handle unary minus for negative numbers
|
||||
if tok.type == animation_dsl.Token.MINUS
|
||||
self.next() # consume the minus
|
||||
var expr = self.process_unary_expression(context)
|
||||
var expr = self.process_unary_expression(context, false) # sub-expressions are not top-level
|
||||
return f"(-{expr})"
|
||||
end
|
||||
|
||||
# Handle unary plus (optional)
|
||||
if tok.type == animation_dsl.Token.PLUS
|
||||
self.next() # consume the plus
|
||||
return self.process_unary_expression(context)
|
||||
return self.process_unary_expression(context, false) # sub-expressions are not top-level
|
||||
end
|
||||
|
||||
return self.process_primary_expression(context)
|
||||
return self.process_primary_expression(context, is_top_level)
|
||||
end
|
||||
|
||||
# Process primary expressions (literals, identifiers, function calls, parentheses)
|
||||
def process_primary_expression(context)
|
||||
def process_primary_expression(context, is_top_level)
|
||||
var tok = self.current()
|
||||
if tok == nil
|
||||
self.error("Expected value")
|
||||
@ -647,7 +647,7 @@ class SimpleDSLTranspiler
|
||||
# Parenthesized expression
|
||||
if tok.type == animation_dsl.Token.LEFT_PAREN
|
||||
self.next() # consume '('
|
||||
var expr = self.process_expression(context)
|
||||
var expr = self.process_additive_expression(context, false) # parenthesized expressions are not top-level
|
||||
self.expect_right_paren()
|
||||
return f"({expr})"
|
||||
end
|
||||
@ -894,15 +894,22 @@ class SimpleDSLTranspiler
|
||||
start_pos -= 1
|
||||
end
|
||||
|
||||
# Check if this is a user variable (not preceded by "animation." or "self.")
|
||||
# Check if this is a user variable (not preceded by "animation." or "self." or already inside a resolve call)
|
||||
var is_user_var = true
|
||||
if start_pos >= 10
|
||||
if start_pos >= 13
|
||||
var check_start = start_pos >= 13 ? start_pos - 13 : 0
|
||||
var prefix = result[check_start..start_pos-1]
|
||||
if string.find(prefix, "self.resolve(") >= 0
|
||||
is_user_var = false
|
||||
end
|
||||
end
|
||||
if is_user_var && start_pos >= 10
|
||||
var check_start = start_pos >= 10 ? start_pos - 10 : 0
|
||||
var prefix = result[check_start..start_pos-1]
|
||||
if string.find(prefix, "animation.") >= 0 || string.find(prefix, "self.") >= 0
|
||||
is_user_var = false
|
||||
end
|
||||
elif start_pos >= 5
|
||||
elif is_user_var && start_pos >= 5
|
||||
var check_start = start_pos >= 5 ? start_pos - 5 : 0
|
||||
var prefix = result[check_start..start_pos-1]
|
||||
if string.find(prefix, "self.") >= 0
|
||||
|
||||
@ -187,7 +187,7 @@ end
|
||||
# @param closure: function - the closure to evaluate at run-time
|
||||
# @return ClosureValueProvider - New ClosureValueProvider instance
|
||||
def create_closure_value(engine, closure)
|
||||
var provider = animation.closure_value(engine)
|
||||
var provider = ClosureValueProvider(engine)
|
||||
provider.closure = closure
|
||||
return provider
|
||||
end
|
||||
|
||||
@ -6794,34 +6794,33 @@ be_local_closure(shift_fast_scroll, /* name */
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
|
||||
--> Unsupported upvals in closure in 'create_closure_value' <---
|
||||
/********************************************************************
|
||||
** Solidified function: create_closure_value
|
||||
********************************************************************/
|
||||
be_local_closure(create_closure_value, /* name */
|
||||
be_nested_proto(
|
||||
5, /* nstack */
|
||||
4, /* nstack */
|
||||
2, /* argc */
|
||||
0, /* varg */
|
||||
0, /* has upvals */
|
||||
NULL, /* no upvals */
|
||||
1, /* has upvals */
|
||||
( &(const bupvaldesc[ 1]) { /* upvals */
|
||||
be_local_const_upval(1, 0),
|
||||
}),
|
||||
0, /* has sup protos */
|
||||
NULL, /* no sub protos */
|
||||
1, /* has constants */
|
||||
( &(const bvalue[ 3]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(animation),
|
||||
/* K1 */ be_nested_str_weak(closure_value),
|
||||
/* K2 */ be_nested_str_weak(closure),
|
||||
( &(const bvalue[ 1]) { /* constants */
|
||||
/* K0 */ be_nested_str_weak(closure),
|
||||
}),
|
||||
be_str_weak(create_closure_value),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[ 6]) { /* code */
|
||||
0xB80A0000, // 0000 GETNGBL R2 K0
|
||||
0x8C080501, // 0001 GETMET R2 R2 K1
|
||||
0x5C100000, // 0002 MOVE R4 R0
|
||||
0x7C080400, // 0003 CALL R2 2
|
||||
0x900A0401, // 0004 SETMBR R2 K2 R1
|
||||
0x80040400, // 0005 RET 1 R2
|
||||
( &(const binstruction[ 5]) { /* code */
|
||||
0x68080000, // 0000 GETUPV R2 U0
|
||||
0x5C0C0000, // 0001 MOVE R3 R0
|
||||
0x7C080200, // 0002 CALL R2 1
|
||||
0x900A0001, // 0003 SETMBR R2 K0 R1
|
||||
0x80040400, // 0004 RET 1 R2
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -290,6 +290,132 @@ def test_variable_assignments()
|
||||
return true
|
||||
end
|
||||
|
||||
# Test computed values and expressions (regression tests)
|
||||
def test_computed_values()
|
||||
print("Testing computed values and expressions...")
|
||||
|
||||
# Test computed values with single resolve calls (regression test for double resolve issue)
|
||||
var computed_dsl = "set strip_len = strip_length()\n" +
|
||||
"animation stream1 = comet_animation(\n" +
|
||||
" color=red\n" +
|
||||
" tail_length=abs(strip_len / 4)\n" +
|
||||
" speed=1.5\n" +
|
||||
" priority=10\n" +
|
||||
")"
|
||||
|
||||
var computed_code = animation_dsl.compile(computed_dsl)
|
||||
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)"
|
||||
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
|
||||
var double_resolve_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
pos = string.find(computed_code, "self.resolve(self.resolve(", pos)
|
||||
if pos < 0
|
||||
break
|
||||
end
|
||||
double_resolve_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(double_resolve_count == 0, f"Should have no double resolve calls, found {double_resolve_count}")
|
||||
|
||||
# Test complex expressions with single closure (regression test for nested closure issue)
|
||||
var complex_expr_dsl = "set strip_len = strip_length()\n" +
|
||||
"set base_value = 5\n" +
|
||||
"animation stream2 = comet_animation(\n" +
|
||||
" color=blue\n" +
|
||||
" tail_length=strip_len / 8 + (2 * strip_len) - 10\n" +
|
||||
" speed=(base_value + strip_len) * 2.5\n" +
|
||||
" priority=max(1, min(10, strip_len / 6))\n" +
|
||||
")"
|
||||
|
||||
var complex_code = animation_dsl.compile(complex_expr_dsl)
|
||||
assert(complex_code != nil, "Should compile complex expressions")
|
||||
|
||||
# Count closure creations - each computed parameter should have exactly one closure
|
||||
var closure_count = 0
|
||||
pos = 0
|
||||
while true
|
||||
pos = string.find(complex_code, "animation.create_closure_value(", pos)
|
||||
if pos < 0
|
||||
break
|
||||
end
|
||||
closure_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(closure_count == 3, f"Should have exactly 3 closures for 3 computed parameters, found {closure_count}")
|
||||
|
||||
# Check that complex expressions are in single closures (no nested closures)
|
||||
var nested_closure_count = 0
|
||||
pos = 0
|
||||
while true
|
||||
# Look for closure inside closure pattern
|
||||
var closure_start = string.find(complex_code, "animation.create_closure_value(", pos)
|
||||
if closure_start < 0
|
||||
break
|
||||
end
|
||||
var closure_end = string.find(complex_code, ") end)", closure_start)
|
||||
if closure_end < 0
|
||||
break
|
||||
end
|
||||
var closure_content = complex_code[closure_start..closure_end]
|
||||
if string.find(closure_content, "animation.create_closure_value(") > 0
|
||||
nested_closure_count += 1
|
||||
end
|
||||
pos = closure_end + 1
|
||||
end
|
||||
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"
|
||||
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"
|
||||
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))"
|
||||
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
|
||||
var simple_expr_dsl = "set strip_len = strip_length()\n" +
|
||||
"animation simple = comet_animation(\n" +
|
||||
" color=red\n" +
|
||||
" tail_length=strip_len\n" +
|
||||
" speed=1.5\n" +
|
||||
" priority=10\n" +
|
||||
")"
|
||||
|
||||
var simple_code = animation_dsl.compile(simple_expr_dsl)
|
||||
assert(simple_code != nil, "Should compile simple expressions")
|
||||
|
||||
# Simple variable reference should not create a closure
|
||||
assert(string.find(simple_code, "simple_.tail_length = strip_len_") >= 0, "Should generate direct variable reference without closure")
|
||||
|
||||
# Test mathematical functions in computed expressions
|
||||
var math_expr_dsl = "set strip_len = strip_length()\n" +
|
||||
"animation math_test = comet_animation(\n" +
|
||||
" color=red\n" +
|
||||
" tail_length=max(1, min(strip_len, 20))\n" +
|
||||
" speed=abs(strip_len - 30)\n" +
|
||||
" priority=round(strip_len / 6)\n" +
|
||||
")"
|
||||
|
||||
var math_code = animation_dsl.compile(math_expr_dsl)
|
||||
assert(math_code != nil, "Should compile mathematical expressions")
|
||||
|
||||
# Check that mathematical functions are prefixed with self. in closures
|
||||
assert(string.find(math_code, "self.max(1, self.min(") >= 0, "Should prefix math functions with self. in closures")
|
||||
assert(string.find(math_code, "self.abs(") >= 0, "Should prefix abs function with self. in closures")
|
||||
assert(string.find(math_code, "self.round(") >= 0, "Should prefix round function with self. in closures")
|
||||
|
||||
print("✓ Computed values test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test error handling
|
||||
def test_error_handling()
|
||||
print("Testing error handling...")
|
||||
@ -758,6 +884,7 @@ def run_dsl_transpiler_tests()
|
||||
test_sequences,
|
||||
test_multiple_run_statements,
|
||||
test_variable_assignments,
|
||||
test_computed_values,
|
||||
test_error_handling,
|
||||
test_forward_references,
|
||||
test_complex_dsl,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user