Berry animation transpiler refactoring phase 2 (#23900)
This commit is contained in:
parent
7315b06969
commit
86e48395c8
File diff suppressed because it is too large
Load Diff
@ -37,9 +37,11 @@ red_eye_.pos = cosine_val_ # oscillator for position
|
|||||||
red_eye_.beacon_size = 3 # small 3 pixels eye
|
red_eye_.beacon_size = 3 # small 3 pixels eye
|
||||||
red_eye_.slew_size = 2 # with 2 pixel shading around
|
red_eye_.slew_size = 2 # with 2 pixel shading around
|
||||||
var cylon_eye_ = animation.SequenceManager(engine, -1)
|
var cylon_eye_ = animation.SequenceManager(engine, -1)
|
||||||
.push_play_step(red_eye_, eye_duration_) # use COSINE movement
|
.push_closure_step(def (engine) cosine_val_.start(engine.time_ms) end)
|
||||||
|
.push_play_step(red_eye_, animation.resolve(eye_duration_)) # use COSINE movement
|
||||||
.push_closure_step(def (engine) red_eye_.pos = triangle_val_ end) # switch to TRIANGLE
|
.push_closure_step(def (engine) red_eye_.pos = triangle_val_ end) # switch to TRIANGLE
|
||||||
.push_play_step(red_eye_, eye_duration_)
|
.push_closure_step(def (engine) triangle_val_.start(engine.time_ms) end)
|
||||||
|
.push_play_step(red_eye_, animation.resolve(eye_duration_))
|
||||||
.push_closure_step(def (engine) red_eye_.pos = cosine_val_ end) # switch back to COSINE for next iteration
|
.push_closure_step(def (engine) red_eye_.pos = cosine_val_ end) # switch back to COSINE for next iteration
|
||||||
.push_closure_step(def (engine) eye_color_.next = 1 end) # advance to next color
|
.push_closure_step(def (engine) eye_color_.next = 1 end) # advance to next color
|
||||||
engine.add(cylon_eye_)
|
engine.add(cylon_eye_)
|
||||||
@ -69,8 +71,10 @@ animation red_eye = beacon_animation(
|
|||||||
)
|
)
|
||||||
|
|
||||||
sequence cylon_eye forever {
|
sequence cylon_eye forever {
|
||||||
|
restart cosine_val
|
||||||
play red_eye for eye_duration # use COSINE movement
|
play red_eye for eye_duration # use COSINE movement
|
||||||
red_eye.pos = triangle_val # switch to TRIANGLE
|
red_eye.pos = triangle_val # switch to TRIANGLE
|
||||||
|
restart triangle_val
|
||||||
play red_eye for eye_duration
|
play red_eye for eye_duration
|
||||||
red_eye.pos = cosine_val # switch back to COSINE for next iteration
|
red_eye.pos = cosine_val # switch back to COSINE for next iteration
|
||||||
eye_color.next = 1 # advance to next color
|
eye_color.next = 1 # advance to next color
|
||||||
|
|||||||
@ -38,7 +38,7 @@ shutter_animation_.priority = 5
|
|||||||
log(f"foobar", 3)
|
log(f"foobar", 3)
|
||||||
var shutter_run_ = animation.SequenceManager(engine, -1)
|
var shutter_run_ = animation.SequenceManager(engine, -1)
|
||||||
.push_closure_step(def (engine) log(f"before", 3) end)
|
.push_closure_step(def (engine) log(f"before", 3) end)
|
||||||
.push_play_step(shutter_animation_, duration_)
|
.push_play_step(shutter_animation_, animation.resolve(duration_))
|
||||||
.push_closure_step(def (engine) log(f"after", 3) end)
|
.push_closure_step(def (engine) log(f"after", 3) end)
|
||||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
|
|||||||
@ -9,9 +9,6 @@ import animation
|
|||||||
# Demo Shutter Rainbow Bidir
|
# Demo Shutter Rainbow Bidir
|
||||||
#
|
#
|
||||||
# Shutter from left to right iterating in all colors, then right to left
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
# Auto-generated strip initialization (using Tasmota configuration)
|
|
||||||
var engine = animation.init_strip()
|
|
||||||
|
|
||||||
# Template function: shutter_bidir
|
# Template function: shutter_bidir
|
||||||
def shutter_bidir_template(engine, colors_, duration_)
|
def shutter_bidir_template(engine, colors_, duration_)
|
||||||
var strip_len_ = animation.strip_length(engine)
|
var strip_len_ = animation.strip_length(engine)
|
||||||
@ -48,13 +45,13 @@ def shutter_bidir_template(engine, colors_, duration_)
|
|||||||
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
.push_play_step(shutter_lr_animation_, duration_)
|
.push_play_step(shutter_lr_animation_, animation.resolve(duration_))
|
||||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
)
|
)
|
||||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
.push_play_step(shutter_rl_animation_, duration_)
|
.push_play_step(shutter_rl_animation_, animation.resolve(duration_))
|
||||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
)
|
)
|
||||||
@ -63,6 +60,9 @@ end
|
|||||||
|
|
||||||
animation.register_user_function('shutter_bidir', shutter_bidir_template)
|
animation.register_user_function('shutter_bidir', shutter_bidir_template)
|
||||||
|
|
||||||
|
# Auto-generated strip initialization (using Tasmota configuration)
|
||||||
|
var engine = animation.init_strip()
|
||||||
|
|
||||||
var rainbow_with_white_ = bytes(
|
var rainbow_with_white_ = bytes(
|
||||||
"FFFF0000"
|
"FFFF0000"
|
||||||
"FFFFA500"
|
"FFFFA500"
|
||||||
|
|||||||
@ -46,13 +46,13 @@ def shutter_central_template(engine, colors_, duration_)
|
|||||||
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
.push_play_step(shutter_inout_animation_, duration_)
|
.push_play_step(shutter_inout_animation_, animation.resolve(duration_))
|
||||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
)
|
)
|
||||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
.push_play_step(shutter_outin_animation_, duration_)
|
.push_play_step(shutter_outin_animation_, animation.resolve(duration_))
|
||||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,9 +9,6 @@ import animation
|
|||||||
# Demo Shutter Rainbow
|
# Demo Shutter Rainbow
|
||||||
#
|
#
|
||||||
# Shutter from left to right iterating in all colors, then right to left
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
# Auto-generated strip initialization (using Tasmota configuration)
|
|
||||||
var engine = animation.init_strip()
|
|
||||||
|
|
||||||
# Template function: shutter_lr
|
# Template function: shutter_lr
|
||||||
def shutter_lr_template(engine, colors_, duration_)
|
def shutter_lr_template(engine, colors_, duration_)
|
||||||
var strip_len_ = animation.strip_length(engine)
|
var strip_len_ = animation.strip_length(engine)
|
||||||
@ -39,7 +36,7 @@ def shutter_lr_template(engine, colors_, duration_)
|
|||||||
shutter_lr_animation_.priority = 5
|
shutter_lr_animation_.priority = 5
|
||||||
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
.push_play_step(shutter_lr_animation_, duration_)
|
.push_play_step(shutter_lr_animation_, animation.resolve(duration_))
|
||||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
engine.add(shutter_seq_)
|
engine.add(shutter_seq_)
|
||||||
@ -47,6 +44,9 @@ end
|
|||||||
|
|
||||||
animation.register_user_function('shutter_lr', shutter_lr_template)
|
animation.register_user_function('shutter_lr', shutter_lr_template)
|
||||||
|
|
||||||
|
# Auto-generated strip initialization (using Tasmota configuration)
|
||||||
|
var engine = animation.init_strip()
|
||||||
|
|
||||||
var rainbow_with_white_ = bytes(
|
var rainbow_with_white_ = bytes(
|
||||||
"FFFF0000"
|
"FFFF0000"
|
||||||
"FFFFA500"
|
"FFFFA500"
|
||||||
|
|||||||
@ -7,9 +7,6 @@
|
|||||||
import animation
|
import animation
|
||||||
|
|
||||||
# Complex template test
|
# Complex template test
|
||||||
# Auto-generated strip initialization (using Tasmota configuration)
|
|
||||||
var engine = animation.init_strip()
|
|
||||||
|
|
||||||
# Template function: rainbow_pulse
|
# Template function: rainbow_pulse
|
||||||
def rainbow_pulse_template(engine, pal1_, pal2_, duration_, back_color_)
|
def rainbow_pulse_template(engine, pal1_, pal2_, duration_, back_color_)
|
||||||
var cycle_color_ = animation.color_cycle(engine)
|
var cycle_color_ = animation.color_cycle(engine)
|
||||||
@ -33,12 +30,18 @@ end
|
|||||||
animation.register_user_function('rainbow_pulse', rainbow_pulse_template)
|
animation.register_user_function('rainbow_pulse', rainbow_pulse_template)
|
||||||
|
|
||||||
# Create palettes
|
# Create palettes
|
||||||
|
# Auto-generated strip initialization (using Tasmota configuration)
|
||||||
|
var engine = animation.init_strip()
|
||||||
|
|
||||||
var fire_palette_ = bytes("00000000" "80FF0000" "FFFFFF00")
|
var fire_palette_ = bytes("00000000" "80FF0000" "FFFFFF00")
|
||||||
var ocean_palette_ = bytes("00000080" "800080FF" "FF00FFFF")
|
var ocean_palette_ = bytes("00000080" "800080FF" "FF00FFFF")
|
||||||
# Use the template
|
# Use the template
|
||||||
rainbow_pulse_template(engine, fire_palette_, ocean_palette_, 3000, 0xFF001100)
|
rainbow_pulse_template(engine, fire_palette_, ocean_palette_, 3000, 0xFF001100)
|
||||||
engine.run()
|
engine.run()
|
||||||
|
|
||||||
|
# Compilation warnings:
|
||||||
|
# Line 28: Template 'rainbow_pulse' parameter 'pal2' is declared but never used in the template body.
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
# Complex template test
|
# Complex template test
|
||||||
|
|||||||
@ -46,14 +46,14 @@ def shutter_bidir_template(engine, colors_, duration_)
|
|||||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
.push_closure_step(def (engine) log(f"begin 1", 3) end)
|
.push_closure_step(def (engine) log(f"begin 1", 3) end)
|
||||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
.push_play_step(shutter_lr_animation_, duration_)
|
.push_play_step(shutter_lr_animation_, animation.resolve(duration_))
|
||||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
)
|
)
|
||||||
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
.push_closure_step(def (engine) log(f"begin 2", 3) end)
|
.push_closure_step(def (engine) log(f"begin 2", 3) end)
|
||||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
.push_play_step(shutter_rl_animation_, duration_)
|
.push_play_step(shutter_rl_animation_, animation.resolve(duration_))
|
||||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,8 +20,10 @@ animation red_eye = beacon_animation(
|
|||||||
)
|
)
|
||||||
|
|
||||||
sequence cylon_eye forever {
|
sequence cylon_eye forever {
|
||||||
|
restart cosine_val
|
||||||
play red_eye for eye_duration # use COSINE movement
|
play red_eye for eye_duration # use COSINE movement
|
||||||
red_eye.pos = triangle_val # switch to TRIANGLE
|
red_eye.pos = triangle_val # switch to TRIANGLE
|
||||||
|
restart triangle_val
|
||||||
play red_eye for eye_duration
|
play red_eye for eye_duration
|
||||||
red_eye.pos = cosine_val # switch back to COSINE for next iteration
|
red_eye.pos = cosine_val # switch back to COSINE for next iteration
|
||||||
eye_color.next = 1 # advance to next color
|
eye_color.next = 1 # advance to next color
|
||||||
|
|||||||
@ -1069,6 +1069,53 @@ Files containing only templates generate pure Berry function definitions without
|
|||||||
- Templates can be called multiple times to create multiple instances
|
- Templates can be called multiple times to create multiple instances
|
||||||
- `engine.run()` is automatically called when templates are used at the top level
|
- `engine.run()` is automatically called when templates are used at the top level
|
||||||
|
|
||||||
|
### Template Parameter Validation
|
||||||
|
|
||||||
|
The DSL transpiler provides comprehensive validation for template parameters to ensure code quality and catch errors early:
|
||||||
|
|
||||||
|
**Parameter Name Validation:**
|
||||||
|
- **Duplicate Detection**: Prevents using the same parameter name twice
|
||||||
|
- **Reserved Keywords**: Prevents conflicts with Berry keywords (`animation`, `color`, `def`, etc.)
|
||||||
|
- **Built-in Colors**: Prevents conflicts with predefined color names (`red`, `blue`, etc.)
|
||||||
|
|
||||||
|
```berry
|
||||||
|
template bad_example {
|
||||||
|
param color type color # ❌ Error: conflicts with built-in color
|
||||||
|
param animation type number # ❌ Error: conflicts with reserved keyword
|
||||||
|
param my_param type color
|
||||||
|
param my_param type number # ❌ Error: duplicate parameter name
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Type Annotation Validation:**
|
||||||
|
Valid parameter types are: `color`, `palette`, `animation`, `number`, `string`, `boolean`, `time`, `percentage`, `variable`, `value_provider`
|
||||||
|
|
||||||
|
```berry
|
||||||
|
template type_example {
|
||||||
|
param my_color type invalid_type # ❌ Error: invalid type annotation
|
||||||
|
param valid_color type color # ✅ Valid type annotation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameter Usage Validation:**
|
||||||
|
The transpiler generates **warnings** (not errors) for unused parameters:
|
||||||
|
|
||||||
|
```berry
|
||||||
|
template unused_example {
|
||||||
|
param used_color type color
|
||||||
|
param unused_param type number # ⚠️ Warning: parameter never used
|
||||||
|
|
||||||
|
animation test = solid(color=used_color)
|
||||||
|
run test
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Benefits:**
|
||||||
|
- **Early Error Detection**: Catches parameter issues at compile time
|
||||||
|
- **Clear Error Messages**: Provides helpful suggestions for fixing issues
|
||||||
|
- **Code Quality**: Encourages proper parameter naming and usage
|
||||||
|
- **Warnings vs Errors**: Unused parameters generate warnings that don't prevent compilation
|
||||||
|
|
||||||
## Execution Statements
|
## Execution Statements
|
||||||
|
|
||||||
Execute animations or sequences:
|
Execute animations or sequences:
|
||||||
|
|||||||
@ -600,17 +600,68 @@ set strip_len = strip_length() # Single function call
|
|||||||
set strip_len3 = (strip_len + 1) / 2 # Computation with existing value
|
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
|
### Error Categories
|
||||||
|
|
||||||
- **Syntax errors**: Invalid DSL syntax (lexer/parser errors)
|
- **Syntax errors**: Invalid DSL syntax (lexer/parser errors)
|
||||||
- **Factory validation**: Non-existent or invalid animation/color provider factories
|
- **Factory validation**: Non-existent or invalid animation/color provider factories
|
||||||
- **Parameter validation**: Invalid parameter names in constructors or property assignments
|
- **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)
|
- **Constraint validation**: Parameter values that violate defined constraints (min/max, enums, types)
|
||||||
- **Reference validation**: Using undefined colors, animations, or variables
|
- **Reference validation**: Using undefined colors, animations, or variables
|
||||||
- **Type validation**: Incorrect parameter types or incompatible assignments
|
- **Type validation**: Incorrect parameter types or incompatible assignments
|
||||||
- **Safety validation**: Dangerous patterns that could cause memory leaks or performance issues
|
- **Safety validation**: Dangerous patterns that could cause memory leaks or performance issues
|
||||||
- **Runtime errors**: Errors during Berry code execution (rare with good validation)
|
- **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
|
## Performance Considerations
|
||||||
|
|
||||||
### DSL vs Programmatic Performance
|
### DSL vs Programmatic Performance
|
||||||
|
|||||||
@ -999,10 +999,10 @@ run red_solid
|
|||||||
```berry
|
```berry
|
||||||
# Define reusable template
|
# Define reusable template
|
||||||
template pulse_effect {
|
template pulse_effect {
|
||||||
param color type color
|
param base_color type color # Use descriptive names
|
||||||
param speed
|
param speed type time # Add type annotations for clarity
|
||||||
|
|
||||||
animation pulse = pulsating_animation(color=color, period=speed)
|
animation pulse = pulsating_animation(color=base_color, period=speed)
|
||||||
run pulse
|
run pulse
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1011,6 +1011,31 @@ pulse_effect(red, 2s)
|
|||||||
pulse_effect(blue, 1s)
|
pulse_effect(blue, 1s)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Common Template Parameter Issues:**
|
||||||
|
|
||||||
|
```berry
|
||||||
|
# ❌ AVOID: Parameter name conflicts
|
||||||
|
template bad_example {
|
||||||
|
param color type color # Error: conflicts with built-in color name
|
||||||
|
param animation type number # Error: conflicts with reserved keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
# ✅ CORRECT: Use descriptive, non-conflicting names
|
||||||
|
template good_example {
|
||||||
|
param base_color type color # Clear, non-conflicting name
|
||||||
|
param anim_speed type time # Descriptive parameter name
|
||||||
|
}
|
||||||
|
|
||||||
|
# ⚠️ WARNING: Unused parameters generate warnings
|
||||||
|
template unused_param_example {
|
||||||
|
param used_color type color
|
||||||
|
param unused_value type number # Warning: never used in template body
|
||||||
|
|
||||||
|
animation test = solid(color=used_color)
|
||||||
|
run test
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Animation with Parameters
|
### Animation with Parameters
|
||||||
```berry
|
```berry
|
||||||
color blue = 0x0000FF
|
color blue = 0x0000FF
|
||||||
|
|||||||
@ -102,4 +102,60 @@ def create_runtime(strip, debug_mode)
|
|||||||
end
|
end
|
||||||
animation_dsl.create_runtime = create_runtime
|
animation_dsl.create_runtime = create_runtime
|
||||||
|
|
||||||
|
# Compile .anim file to .be file
|
||||||
|
# Takes a filename with .anim suffix and compiles to same prefix with .be suffix
|
||||||
|
#
|
||||||
|
# @param filename: string - Path to .anim file
|
||||||
|
# @return bool - True if compilation successful
|
||||||
|
# @raises "io_error" - If file cannot be read or written
|
||||||
|
# @raises "dsl_compilation_error" - If DSL compilation fails
|
||||||
|
# @raises "invalid_filename" - If filename doesn't have .anim extension
|
||||||
|
def compile_file(filename)
|
||||||
|
import string
|
||||||
|
|
||||||
|
# Validate input filename
|
||||||
|
if !string.endswith(filename, ".anim")
|
||||||
|
raise "invalid_filename", f"Input file must have .anim extension: {filename}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate output filename
|
||||||
|
var base_name = filename[0..-6] # Remove .anim extension (5 chars + 1 for 0-based)
|
||||||
|
var output_filename = base_name + ".be"
|
||||||
|
|
||||||
|
# Read DSL source
|
||||||
|
var f = open(filename, "r")
|
||||||
|
if f == nil
|
||||||
|
raise "io_error", f"Cannot open input file: {filename}"
|
||||||
|
end
|
||||||
|
|
||||||
|
var dsl_source = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# Compile DSL to Berry code
|
||||||
|
var berry_code = animation_dsl.compile(dsl_source)
|
||||||
|
if berry_code == nil
|
||||||
|
raise "dsl_compilation_error", f"DSL compilation failed for: {filename}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate header with metadata (no original source for compile_file)
|
||||||
|
var header = "# Generated Berry code from Animation DSL\n" +
|
||||||
|
f"# Source: {filename}\n" +
|
||||||
|
"# Generated automatically by animation_dsl.compile_file()\n" +
|
||||||
|
"# \n" +
|
||||||
|
"# Do not edit manually - changes will be overwritten\n" +
|
||||||
|
"\n"
|
||||||
|
|
||||||
|
# Write complete Berry file (no footer with original source)
|
||||||
|
var output_f = open(output_filename, "w")
|
||||||
|
if output_f == nil
|
||||||
|
raise "io_error", f"Cannot create output file: {output_filename}"
|
||||||
|
end
|
||||||
|
|
||||||
|
output_f.write(header + berry_code)
|
||||||
|
output_f.close()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
animation_dsl.compile_file = compile_file
|
||||||
|
|
||||||
return animation_dsl
|
return animation_dsl
|
||||||
|
|||||||
@ -12,7 +12,6 @@ class DSLLexer
|
|||||||
var line # Integer - current line number (1-based)
|
var line # Integer - current line number (1-based)
|
||||||
var column # Integer - current column number (1-based)
|
var column # Integer - current column number (1-based)
|
||||||
var tokens # List - generated tokens
|
var tokens # List - generated tokens
|
||||||
var errors # List - lexical errors encountered
|
|
||||||
|
|
||||||
# Initialize lexer with source code
|
# Initialize lexer with source code
|
||||||
#
|
#
|
||||||
@ -23,7 +22,6 @@ class DSLLexer
|
|||||||
self.line = 1
|
self.line = 1
|
||||||
self.column = 1
|
self.column = 1
|
||||||
self.tokens = []
|
self.tokens = []
|
||||||
self.errors = []
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Tokenize the entire source code
|
# Tokenize the entire source code
|
||||||
@ -31,7 +29,6 @@ class DSLLexer
|
|||||||
# @return list - Array of Token objects
|
# @return list - Array of Token objects
|
||||||
def tokenize()
|
def tokenize()
|
||||||
self.tokens = []
|
self.tokens = []
|
||||||
self.errors = []
|
|
||||||
self.position = 0
|
self.position = 0
|
||||||
self.line = 1
|
self.line = 1
|
||||||
self.column = 1
|
self.column = 1
|
||||||
@ -117,8 +114,7 @@ class DSLLexer
|
|||||||
if hex_digits == 6 || hex_digits == 8
|
if hex_digits == 6 || hex_digits == 8
|
||||||
self.add_token(4 #-animation_dsl.Token.COLOR-#, color_value, size(color_value))
|
self.add_token(4 #-animation_dsl.Token.COLOR-#, color_value, size(color_value))
|
||||||
else
|
else
|
||||||
self.add_error("Invalid hex color format: " + color_value + " (expected 0xRRGGBB or 0xAARRGGBB)")
|
self.error("Invalid hex color format: " + color_value + " (expected 0xRRGGBB or 0xAARRGGBB)")
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, color_value, size(color_value))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -265,8 +261,7 @@ class DSLLexer
|
|||||||
end
|
end
|
||||||
|
|
||||||
if self.at_end()
|
if self.at_end()
|
||||||
self.add_error("Unterminated string literal")
|
self.error("Unterminated string literal")
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, value, self.position - start_pos)
|
|
||||||
else
|
else
|
||||||
# Consume closing quote
|
# Consume closing quote
|
||||||
self.advance()
|
self.advance()
|
||||||
@ -310,8 +305,7 @@ class DSLLexer
|
|||||||
|
|
||||||
# Check if we reached end without finding closing quotes
|
# Check if we reached end without finding closing quotes
|
||||||
if self.at_end() && !(self.source[self.position-3..self.position-1] == quote_char + quote_char + quote_char)
|
if self.at_end() && !(self.source[self.position-3..self.position-1] == quote_char + quote_char + quote_char)
|
||||||
self.add_error("Unterminated triple-quoted string literal")
|
self.error("Unterminated triple-quoted string literal")
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, value, self.position - start_pos)
|
|
||||||
else
|
else
|
||||||
self.add_token(3 #-animation_dsl.Token.STRING-#, value, self.position - start_pos)
|
self.add_token(3 #-animation_dsl.Token.STRING-#, value, self.position - start_pos)
|
||||||
end
|
end
|
||||||
@ -323,9 +317,7 @@ class DSLLexer
|
|||||||
var start_column = self.column - 1
|
var start_column = self.column - 1
|
||||||
|
|
||||||
if self.at_end() || !(self.is_alpha(self.peek()) || self.peek() == '_')
|
if self.at_end() || !(self.is_alpha(self.peek()) || self.peek() == '_')
|
||||||
self.add_error("Invalid variable reference: $ must be followed by identifier")
|
self.error("Invalid variable reference: $ must be followed by identifier")
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, "$", 1)
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Scan identifier part
|
# Scan identifier part
|
||||||
@ -358,7 +350,7 @@ class DSLLexer
|
|||||||
self.add_token(18 #-animation_dsl.Token.LESS_EQUAL-#, "<=", 2)
|
self.add_token(18 #-animation_dsl.Token.LESS_EQUAL-#, "<=", 2)
|
||||||
elif self.match('<')
|
elif self.match('<')
|
||||||
# Left shift - not used in DSL but included for completeness
|
# Left shift - not used in DSL but included for completeness
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, "<<", 2)
|
self.error("Left shift operator '<<' not supported in DSL")
|
||||||
else
|
else
|
||||||
self.add_token(17 #-animation_dsl.Token.LESS_THAN-#, "<", 1)
|
self.add_token(17 #-animation_dsl.Token.LESS_THAN-#, "<", 1)
|
||||||
end
|
end
|
||||||
@ -367,7 +359,7 @@ class DSLLexer
|
|||||||
self.add_token(20 #-animation_dsl.Token.GREATER_EQUAL-#, ">=", 2)
|
self.add_token(20 #-animation_dsl.Token.GREATER_EQUAL-#, ">=", 2)
|
||||||
elif self.match('>')
|
elif self.match('>')
|
||||||
# Right shift - not used in DSL but included for completeness
|
# Right shift - not used in DSL but included for completeness
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, ">>", 2)
|
self.error("Right shift operator '>>' not supported in DSL")
|
||||||
else
|
else
|
||||||
self.add_token(19 #-animation_dsl.Token.GREATER_THAN-#, ">", 1)
|
self.add_token(19 #-animation_dsl.Token.GREATER_THAN-#, ">", 1)
|
||||||
end
|
end
|
||||||
@ -375,15 +367,13 @@ class DSLLexer
|
|||||||
if self.match('&')
|
if self.match('&')
|
||||||
self.add_token(21 #-animation_dsl.Token.LOGICAL_AND-#, "&&", 2)
|
self.add_token(21 #-animation_dsl.Token.LOGICAL_AND-#, "&&", 2)
|
||||||
else
|
else
|
||||||
self.add_error("Single '&' not supported in DSL")
|
self.error("Single '&' not supported in DSL")
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, "&", 1)
|
|
||||||
end
|
end
|
||||||
elif ch == '|'
|
elif ch == '|'
|
||||||
if self.match('|')
|
if self.match('|')
|
||||||
self.add_token(22 #-animation_dsl.Token.LOGICAL_OR-#, "||", 2)
|
self.add_token(22 #-animation_dsl.Token.LOGICAL_OR-#, "||", 2)
|
||||||
else
|
else
|
||||||
self.add_error("Single '|' not supported in DSL")
|
self.error("Single '|' not supported in DSL")
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, "|", 1)
|
|
||||||
end
|
end
|
||||||
elif ch == '-'
|
elif ch == '-'
|
||||||
if self.match('>')
|
if self.match('>')
|
||||||
@ -428,8 +418,7 @@ class DSLLexer
|
|||||||
self.add_token(33 #-animation_dsl.Token.DOT-#, ".", 1)
|
self.add_token(33 #-animation_dsl.Token.DOT-#, ".", 1)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
self.add_error("Unexpected character: '" + ch + "'")
|
self.error("Unexpected character: '" + ch + "'")
|
||||||
self.add_token(39 #-animation_dsl.Token.ERROR-#, ch, 1)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -510,37 +499,10 @@ class DSLLexer
|
|||||||
self.tokens.push(token)
|
self.tokens.push(token)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add error to errors list
|
# Raise lexical error immediately
|
||||||
def add_error(message)
|
def error(message)
|
||||||
self.errors.push({
|
var error_msg = "Line " + str(self.line) + ":" + str(self.column) + ": " + message
|
||||||
"message": message,
|
raise "lexical_error", error_msg
|
||||||
"line": self.line,
|
|
||||||
"column": self.column,
|
|
||||||
"position": self.position
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get all errors encountered during tokenization
|
|
||||||
def get_errors()
|
|
||||||
return self.errors
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check if any errors were encountered
|
|
||||||
def has_errors()
|
|
||||||
return size(self.errors) > 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get a formatted error report
|
|
||||||
def get_error_report()
|
|
||||||
if !self.has_errors()
|
|
||||||
return "No lexical errors"
|
|
||||||
end
|
|
||||||
|
|
||||||
var report = "Lexical errors (" + str(size(self.errors)) + "):\n"
|
|
||||||
for error : self.errors
|
|
||||||
report += " Line " + str(error["line"]) + ":" + str(error["column"]) + ": " + error["message"] + "\n"
|
|
||||||
end
|
|
||||||
return report
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reset lexer state for reuse
|
# Reset lexer state for reuse
|
||||||
@ -550,7 +512,6 @@ class DSLLexer
|
|||||||
self.line = 1
|
self.line = 1
|
||||||
self.column = 1
|
self.column = 1
|
||||||
self.tokens = []
|
self.tokens = []
|
||||||
self.errors = []
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get current position info for debugging
|
# Get current position info for debugging
|
||||||
@ -563,16 +524,7 @@ class DSLLexer
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Tokenize and return both tokens and errors
|
|
||||||
def tokenize_with_errors()
|
|
||||||
var tokens = self.tokenize()
|
|
||||||
var result = {
|
|
||||||
"tokens": tokens,
|
|
||||||
"errors": self.errors,
|
|
||||||
"success": !self.has_errors()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Utility function to tokenize DSL source code
|
# Utility function to tokenize DSL source code
|
||||||
@ -584,17 +536,7 @@ def tokenize_dsl(source)
|
|||||||
return lexer.tokenize()
|
return lexer.tokenize()
|
||||||
end
|
end
|
||||||
|
|
||||||
# Utility function to tokenize with error handling
|
|
||||||
#
|
|
||||||
# @param source: string - DSL source code
|
|
||||||
# @return map - {tokens: list, errors: list, success: bool}
|
|
||||||
def tokenize_dsl_with_errors(source)
|
|
||||||
var lexer = animation_dsl.DSLLexer(source)
|
|
||||||
return lexer.tokenize_with_errors()
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"DSLLexer": DSLLexer,
|
"DSLLexer": DSLLexer,
|
||||||
"tokenize_dsl": tokenize_dsl,
|
"tokenize_dsl": tokenize_dsl
|
||||||
"tokenize_dsl_with_errors": tokenize_dsl_with_errors
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
479
lib/libesp32/berry_animation/src/tests/dsl_compilation_test.be
Normal file
479
lib/libesp32/berry_animation/src/tests/dsl_compilation_test.be
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
# DSL Compilation Test Suite
|
||||||
|
# Tests for DSL compilation with both successful and failing cases
|
||||||
|
#
|
||||||
|
# Command to run test is:
|
||||||
|
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota def log(x) print(x) end import animation import animation_dsl " lib/libesp32/berry_animation/src/tests/dsl_compilation_test.be
|
||||||
|
|
||||||
|
import animation
|
||||||
|
import animation_dsl
|
||||||
|
import user_functions
|
||||||
|
import string
|
||||||
|
|
||||||
|
# Test successful compilation cases
|
||||||
|
def test_successful_compilation()
|
||||||
|
print("Testing successful DSL compilation cases...")
|
||||||
|
|
||||||
|
# Test basic variable assignments and computed values
|
||||||
|
var basic_dsl =
|
||||||
|
"set strip_len = strip_length()\n" +
|
||||||
|
"set r1 = rand_demo()\n" +
|
||||||
|
"set r2 = rand_demo(12)\n" +
|
||||||
|
"set r3 = rand_demo(4 + 5)\n" +
|
||||||
|
"set r4 = rand_demo(strip_len)\n" +
|
||||||
|
"set r5 = rand_demo(strip_len + 1)\n" +
|
||||||
|
"set az = abs(strip_len / 4)\n" +
|
||||||
|
"set x = 3s\n" +
|
||||||
|
"set xy = strip_length()\n" +
|
||||||
|
"set xx = (0 + 3*4)\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile(basic_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile basic DSL")
|
||||||
|
|
||||||
|
# Check for proper variable definitions
|
||||||
|
assert(string.find(berry_code, "var strip_len_ = animation.strip_length(engine)") >= 0, "Should create strip_length value provider")
|
||||||
|
assert(string.find(berry_code, "var r1_ = animation.create_closure_value(engine") >= 0, "Should create closure for user function")
|
||||||
|
assert(string.find(berry_code, "var x_ = 3000") >= 0, "Should convert time to milliseconds")
|
||||||
|
assert(string.find(berry_code, "var xx_ = (0 + 3 * 4)") >= 0, "Should preserve arithmetic expressions")
|
||||||
|
|
||||||
|
print("✓ Basic compilation test passed")
|
||||||
|
|
||||||
|
# Test value provider assignments
|
||||||
|
var provider_dsl =
|
||||||
|
"set shutter_size = sawtooth(min_value = 0, max_value = 10, duration = 3s)\n" +
|
||||||
|
"shutter_size.min_value = rand_demo()\n" +
|
||||||
|
"shutter_size.max_value = strip_length()\n" +
|
||||||
|
"shutter_size.min_value = 5\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(provider_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile value provider assignments")
|
||||||
|
assert(string.find(berry_code, "animation.sawtooth(engine)") >= 0, "Should create sawtooth provider")
|
||||||
|
assert(string.find(berry_code, "shutter_size_.min_value = animation.create_closure_value") >= 0, "Should create closure for property assignment")
|
||||||
|
|
||||||
|
print("✓ Value provider assignment test passed")
|
||||||
|
|
||||||
|
# Test animation definitions
|
||||||
|
var animation_dsl_code =
|
||||||
|
"animation test = pulsating_animation(color=0xFF0000FF, min_brightness=(0+1))\n" +
|
||||||
|
"test.priority = 10\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(animation_dsl_code)
|
||||||
|
assert(berry_code != nil, "Should compile animation definitions")
|
||||||
|
assert(string.find(berry_code, "animation.pulsating_animation(engine)") >= 0, "Should create pulsating animation")
|
||||||
|
assert(string.find(berry_code, "test_.color = 0xFF0000FF") >= 0, "Should set color parameter")
|
||||||
|
assert(string.find(berry_code, "test_.priority = 10") >= 0, "Should set priority property")
|
||||||
|
|
||||||
|
print("✓ Animation definition test passed")
|
||||||
|
|
||||||
|
# Test palette definitions
|
||||||
|
var palette_dsl =
|
||||||
|
"palette col1 = [red, orange, yellow, green, blue, indigo, white]\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(palette_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile palette definitions")
|
||||||
|
assert(string.find(berry_code, 'var col1_ = bytes("FFFF0000"') >= 0, "Should create palette bytes")
|
||||||
|
|
||||||
|
print("✓ Palette definition test passed")
|
||||||
|
|
||||||
|
# Test sequences with repeat
|
||||||
|
var sequence_dsl =
|
||||||
|
"set strip_len = strip_length()\n" +
|
||||||
|
"palette col1 = [red, orange, yellow]\n" +
|
||||||
|
"sequence seq1 repeat forever {\n" +
|
||||||
|
" repeat col1.palette_size times {\n" +
|
||||||
|
' log("begin 1")\n' +
|
||||||
|
" col1.next = 1\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}\n" +
|
||||||
|
"sequence seq2 repeat forever {\n" +
|
||||||
|
" repeat 7 + 2 times {\n" +
|
||||||
|
' log("begin 2")\n' +
|
||||||
|
" }\n" +
|
||||||
|
"}\n" +
|
||||||
|
"sequence seq3 repeat forever {\n" +
|
||||||
|
" repeat strip_len times {\n" +
|
||||||
|
' log("begin 3")\n' +
|
||||||
|
" }\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(sequence_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile sequences with repeat")
|
||||||
|
assert(string.find(berry_code, "animation.SequenceManager(engine, -1)") >= 0, "Should create sequence with forever repeat")
|
||||||
|
assert(string.find(berry_code, "push_repeat_subsequence") >= 0, "Should create repeat subsequence")
|
||||||
|
assert(string.find(berry_code, "col1_.palette_size") >= 0, "Should reference palette size")
|
||||||
|
assert(string.find(berry_code, "7 + 2") >= 0, "Should preserve arithmetic in repeat count")
|
||||||
|
|
||||||
|
print("✓ Sequence with repeat test passed")
|
||||||
|
|
||||||
|
# Test restart statements
|
||||||
|
var restart_dsl =
|
||||||
|
"set shutter_size = sawtooth(min_value = 0, max_value = 10, duration = 3s)\n" +
|
||||||
|
"sequence tt {\n" +
|
||||||
|
" restart shutter_size\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(restart_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile restart statements")
|
||||||
|
assert(string.find(berry_code, "shutter_size_.start(engine.time_ms)") >= 0, "Should generate restart call")
|
||||||
|
|
||||||
|
print("✓ Restart statement test passed")
|
||||||
|
|
||||||
|
# Test computed expressions with mathematical functions
|
||||||
|
var math_dsl =
|
||||||
|
"set strip_len = strip_length()\n" +
|
||||||
|
"set computed1 = max(1, min(strip_len, 20))\n" +
|
||||||
|
"set computed2 = abs(strip_len - 30)\n" +
|
||||||
|
"set computed3 = round(strip_len / 6)\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(math_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile mathematical expressions")
|
||||||
|
assert(string.find(berry_code, "animation._math.max") >= 0, "Should use animation._math for max function")
|
||||||
|
assert(string.find(berry_code, "animation._math.abs") >= 0, "Should use animation._math for abs function")
|
||||||
|
assert(string.find(berry_code, "animation._math.round") >= 0, "Should use animation._math for round function")
|
||||||
|
|
||||||
|
print("✓ Mathematical expressions test passed")
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test compilation failure cases
|
||||||
|
def test_compilation_failures()
|
||||||
|
print("Testing DSL compilation failure cases...")
|
||||||
|
|
||||||
|
# Test dangerous function creation in computed expressions
|
||||||
|
var dangerous_dsl = "set s2 = strip_length() + strip_length()"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(dangerous_dsl)
|
||||||
|
assert(false, "Should fail with dangerous function creation")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "Function 'strip_length' cannot be used in computed expressions") >= 0,
|
||||||
|
"Should report dangerous function creation error")
|
||||||
|
print("✓ Dangerous function creation properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test undefined variable reference
|
||||||
|
var undefined_var_dsl = "set result = undefined_variable + 5"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(undefined_var_dsl)
|
||||||
|
assert(false, "Should fail with undefined variable")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "Unknown identifier 'undefined_variable'") >= 0, "Should report unknown identifier error")
|
||||||
|
print("✓ Undefined variable properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test invalid animation factory
|
||||||
|
var invalid_factory_dsl = "animation bad = nonexistent_animation(color=red)"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(invalid_factory_dsl)
|
||||||
|
assert(false, "Should fail with invalid animation factory")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "Animation factory function 'nonexistent_animation' does not exist") >= 0,
|
||||||
|
"Should report invalid factory error")
|
||||||
|
print("✓ Invalid animation factory properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test invalid parameter name
|
||||||
|
var invalid_param_dsl = "animation pulse = pulsating_animation(invalid_param=123)"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(invalid_param_dsl)
|
||||||
|
assert(false, "Should fail with invalid parameter")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "does not have parameter 'invalid_param'") >= 0,
|
||||||
|
"Should report invalid parameter error")
|
||||||
|
print("✓ Invalid parameter properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test invalid property assignment
|
||||||
|
var invalid_property_dsl =
|
||||||
|
"animation pulse = pulsating_animation(color=red, period=2s)\n" +
|
||||||
|
"pulse.wrong_property = 15"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(invalid_property_dsl)
|
||||||
|
assert(false, "Should fail with invalid property")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "does not have parameter 'wrong_property'") >= 0,
|
||||||
|
"Should report invalid property error")
|
||||||
|
print("✓ Invalid property assignment properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test undefined color reference
|
||||||
|
var undefined_color_dsl = "animation pulse = pulsating_animation(color=undefined_color)"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(undefined_color_dsl)
|
||||||
|
assert(false, "Should fail with undefined color")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "Unknown identifier 'undefined_color'") >= 0,
|
||||||
|
"Should report unknown identifier error")
|
||||||
|
print("✓ Undefined color reference properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test undefined animation in run statement
|
||||||
|
var undefined_run_dsl = "run nonexistent_animation"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(undefined_run_dsl)
|
||||||
|
assert(false, "Should fail with undefined animation in run")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "Undefined reference 'nonexistent_animation' in run") >= 0,
|
||||||
|
"Should report undefined animation in run error")
|
||||||
|
print("✓ Undefined animation in run properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test undefined animation in sequence play
|
||||||
|
var undefined_play_dsl =
|
||||||
|
"sequence demo {\n" +
|
||||||
|
" play nonexistent_animation for 5s\n" +
|
||||||
|
"}"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(undefined_play_dsl)
|
||||||
|
assert(false, "Should fail with undefined animation in play")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "Undefined reference 'nonexistent_animation' in sequence play") >= 0,
|
||||||
|
"Should report undefined animation in play error")
|
||||||
|
print("✓ Undefined animation in play properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test undefined duration variable
|
||||||
|
var undefined_duration_dsl =
|
||||||
|
"animation test = solid(color=red)\n" +
|
||||||
|
"sequence demo {\n" +
|
||||||
|
" play test for invalid_duration\n" +
|
||||||
|
"}"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(undefined_duration_dsl)
|
||||||
|
assert(false, "Should fail with undefined duration")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "Undefined reference 'invalid_duration' in duration") >= 0,
|
||||||
|
"Should report undefined duration error")
|
||||||
|
print("✓ Undefined duration properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test invalid color provider factory
|
||||||
|
var invalid_color_provider_dsl = "color bad = nonexistent_color_provider(period=2s)"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(invalid_color_provider_dsl)
|
||||||
|
assert(false, "Should fail with invalid color provider")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
# Accept any compilation error for invalid color provider
|
||||||
|
print("✓ Invalid color provider properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test function that doesn't create animation instance
|
||||||
|
var wrong_type_dsl = "animation bad = triangle(min_value=0, max_value=10)"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(wrong_type_dsl)
|
||||||
|
assert(false, "Should fail with wrong instance type")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
# Accept any compilation error for wrong instance type
|
||||||
|
print("✓ Wrong instance type properly rejected")
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test edge cases and complex scenarios
|
||||||
|
def test_edge_cases()
|
||||||
|
print("Testing edge cases...")
|
||||||
|
|
||||||
|
# Test empty DSL
|
||||||
|
var empty_dsl = ""
|
||||||
|
var berry_code = animation_dsl.compile(empty_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile empty DSL")
|
||||||
|
# Empty DSL might not generate engine initialization - check what it actually generates
|
||||||
|
if string.find(berry_code, "var engine = animation.init_strip()") >= 0
|
||||||
|
print("✓ Empty DSL generates engine initialization")
|
||||||
|
else
|
||||||
|
print("✓ Empty DSL compiles without engine initialization (expected for empty code)")
|
||||||
|
end
|
||||||
|
|
||||||
|
print("✓ Empty DSL test passed")
|
||||||
|
|
||||||
|
# Test comments only
|
||||||
|
var comments_dsl =
|
||||||
|
"# This is a comment\n" +
|
||||||
|
"# Another comment\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(comments_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile comments-only DSL")
|
||||||
|
|
||||||
|
print("✓ Comments-only DSL test passed")
|
||||||
|
|
||||||
|
# Test complex nested expressions
|
||||||
|
var complex_expr_dsl =
|
||||||
|
"set strip_len = strip_length()\n" +
|
||||||
|
"set base_value = 5\n" +
|
||||||
|
"set complex = max(1, min(strip_len, abs(base_value * 2 - strip_len / 3)))\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(complex_expr_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile complex nested expressions")
|
||||||
|
assert(string.find(berry_code, "animation._math.max") >= 0, "Should handle nested math functions")
|
||||||
|
|
||||||
|
print("✓ Complex nested expressions test passed")
|
||||||
|
|
||||||
|
# Test multiple value providers of same type
|
||||||
|
var multiple_providers_dsl =
|
||||||
|
"set osc1 = triangle(min_value=0, max_value=10, duration=2s)\n" +
|
||||||
|
"set osc2 = triangle(min_value=5, max_value=15, duration=3s)\n" +
|
||||||
|
"set osc3 = sawtooth(min_value=0, max_value=20, duration=4s)\n"
|
||||||
|
|
||||||
|
berry_code = animation_dsl.compile(multiple_providers_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile multiple value providers")
|
||||||
|
|
||||||
|
# Count triangle providers
|
||||||
|
var triangle_count = 0
|
||||||
|
var pos = 0
|
||||||
|
while true
|
||||||
|
pos = string.find(berry_code, "animation.triangle(engine)", pos)
|
||||||
|
if pos < 0 break end
|
||||||
|
triangle_count += 1
|
||||||
|
pos += 1
|
||||||
|
end
|
||||||
|
assert(triangle_count == 2, f"Should create 2 triangle providers, found {triangle_count}")
|
||||||
|
|
||||||
|
print("✓ Multiple value providers test passed")
|
||||||
|
|
||||||
|
# Test user functions with different parameter counts
|
||||||
|
var user_func_dsl =
|
||||||
|
"set r1 = rand_demo()\n" +
|
||||||
|
"set r2 = rand_demo(12)\n" +
|
||||||
|
"set r3 = rand_demo(4, 8)\n" # This might fail if rand_demo doesn't support 2 params
|
||||||
|
|
||||||
|
try
|
||||||
|
berry_code = animation_dsl.compile(user_func_dsl)
|
||||||
|
if berry_code != nil
|
||||||
|
print("✓ User functions with different parameters compiled")
|
||||||
|
end
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
print("✓ User function parameter validation working (expected for unsupported parameter count)")
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test the complete example from test_simple_transpiler.be
|
||||||
|
def test_complete_example()
|
||||||
|
print("Testing complete example from test_simple_transpiler.be...")
|
||||||
|
|
||||||
|
var complete_dsl =
|
||||||
|
"set strip_len = strip_length()\n" +
|
||||||
|
"set r1 = rand_demo()\n" +
|
||||||
|
"set r2 = rand_demo(12)\n" +
|
||||||
|
"set r3 = rand_demo(4 + 5)\n" +
|
||||||
|
"set r4 = rand_demo(strip_len)\n" +
|
||||||
|
"set r5 = rand_demo(strip_len + 1)\n" +
|
||||||
|
"set az = abs(strip_len / 4)\n" +
|
||||||
|
"set x = 3s\n" +
|
||||||
|
"set xy = strip_length()\n" +
|
||||||
|
"set xx = (0 + 3*4)\n" +
|
||||||
|
"set shutter_size = sawtooth(min_value = 0, max_value = strip_len / 2 + 1, duration = x)\n" +
|
||||||
|
"shutter_size.min_value = rand_demo()\n" +
|
||||||
|
"shutter_size.max_value = strip_len\n" +
|
||||||
|
"shutter_size.min_value = strip_len / 2\n" +
|
||||||
|
"animation test = pulsating_animation(color=0xFF0000FF, min_brightness=(0+1))\n" +
|
||||||
|
"palette col1 = [red, orange, yellow, green, blue, indigo, white]\n" +
|
||||||
|
"set zz = strip_len - 2\n" +
|
||||||
|
"set z1 = x\n" +
|
||||||
|
"set m1 = x + 1\n" +
|
||||||
|
"set m2 = 1 + x\n" +
|
||||||
|
"sequence tt {\n" +
|
||||||
|
" restart shutter_size\n" +
|
||||||
|
"}\n" +
|
||||||
|
"set z2 = x + x\n" +
|
||||||
|
"set z3 = sawtooth()\n" +
|
||||||
|
"set z4 = linear(min_value=10, max_value=20)\n" +
|
||||||
|
"set y = x + 4\n" +
|
||||||
|
"sequence seq1 repeat forever {\n" +
|
||||||
|
" repeat col1.palette_size times {\n" +
|
||||||
|
' log("begin 1")\n' +
|
||||||
|
" restart shutter_size\n" +
|
||||||
|
" col1.next = 1\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile(complete_dsl)
|
||||||
|
assert(berry_code != nil, "Should compile complete example")
|
||||||
|
|
||||||
|
# Verify key components are present
|
||||||
|
assert(string.find(berry_code, "var strip_len_ = animation.strip_length(engine)") >= 0, "Should create strip_length provider")
|
||||||
|
assert(string.find(berry_code, "var r1_ = animation.create_closure_value(engine") >= 0, "Should create user function closures")
|
||||||
|
assert(string.find(berry_code, "var x_ = 3000") >= 0, "Should convert time values")
|
||||||
|
assert(string.find(berry_code, "animation.sawtooth(engine)") >= 0, "Should create sawtooth providers")
|
||||||
|
assert(string.find(berry_code, "animation.pulsating_animation(engine)") >= 0, "Should create animations")
|
||||||
|
assert(string.find(berry_code, 'bytes("FFFF0000"') >= 0, "Should create palette bytes")
|
||||||
|
assert(string.find(berry_code, "animation.SequenceManager(engine") >= 0, "Should create sequences")
|
||||||
|
assert(string.find(berry_code, "push_repeat_subsequence") >= 0, "Should create repeat loops")
|
||||||
|
|
||||||
|
print("✓ Complete example compilation test passed")
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test the specific failing case mentioned in the original request
|
||||||
|
def test_specific_failing_case()
|
||||||
|
print("Testing specific failing case: set s2 = strip_length() + strip_length()...")
|
||||||
|
|
||||||
|
var failing_dsl = "set s2 = strip_length() + strip_length()"
|
||||||
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(failing_dsl)
|
||||||
|
assert(false, "Should fail - dangerous pattern should be rejected")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
assert(string.find(msg, "Function 'strip_length' cannot be used in computed expressions") >= 0,
|
||||||
|
"Should report the specific error about function usage in computed expressions")
|
||||||
|
print("✓ Specific failing case properly rejected with correct error message")
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
def run_all_tests()
|
||||||
|
print("=== DSL Compilation Test Suite ===")
|
||||||
|
|
||||||
|
var tests = [
|
||||||
|
test_successful_compilation,
|
||||||
|
test_compilation_failures,
|
||||||
|
test_edge_cases,
|
||||||
|
test_complete_example,
|
||||||
|
test_specific_failing_case
|
||||||
|
]
|
||||||
|
|
||||||
|
var passed = 0
|
||||||
|
var total = size(tests)
|
||||||
|
|
||||||
|
for test_func : tests
|
||||||
|
try
|
||||||
|
if test_func()
|
||||||
|
passed += 1
|
||||||
|
end
|
||||||
|
except .. as e, msg
|
||||||
|
print(f"Test failed with exception: {e}")
|
||||||
|
print(f"Message: {msg}")
|
||||||
|
import debug
|
||||||
|
debug.traceback()
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
|
||||||
|
print(f"=== Test Results: {passed}/{total} tests passed ===")
|
||||||
|
|
||||||
|
if passed == total
|
||||||
|
print("🎉 All tests passed!")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Some tests failed")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run the tests
|
||||||
|
run_all_tests()
|
||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import animation
|
import animation
|
||||||
import animation_dsl
|
import animation_dsl
|
||||||
|
import string
|
||||||
|
|
||||||
# Test basic tokenization
|
# Test basic tokenization
|
||||||
def test_basic_tokenization()
|
def test_basic_tokenization()
|
||||||
@ -43,8 +44,7 @@ def test_basic_tokenization()
|
|||||||
assert(found_color_keyword, "Should find 'color' keyword")
|
assert(found_color_keyword, "Should find 'color' keyword")
|
||||||
assert(found_color_value, "Should find '0xFF0000' color value")
|
assert(found_color_value, "Should find '0xFF0000' color value")
|
||||||
|
|
||||||
# Should have no errors
|
# Should have no errors (lexer would have raised exception if there were errors)
|
||||||
assert(!lexer.has_errors(), "Should have no lexical errors")
|
|
||||||
|
|
||||||
print("✓ Basic tokenization test passed")
|
print("✓ Basic tokenization test passed")
|
||||||
return true
|
return true
|
||||||
@ -240,13 +240,17 @@ def test_string_literals()
|
|||||||
end
|
end
|
||||||
|
|
||||||
assert(found_string, "Should recognize string literal: " + str_test)
|
assert(found_string, "Should recognize string literal: " + str_test)
|
||||||
assert(!lexer.has_errors(), "String parsing should not produce errors")
|
# No errors check needed - lexer would have raised exception if there were errors
|
||||||
end
|
end
|
||||||
|
|
||||||
# Test unterminated string (should produce error)
|
# Test unterminated string (should raise exception)
|
||||||
var lexer = animation_dsl.DSLLexer('text = "unterminated string')
|
try
|
||||||
var tokens = lexer.tokenize()
|
var lexer = animation_dsl.DSLLexer('text = "unterminated string')
|
||||||
assert(lexer.has_errors(), "Unterminated string should produce error")
|
var tokens = lexer.tokenize()
|
||||||
|
assert(false, "Unterminated string should raise exception")
|
||||||
|
except "lexical_error" as e, msg
|
||||||
|
# Expected - unterminated string should raise lexical_error
|
||||||
|
end
|
||||||
|
|
||||||
print("✓ String literals test passed")
|
print("✓ String literals test passed")
|
||||||
return true
|
return true
|
||||||
@ -277,12 +281,16 @@ def test_variable_references()
|
|||||||
assert(found_var_ref, "Should recognize variable reference: " + var_test)
|
assert(found_var_ref, "Should recognize variable reference: " + var_test)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Test invalid variable references
|
# Test invalid variable references (should raise exceptions)
|
||||||
var invalid_tests = ["$123", "$"]
|
var invalid_tests = ["$123", "$"]
|
||||||
for invalid_test : invalid_tests
|
for invalid_test : invalid_tests
|
||||||
var lexer = animation_dsl.DSLLexer("value = " + invalid_test)
|
try
|
||||||
var tokens = lexer.tokenize()
|
var lexer = animation_dsl.DSLLexer("value = " + invalid_test)
|
||||||
assert(lexer.has_errors(), "Invalid variable reference should produce error: " + invalid_test)
|
var tokens = lexer.tokenize()
|
||||||
|
assert(false, "Invalid variable reference should raise exception: " + invalid_test)
|
||||||
|
except "lexical_error" as e, msg
|
||||||
|
# Expected - invalid variable reference should raise lexical_error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
print("✓ Variable references test passed")
|
print("✓ Variable references test passed")
|
||||||
@ -348,14 +356,13 @@ def test_complex_dsl()
|
|||||||
"run campfire"
|
"run campfire"
|
||||||
|
|
||||||
var lexer = animation_dsl.DSLLexer(complex_dsl)
|
var lexer = animation_dsl.DSLLexer(complex_dsl)
|
||||||
var result = lexer.tokenize_with_errors()
|
var tokens = lexer.tokenize()
|
||||||
|
|
||||||
assert(result["success"], "Complex DSL should tokenize successfully")
|
assert(size(tokens) > 50, "Should have many tokens")
|
||||||
assert(size(result["tokens"]) > 50, "Should have many tokens")
|
|
||||||
|
|
||||||
# Count token types
|
# Count token types
|
||||||
var token_counts = {}
|
var token_counts = {}
|
||||||
for token : result["tokens"]
|
for token : tokens
|
||||||
var type_name = animation_dsl.Token.to_string(token.type)
|
var type_name = animation_dsl.Token.to_string(token.type)
|
||||||
if token_counts.contains(type_name)
|
if token_counts.contains(type_name)
|
||||||
token_counts[type_name] += 1
|
token_counts[type_name] += 1
|
||||||
@ -380,27 +387,36 @@ end
|
|||||||
def test_error_handling()
|
def test_error_handling()
|
||||||
print("Testing error handling...")
|
print("Testing error handling...")
|
||||||
|
|
||||||
# Test invalid characters
|
# Test invalid characters (should raise exception)
|
||||||
var lexer1 = animation_dsl.DSLLexer("color red = @invalid")
|
try
|
||||||
var tokens1 = lexer1.tokenize()
|
var lexer1 = animation_dsl.DSLLexer("color red = @invalid")
|
||||||
assert(lexer1.has_errors(), "Invalid character should produce error")
|
var tokens1 = lexer1.tokenize()
|
||||||
|
assert(false, "Invalid character should raise exception")
|
||||||
|
except "lexical_error" as e, msg
|
||||||
|
# Expected - invalid character should raise lexical_error
|
||||||
|
assert(size(msg) > 0, "Should have error message")
|
||||||
|
end
|
||||||
|
|
||||||
# Test invalid hex color
|
# Test invalid hex color (should raise exception)
|
||||||
var lexer2 = animation_dsl.DSLLexer("color red = 0xGGGGGG")
|
try
|
||||||
var tokens2 = lexer2.tokenize()
|
var lexer2 = animation_dsl.DSLLexer("color red = 0xGGGGGG")
|
||||||
assert(lexer2.has_errors(), "Invalid hex color should produce error")
|
var tokens2 = lexer2.tokenize()
|
||||||
|
assert(false, "Invalid hex color should raise exception")
|
||||||
|
except "lexical_error" as e, msg
|
||||||
|
# Expected - invalid hex color should raise lexical_error
|
||||||
|
assert(size(msg) > 0, "Should have error message")
|
||||||
|
end
|
||||||
|
|
||||||
# Test unterminated string
|
# Test unterminated string (should raise exception)
|
||||||
var lexer3 = animation_dsl.DSLLexer('text = "unterminated')
|
try
|
||||||
var tokens3 = lexer3.tokenize()
|
var lexer3 = animation_dsl.DSLLexer('text = "unterminated')
|
||||||
assert(lexer3.has_errors(), "Unterminated string should produce error")
|
var tokens3 = lexer3.tokenize()
|
||||||
|
assert(false, "Unterminated string should raise exception")
|
||||||
# Test error reporting
|
except "lexical_error" as e, msg
|
||||||
var errors = lexer3.get_errors()
|
# Expected - unterminated string should raise lexical_error
|
||||||
assert(size(errors) > 0, "Should have error details")
|
assert(size(msg) > 0, "Should have error message")
|
||||||
|
assert(string.find(msg, "Unterminated") >= 0, "Error message should mention unterminated string")
|
||||||
var error_report = lexer3.get_error_report()
|
end
|
||||||
assert(size(error_report) > 0, "Should generate error report")
|
|
||||||
|
|
||||||
print("✓ Error handling test passed")
|
print("✓ Error handling test passed")
|
||||||
return true
|
return true
|
||||||
|
|||||||
@ -101,19 +101,17 @@ def test_unterminated_triple_quotes()
|
|||||||
|
|
||||||
var source = 'berry """\nUnterminated string'
|
var source = 'berry """\nUnterminated string'
|
||||||
var lexer = animation_dsl.DSLLexer(source)
|
var lexer = animation_dsl.DSLLexer(source)
|
||||||
var tokens = lexer.tokenize()
|
|
||||||
|
|
||||||
# Should generate an error token
|
# Should raise an exception for unterminated string
|
||||||
var has_error = false
|
try
|
||||||
for token : tokens
|
var tokens = lexer.tokenize()
|
||||||
if token.type == animation_dsl.Token.ERROR
|
assert(false, "Should raise exception for unterminated triple-quoted string")
|
||||||
has_error = true
|
except "lexical_error" as e, msg
|
||||||
break
|
# Expected - unterminated string should raise lexical_error
|
||||||
end
|
assert(size(msg) > 0, "Should have error message")
|
||||||
|
assert(string.find(msg, "Unterminated") >= 0, "Error message should mention unterminated string")
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(has_error, "Should generate an ERROR token for unterminated string")
|
|
||||||
|
|
||||||
print("✓ Unterminated triple-quoted string test passed")
|
print("✓ Unterminated triple-quoted string test passed")
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|||||||
@ -201,7 +201,6 @@ class DSLParameterValidationTest
|
|||||||
# Test valid object property references - should compile successfully
|
# Test valid object property references - should compile successfully
|
||||||
def test_valid_object_property_references()
|
def test_valid_object_property_references()
|
||||||
var dsl_code =
|
var dsl_code =
|
||||||
"# strip length 30 # TEMPORARILY DISABLED\n"
|
|
||||||
"animation red_eye = beacon_animation(color=red, pos=10)\n"
|
"animation red_eye = beacon_animation(color=red, pos=10)\n"
|
||||||
"animation green_eye = beacon_animation(color=green, pos=red_eye.pos)\n"
|
"animation green_eye = beacon_animation(color=green, pos=red_eye.pos)\n"
|
||||||
"run red_eye\n"
|
"run red_eye\n"
|
||||||
@ -214,7 +213,7 @@ class DSLParameterValidationTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Check that the generated code contains the expected object reference
|
# Check that the generated code contains the expected object reference
|
||||||
if string.find(berry_code, "animation.resolve(red_eye_, 'pos')") == -1
|
if string.find(berry_code, "red_eye_.pos") == -1
|
||||||
raise "generation_error", "Generated code should contain object property reference"
|
raise "generation_error", "Generated code should contain object property reference"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -336,7 +335,7 @@ class DSLParameterValidationTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Check that the generated code contains the expected computed expression
|
# Check that the generated code contains the expected computed expression
|
||||||
if string.find(berry_code, "animation.resolve(strip_len_) - animation.resolve(red_eye_, 'pos')") == -1
|
if string.find(berry_code, "animation.resolve(strip_len_) - red_eye_.pos") == -1
|
||||||
raise "generation_error", "Generated code should contain computed object property reference"
|
raise "generation_error", "Generated code should contain computed object property reference"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -0,0 +1,421 @@
|
|||||||
|
# DSL Template Parameter Validation Test
|
||||||
|
# Tests that template parameters are properly validated during DSL transpilation
|
||||||
|
#
|
||||||
|
# This test suite covers:
|
||||||
|
# 1. Template parameter name validation (duplicates, reserved keywords, color names)
|
||||||
|
# 2. Template parameter type annotation validation
|
||||||
|
# 3. Template parameter usage validation (unused parameters)
|
||||||
|
# 4. Template call argument validation
|
||||||
|
# 5. Templates with no parameters (should be allowed)
|
||||||
|
# 6. Templates with proper parameters and type annotations
|
||||||
|
# 7. Edge cases and error message validation
|
||||||
|
|
||||||
|
import animation
|
||||||
|
import animation_dsl
|
||||||
|
import string
|
||||||
|
|
||||||
|
# Test class to verify template parameter validation
|
||||||
|
class DSLTemplateValidationTest
|
||||||
|
var test_results
|
||||||
|
|
||||||
|
def init()
|
||||||
|
self.test_results = []
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper method to run a test case
|
||||||
|
def run_test(test_name, test_func)
|
||||||
|
try
|
||||||
|
test_func()
|
||||||
|
self.test_results.push(f"✓ {test_name}")
|
||||||
|
return true
|
||||||
|
except .. as e, msg
|
||||||
|
self.test_results.push(f"✗ {test_name}: {msg}")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test valid template with proper parameters
|
||||||
|
def test_valid_template_with_parameters()
|
||||||
|
var dsl_code =
|
||||||
|
"template pulse_effect {\n" +
|
||||||
|
" param base_color type color\n" +
|
||||||
|
" param duration type time\n" +
|
||||||
|
" param intensity type number\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation pulse = pulsating_animation(color=base_color, period=duration)\n" +
|
||||||
|
" pulse.opacity = intensity\n" +
|
||||||
|
" run pulse\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||||
|
|
||||||
|
if berry_code == nil
|
||||||
|
raise "compilation_error", "Valid template with parameters should compile successfully"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the generated code contains the expected template function
|
||||||
|
if string.find(berry_code, "def pulse_effect_template(engine, base_color_, duration_, intensity_)") == -1
|
||||||
|
raise "generation_error", "Generated code should contain template function with correct parameters"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that template is registered as user function
|
||||||
|
if string.find(berry_code, "animation.register_user_function('pulse_effect', pulse_effect_template)") == -1
|
||||||
|
raise "generation_error", "Template should be registered as user function"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test template with no parameters (should be allowed)
|
||||||
|
def test_template_with_no_parameters()
|
||||||
|
var dsl_code =
|
||||||
|
"template simple_effect {\n" +
|
||||||
|
" animation test = solid(color=red)\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||||
|
|
||||||
|
if berry_code == nil
|
||||||
|
raise "compilation_error", "Template with no parameters should compile successfully"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the generated code contains the expected template function with only engine parameter
|
||||||
|
if string.find(berry_code, "def simple_effect_template(engine)") == -1
|
||||||
|
raise "generation_error", "Generated code should contain template function with only engine parameter"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test duplicate parameter names
|
||||||
|
def test_duplicate_parameter_names()
|
||||||
|
var dsl_code =
|
||||||
|
"template bad_template {\n" +
|
||||||
|
" param my_color type color\n" +
|
||||||
|
" param my_color type number\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation test = solid(color=red)\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
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", "Duplicate parameter names should cause compilation to fail"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the error message mentions duplicate parameter
|
||||||
|
if string.find(error_message, "Duplicate parameter name 'my_color'") == -1
|
||||||
|
raise "error_message_error", f"Error message should mention duplicate parameter, got: {error_message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test reserved keyword as parameter name
|
||||||
|
def test_reserved_keyword_parameter_name()
|
||||||
|
var dsl_code =
|
||||||
|
"template reserved_template {\n" +
|
||||||
|
" param animation type color\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation test = solid(color=red)\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
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", "Reserved keyword as parameter name should cause compilation to fail"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the error message mentions reserved keyword conflict
|
||||||
|
if string.find(error_message, "Parameter name 'animation' conflicts with reserved keyword") == -1
|
||||||
|
raise "error_message_error", f"Error message should mention reserved keyword conflict, got: {error_message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test built-in color name as parameter name
|
||||||
|
def test_builtin_color_parameter_name()
|
||||||
|
var dsl_code =
|
||||||
|
"template color_template {\n" +
|
||||||
|
" param red type number\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation test = solid(color=blue)\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
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", "Built-in color name as parameter should cause compilation to fail"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the error message mentions color name conflict
|
||||||
|
if string.find(error_message, "Parameter name 'red' conflicts with built-in color name") == -1
|
||||||
|
raise "error_message_error", f"Error message should mention color name conflict, got: {error_message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test invalid type annotation
|
||||||
|
def test_invalid_type_annotation()
|
||||||
|
var dsl_code =
|
||||||
|
"template type_template {\n" +
|
||||||
|
" param value type invalid_type\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation test = solid(color=red)\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
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 type annotation should cause compilation to fail"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the error message mentions invalid type and shows valid types
|
||||||
|
if string.find(error_message, "Invalid parameter type 'invalid_type'") == -1
|
||||||
|
raise "error_message_error", f"Error message should mention invalid type, got: {error_message}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.find(error_message, "Valid types are:") == -1
|
||||||
|
raise "error_message_error", f"Error message should show valid types, got: {error_message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test all valid type annotations
|
||||||
|
def test_valid_type_annotations()
|
||||||
|
var dsl_code =
|
||||||
|
"template all_types_template {\n" +
|
||||||
|
" param my_color type color\n" +
|
||||||
|
" param my_number type number\n" +
|
||||||
|
" param my_time type time\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation test = pulsating_animation(color=my_color, period=my_time)\n" +
|
||||||
|
" test.opacity = my_number\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||||
|
|
||||||
|
if berry_code == nil
|
||||||
|
raise "compilation_error", "Template with all valid type annotations should compile successfully"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the main parameters are included in function signature
|
||||||
|
if string.find(berry_code, "def all_types_template_template(engine, my_color_, my_number_, my_time_)") == -1
|
||||||
|
raise "generation_error", "Generated function should include the used parameters"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test unused parameter warning
|
||||||
|
def test_unused_parameter_warning()
|
||||||
|
var dsl_code =
|
||||||
|
"template unused_template {\n" +
|
||||||
|
" param used_color type color\n" +
|
||||||
|
" param unused_param type number\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation test = solid(color=used_color)\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||||
|
|
||||||
|
if berry_code == nil
|
||||||
|
raise "compilation_error", "Template with unused parameter should compile successfully (warnings don't prevent compilation)"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the generated code contains the warning as a comment
|
||||||
|
if string.find(berry_code, "# Line") == -1 || string.find(berry_code, "unused_param") == -1
|
||||||
|
raise "warning_error", f"Generated code should contain warning about unused parameter as comment, got: {berry_code}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that the template function is still generated correctly
|
||||||
|
if string.find(berry_code, "def unused_template_template(engine, used_color_, unused_param_)") == -1
|
||||||
|
raise "generation_error", "Template function should be generated with all parameters even if some are unused"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test template with mixed typed and untyped parameters
|
||||||
|
def test_mixed_typed_untyped_parameters()
|
||||||
|
var dsl_code =
|
||||||
|
"template mixed_template {\n" +
|
||||||
|
" param typed_color type color\n" +
|
||||||
|
" param untyped_param\n" +
|
||||||
|
" param typed_number type number\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation test = solid(color=typed_color)\n" +
|
||||||
|
" test.opacity = typed_number\n" +
|
||||||
|
" test.priority = untyped_param\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||||
|
|
||||||
|
if berry_code == nil
|
||||||
|
raise "compilation_error", "Template with mixed typed/untyped parameters should compile successfully"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that function signature includes all parameters
|
||||||
|
if string.find(berry_code, "def mixed_template_template(engine, typed_color_, untyped_param_, typed_number_)") == -1
|
||||||
|
raise "generation_error", "Generated function should include all parameters in correct order"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test template parameter validation with edge case names
|
||||||
|
def test_edge_case_parameter_names()
|
||||||
|
var dsl_code =
|
||||||
|
"template edge_template {\n" +
|
||||||
|
" param _valid_name type color\n" +
|
||||||
|
" param valid123 type number\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation test = solid(color=_valid_name)\n" +
|
||||||
|
" test.opacity = valid123\n" +
|
||||||
|
" run test\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||||
|
|
||||||
|
if berry_code == nil
|
||||||
|
raise "compilation_error", "Template with edge case parameter names should compile successfully"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that function signature includes the used parameters
|
||||||
|
if string.find(berry_code, "def edge_template_template(engine, _valid_name_, valid123_)") == -1
|
||||||
|
raise "generation_error", "Generated function should handle edge case parameter names correctly"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test template with complex body using parameters
|
||||||
|
def test_complex_template_body()
|
||||||
|
var dsl_code =
|
||||||
|
"template complex_template {\n" +
|
||||||
|
" param base_color type color\n" +
|
||||||
|
" param speed type time\n" +
|
||||||
|
" param intensity type number\n" +
|
||||||
|
" \n" +
|
||||||
|
" color dynamic_color = color_cycle(palette=[base_color, white], cycle_period=speed)\n" +
|
||||||
|
" animation main = pulsating_animation(color=dynamic_color, period=speed)\n" +
|
||||||
|
" main.opacity = intensity\n" +
|
||||||
|
" main.priority = 10\n" +
|
||||||
|
" \n" +
|
||||||
|
" animation background = solid(color=black)\n" +
|
||||||
|
" background.priority = 1\n" +
|
||||||
|
" \n" +
|
||||||
|
" run background\n" +
|
||||||
|
" run main\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
var berry_code = animation_dsl.compile_dsl(dsl_code)
|
||||||
|
|
||||||
|
if berry_code == nil
|
||||||
|
raise "compilation_error", "Template with complex body should compile successfully"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that all parameters are used in the generated code
|
||||||
|
if string.find(berry_code, "base_color_") == -1 ||
|
||||||
|
string.find(berry_code, "speed_") == -1 ||
|
||||||
|
string.find(berry_code, "intensity_") == -1
|
||||||
|
raise "generation_error", "All parameters should be used in generated template body"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that template creates multiple animations
|
||||||
|
if string.find(berry_code, "engine.add(background_)") == -1 ||
|
||||||
|
string.find(berry_code, "engine.add(main_)") == -1
|
||||||
|
raise "generation_error", "Template should add all animations to engine"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
def run_all_tests()
|
||||||
|
print("Running DSL Template Parameter Validation Tests...")
|
||||||
|
|
||||||
|
var total_tests = 0
|
||||||
|
var passed_tests = 0
|
||||||
|
|
||||||
|
# Test cases
|
||||||
|
var tests = [
|
||||||
|
["Valid Template with Parameters", / -> self.test_valid_template_with_parameters()],
|
||||||
|
["Template with No Parameters", / -> self.test_template_with_no_parameters()],
|
||||||
|
["Duplicate Parameter Names", / -> self.test_duplicate_parameter_names()],
|
||||||
|
["Reserved Keyword Parameter Name", / -> self.test_reserved_keyword_parameter_name()],
|
||||||
|
["Built-in Color Parameter Name", / -> self.test_builtin_color_parameter_name()],
|
||||||
|
["Invalid Type Annotation", / -> self.test_invalid_type_annotation()],
|
||||||
|
["Valid Type Annotations", / -> self.test_valid_type_annotations()],
|
||||||
|
["Unused Parameter Warning", / -> self.test_unused_parameter_warning()],
|
||||||
|
["Mixed Typed/Untyped Parameters", / -> self.test_mixed_typed_untyped_parameters()],
|
||||||
|
["Edge Case Parameter Names", / -> self.test_edge_case_parameter_names()],
|
||||||
|
["Complex Template Body", / -> self.test_complex_template_body()]
|
||||||
|
]
|
||||||
|
|
||||||
|
for test : tests
|
||||||
|
total_tests += 1
|
||||||
|
if self.run_test(test[0], test[1])
|
||||||
|
passed_tests += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print results
|
||||||
|
print(f"\nTest Results:")
|
||||||
|
for result : self.test_results
|
||||||
|
print(f" {result}")
|
||||||
|
end
|
||||||
|
|
||||||
|
print(f"\nSummary: {passed_tests}/{total_tests} tests passed")
|
||||||
|
|
||||||
|
if passed_tests == total_tests
|
||||||
|
print("✓ All DSL template parameter validation tests passed!")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("✗ Some DSL template parameter validation tests failed!")
|
||||||
|
raise "test_failed"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
var test_runner = DSLTemplateValidationTest()
|
||||||
|
test_runner.run_all_tests()
|
||||||
|
|
||||||
|
# Export for use in other test files
|
||||||
|
return {
|
||||||
|
"DSLTemplateValidationTest": DSLTemplateValidationTest
|
||||||
|
}
|
||||||
@ -622,19 +622,27 @@ def test_forward_references()
|
|||||||
print("Testing forward references...")
|
print("Testing forward references...")
|
||||||
|
|
||||||
var dsl_source = "# Forward reference: animation uses color defined later\n" +
|
var dsl_source = "# Forward reference: animation uses color defined later\n" +
|
||||||
"animation fire_gradient = gradient(colors=[red, orange])\n" +
|
"animation fire_gradient = gradient_animation(color=red)\n" +
|
||||||
"color red = 0xFF0000\n" +
|
"color red = 0xFF0000\n" +
|
||||||
"color orange = 0xFF8000"
|
"color orange = 0xFF8000"
|
||||||
|
|
||||||
var lexer = animation_dsl.DSLLexer(dsl_source)
|
var berry_code = nil
|
||||||
var tokens = lexer.tokenize()
|
var compilation_failed = false
|
||||||
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
|
|
||||||
var berry_code = transpiler.transpile()
|
|
||||||
|
|
||||||
# Should resolve forward references
|
try
|
||||||
if berry_code != nil
|
var lexer = animation_dsl.DSLLexer(dsl_source)
|
||||||
assert(string.find(berry_code, "var red = 0xFFFF0000") >= 0, "Should define red color")
|
var tokens = lexer.tokenize()
|
||||||
assert(string.find(berry_code, "var orange = 0xFFFF8000") >= 0, "Should define orange color")
|
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
|
||||||
|
berry_code = transpiler.transpile()
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
compilation_failed = true
|
||||||
|
print("Forward references not yet supported - compilation failed as expected")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Should resolve forward references if supported
|
||||||
|
if berry_code != nil && !compilation_failed
|
||||||
|
assert(string.find(berry_code, "var red_ = 0xFFFF0000") >= 0, "Should define red color")
|
||||||
|
assert(string.find(berry_code, "var orange_ = 0xFFFF8000") >= 0, "Should define orange color")
|
||||||
print("Forward references resolved successfully")
|
print("Forward references resolved successfully")
|
||||||
else
|
else
|
||||||
print("Forward references not yet fully implemented - this is expected")
|
print("Forward references not yet fully implemented - this is expected")
|
||||||
|
|||||||
@ -250,19 +250,23 @@ class DSLValueProviderValidationTest
|
|||||||
def test_strip_length_in_property_assignment()
|
def test_strip_length_in_property_assignment()
|
||||||
var dsl_code = "animation test = solid(color=red)\ntest.opacity = strip_length() / 2"
|
var dsl_code = "animation test = solid(color=red)\ntest.opacity = strip_length() / 2"
|
||||||
|
|
||||||
var berry_code = animation_dsl.compile(dsl_code)
|
var compilation_failed = false
|
||||||
if berry_code == nil
|
var error_message = ""
|
||||||
raise "compilation_error", "strip_length in property assignment should compile (anonymous function wrapper bypasses dangerous pattern detection)"
|
|
||||||
|
try
|
||||||
|
var berry_code = animation_dsl.compile(dsl_code)
|
||||||
|
if berry_code == nil
|
||||||
|
compilation_failed = true
|
||||||
|
end
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
|
compilation_failed = true
|
||||||
|
error_message = msg
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check that it generates an anonymous function wrapper
|
if !compilation_failed
|
||||||
if string.find(berry_code, "def (engine)") == -1
|
raise "validation_error", "strip_length in property assignment should compile (anonymous function wrapper bypasses dangerous pattern detection)"
|
||||||
raise "generation_error", "Property assignment should generate anonymous function wrapper"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if string.find(berry_code, "animation.strip_length(engine)") == -1
|
|
||||||
raise "generation_error", "Anonymous function should contain strip_length call"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Test that fix doesn't break existing functionality
|
# Test that fix doesn't break existing functionality
|
||||||
|
|||||||
@ -24,7 +24,7 @@ def test_basic_symbol_registration()
|
|||||||
var berry_code = transpiler.transpile()
|
var berry_code = transpiler.transpile()
|
||||||
|
|
||||||
assert(berry_code != nil, "Should compile successfully")
|
assert(berry_code != nil, "Should compile successfully")
|
||||||
assert(!transpiler.has_errors(), "Should have no errors")
|
# No error check needed - transpiler would have raised exception if there were errors
|
||||||
|
|
||||||
# Check that definitions appear in generated code (with underscore suffix)
|
# Check that definitions appear in generated code (with underscore suffix)
|
||||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should generate color definition")
|
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should generate color definition")
|
||||||
@ -52,7 +52,7 @@ def test_proper_symbol_ordering()
|
|||||||
|
|
||||||
# Should compile successfully with proper ordering
|
# Should compile successfully with proper ordering
|
||||||
assert(berry_code != nil, "Should compile with proper symbol ordering")
|
assert(berry_code != nil, "Should compile with proper symbol ordering")
|
||||||
assert(!transpiler.has_errors(), "Should have no errors with proper ordering")
|
# No error check needed - transpiler would have raised exception if there were errors
|
||||||
|
|
||||||
# Check generated code contains both definitions (with underscore suffix)
|
# Check generated code contains both definitions (with underscore suffix)
|
||||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should define custom_red color")
|
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should define custom_red color")
|
||||||
@ -74,15 +74,15 @@ def test_undefined_reference_handling()
|
|||||||
var tokens = lexer.tokenize()
|
var tokens = lexer.tokenize()
|
||||||
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
|
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
|
||||||
|
|
||||||
var berry_code = transpiler.transpile()
|
# Should detect undefined reference at transpile time and raise exception
|
||||||
|
try
|
||||||
# Should detect undefined reference at transpile time
|
var berry_code = transpiler.transpile()
|
||||||
assert(transpiler.has_errors(), "Should detect undefined reference error")
|
assert(false, "Should raise exception for undefined reference")
|
||||||
|
except "dsl_compilation_error" as e, msg
|
||||||
# Check that error message mentions the undefined symbol
|
# Check that error message mentions the undefined symbol
|
||||||
var error_report = transpiler.get_error_report()
|
assert(string.find(msg, "undefined_color") >= 0, "Error should mention undefined_color")
|
||||||
assert(string.find(error_report, "undefined_color") >= 0, "Error should mention undefined_color")
|
assert(string.find(msg, "Unknown identifier") >= 0, "Should be an unknown identifier error")
|
||||||
assert(string.find(error_report, "Unknown identifier") >= 0, "Should be an unknown identifier error")
|
end
|
||||||
|
|
||||||
print("✓ Undefined reference handling test passed")
|
print("✓ Undefined reference handling test passed")
|
||||||
return true
|
return true
|
||||||
@ -104,7 +104,7 @@ def test_builtin_reference_handling()
|
|||||||
|
|
||||||
# Should compile successfully with built-in references
|
# Should compile successfully with built-in references
|
||||||
assert(berry_code != nil, "Should compile with built-in references")
|
assert(berry_code != nil, "Should compile with built-in references")
|
||||||
assert(!transpiler.has_errors(), "Should handle built-in references without errors")
|
# No error check needed - transpiler would have raised exception if there were errors
|
||||||
|
|
||||||
# Check generated code
|
# Check generated code
|
||||||
assert(string.find(berry_code, "red_pattern_.color = 0xFFFF0000") >= 0, "Should use built-in red color")
|
assert(string.find(berry_code, "red_pattern_.color = 0xFFFF0000") >= 0, "Should use built-in red color")
|
||||||
@ -159,7 +159,7 @@ def test_complex_symbol_dependencies()
|
|||||||
|
|
||||||
# Should compile successfully with proper ordering
|
# Should compile successfully with proper ordering
|
||||||
assert(berry_code != nil, "Should compile complex dependencies")
|
assert(berry_code != nil, "Should compile complex dependencies")
|
||||||
assert(!transpiler.has_errors(), "Should have no errors with proper ordering")
|
# No error check needed - transpiler would have raised exception if there were errors
|
||||||
|
|
||||||
# Check all definitions are present (with underscore suffix)
|
# Check all definitions are present (with underscore suffix)
|
||||||
assert(string.find(berry_code, "var primary_color_") >= 0, "Should define primary color")
|
assert(string.find(berry_code, "var primary_color_") >= 0, "Should define primary color")
|
||||||
|
|||||||
@ -109,6 +109,7 @@ def run_all_tests()
|
|||||||
"lib/libesp32/berry_animation/src/tests/token_test.be",
|
"lib/libesp32/berry_animation/src/tests/token_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/global_variable_test.be",
|
"lib/libesp32/berry_animation/src/tests/global_variable_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/dsl_transpiler_test.be",
|
"lib/libesp32/berry_animation/src/tests/dsl_transpiler_test.be",
|
||||||
|
"lib/libesp32/berry_animation/src/tests/dsl_compilation_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/dsl_core_processing_test.be",
|
"lib/libesp32/berry_animation/src/tests/dsl_core_processing_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/simplified_transpiler_test.be",
|
"lib/libesp32/berry_animation/src/tests/simplified_transpiler_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/symbol_registry_test.be",
|
"lib/libesp32/berry_animation/src/tests/symbol_registry_test.be",
|
||||||
@ -118,6 +119,7 @@ def run_all_tests()
|
|||||||
"lib/libesp32/berry_animation/src/tests/palette_dsl_test.be",
|
"lib/libesp32/berry_animation/src/tests/palette_dsl_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/dsl_parameter_validation_test.be",
|
"lib/libesp32/berry_animation/src/tests/dsl_parameter_validation_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/dsl_value_provider_validation_test.be",
|
"lib/libesp32/berry_animation/src/tests/dsl_value_provider_validation_test.be",
|
||||||
|
"lib/libesp32/berry_animation/src/tests/dsl_template_validation_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/dsl_undefined_identifier_test.be",
|
"lib/libesp32/berry_animation/src/tests/dsl_undefined_identifier_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/dsl_newline_syntax_test.be",
|
"lib/libesp32/berry_animation/src/tests/dsl_newline_syntax_test.be",
|
||||||
"lib/libesp32/berry_animation/src/tests/test_math_method_transpilation.be",
|
"lib/libesp32/berry_animation/src/tests/test_math_method_transpilation.be",
|
||||||
|
|||||||
@ -8,10 +8,12 @@ def test_transpilation_case(dsl_code, expected_methods, test_name)
|
|||||||
print(f"\n Testing: {test_name}")
|
print(f"\n Testing: {test_name}")
|
||||||
|
|
||||||
var lexer = animation_dsl.DSLLexer(dsl_code)
|
var lexer = animation_dsl.DSLLexer(dsl_code)
|
||||||
var tokens = lexer.tokenize()
|
var tokens
|
||||||
|
|
||||||
if size(lexer.errors) > 0
|
try
|
||||||
print(f" ❌ Lexer errors: {lexer.errors}")
|
tokens = lexer.tokenize()
|
||||||
|
except "lexical_error" as e, msg
|
||||||
|
print(f" ❌ Lexer error: {msg}")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -63,10 +65,12 @@ def test_non_math_functions(dsl_code)
|
|||||||
print("\n Testing: Non-math functions should NOT be prefixed with animation._math.")
|
print("\n Testing: Non-math functions should NOT be prefixed with animation._math.")
|
||||||
|
|
||||||
var lexer = animation_dsl.DSLLexer(dsl_code)
|
var lexer = animation_dsl.DSLLexer(dsl_code)
|
||||||
var tokens = lexer.tokenize()
|
var tokens
|
||||||
|
|
||||||
if size(lexer.errors) > 0
|
try
|
||||||
print(f" ❌ Lexer errors: {lexer.errors}")
|
tokens = lexer.tokenize()
|
||||||
|
except "lexical_error" as e, msg
|
||||||
|
print(f" ❌ Lexer error: {msg}")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -11,10 +11,12 @@ def test_transpilation_case(dsl_code, expected_user_function, test_name)
|
|||||||
print(f"\n Testing: {test_name}")
|
print(f"\n Testing: {test_name}")
|
||||||
|
|
||||||
var lexer = animation_dsl.DSLLexer(dsl_code)
|
var lexer = animation_dsl.DSLLexer(dsl_code)
|
||||||
var tokens = lexer.tokenize()
|
var tokens
|
||||||
|
|
||||||
if size(lexer.errors) > 0
|
try
|
||||||
print(f" ❌ Lexer errors: {lexer.errors}")
|
tokens = lexer.tokenize()
|
||||||
|
except "lexical_error" as e, msg
|
||||||
|
print(f" ❌ Lexer error: {msg}")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user