Berry animation transpiler refactoring phase 2 (#23900)

This commit is contained in:
s-hadinger 2025-09-10 20:06:19 +02:00 committed by GitHub
parent 7315b06969
commit 86e48395c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 12897 additions and 13417 deletions

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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)
) )

View File

@ -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"

View File

@ -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

View File

@ -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)
) )

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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")

View File

@ -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

View File

@ -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")

View File

@ -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",

View File

@ -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

View File

@ -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