Tasmota/lib/libesp32/berry_animation/src/dsl/symbol_table.be

622 lines
21 KiB
Plaintext

# Symbol Table Classes for DSL Transpiler
# Enhanced symbol caching and management for the Animation DSL
# Symbol table entry class for enhanced symbol caching
class SymbolEntry
# Type constants
static var TYPE_PALETTE_CONSTANT = 1
static var TYPE_PALETTE = 2
static var TYPE_CONSTANT = 3
static var TYPE_MATH_FUNCTION = 4
static var TYPE_USER_FUNCTION = 5
static var TYPE_VALUE_PROVIDER_CONSTRUCTOR = 6
static var TYPE_VALUE_PROVIDER = 7
static var TYPE_ANIMATION_CONSTRUCTOR = 8
static var TYPE_ANIMATION = 9
static var TYPE_COLOR_CONSTRUCTOR = 10
static var TYPE_COLOR = 11
static var TYPE_VARIABLE = 12
static var TYPE_SEQUENCE = 13
static var TYPE_TEMPLATE = 14
var name # Symbol name
var type # Symbol type (int constant)
var instance # Actual instance (for validation) or nil
var takes_args # Boolean: whether this symbol takes arguments
var arg_type # "positional", "named", or "none"
var is_builtin # Boolean: whether this is a built-in symbol from animation module
var is_dangerous # Boolean: whether calling this symbol creates a new instance (dangerous in computed expressions)
var param_types # Map of parameter names to types (for templates and user functions)
def init(name, typ, instance, is_builtin)
self.name = name
self.type = typ
self.instance = instance
self.is_builtin = is_builtin != nil ? is_builtin : false
self.takes_args = false
self.arg_type = "none"
self.is_dangerous = false
self.param_types = {}
# Auto-detect argument characteristics and danger level based on type
self._detect_arg_characteristics()
self._detect_danger_level()
end
# Detect if this symbol takes arguments and what type
def _detect_arg_characteristics()
if self.type == self.TYPE_PALETTE_CONSTANT || self.type == self.TYPE_PALETTE || self.type == self.TYPE_CONSTANT
# Palette objects and constants don't take arguments
self.takes_args = false
self.arg_type = "none"
elif self.type == self.TYPE_MATH_FUNCTION
# Math functions like max, min take positional arguments
self.takes_args = true
self.arg_type = "positional"
elif self.type == self.TYPE_USER_FUNCTION
# User functions take positional arguments (engine + user args)
self.takes_args = true
self.arg_type = "positional"
elif self.type == self.TYPE_VALUE_PROVIDER_CONSTRUCTOR || self.type == self.TYPE_ANIMATION_CONSTRUCTOR || self.type == self.TYPE_COLOR_CONSTRUCTOR
# Constructor functions take named arguments
self.takes_args = true
self.arg_type = "named"
else
# Instances, variables, sequences, templates don't take arguments when referenced
self.takes_args = false
self.arg_type = "none"
end
end
# Detect if this symbol is dangerous (creates new instances when called)
def _detect_danger_level()
if self.type == self.TYPE_VALUE_PROVIDER_CONSTRUCTOR
# Value provider constructors create new instances - dangerous in computed expressions
self.is_dangerous = true
elif self.type == self.TYPE_ANIMATION_CONSTRUCTOR
# Animation constructors create new instances - dangerous in computed expressions
self.is_dangerous = true
elif self.type == self.TYPE_COLOR_CONSTRUCTOR
# Color provider constructors create new instances - dangerous in computed expressions
self.is_dangerous = true
else
# Constants, math functions, variables, instances, user functions, etc. are safe
self.is_dangerous = false
end
end
# Check if this symbol is a bytes() instance (for palettes)
def is_bytes_instance()
return (self.type == self.TYPE_PALETTE_CONSTANT || self.type == self.TYPE_PALETTE) && self.instance != nil && isinstance(self.instance, bytes)
end
# Check if this symbol is a math function
def is_math_function()
return self.type == self.TYPE_MATH_FUNCTION
end
# Check if this symbol is a user function
def is_user_function()
return self.type == self.TYPE_USER_FUNCTION
end
# Check if this symbol is a value provider constructor
def is_value_provider_constructor()
return self.type == self.TYPE_VALUE_PROVIDER_CONSTRUCTOR
end
# Check if this symbol is a value provider instance
def is_value_provider_instance()
return self.type == self.TYPE_VALUE_PROVIDER
end
# Check if this symbol is an animation constructor
def is_animation_constructor()
return self.type == self.TYPE_ANIMATION_CONSTRUCTOR
end
# Check if this symbol is an animation instance
def is_animation_instance()
return self.type == self.TYPE_ANIMATION
end
# Check if this symbol is a color constructor
def is_color_constructor()
return self.type == self.TYPE_COLOR_CONSTRUCTOR
end
# Check if this symbol is a color instance
def is_color_instance()
return self.type == self.TYPE_COLOR
end
# Check if this symbol takes positional arguments
def takes_positional_args()
return self.takes_args && self.arg_type == "positional"
end
# Check if this symbol takes named arguments
def takes_named_args()
return self.takes_args && self.arg_type == "named"
end
# Check if this symbol is dangerous (creates new instances when called)
def is_dangerous_call()
return self.is_dangerous
end
# Set parameter types for templates and user functions
def set_param_types(param_types)
self.param_types = param_types != nil ? param_types : {}
end
# Get parameter types
def get_param_types()
return self.param_types
end
# Convert type constant to string for debugging
def type_to_string()
if self.type == self.TYPE_PALETTE_CONSTANT return "palette_constant"
elif self.type == self.TYPE_PALETTE return "palette"
elif self.type == self.TYPE_CONSTANT return "constant"
elif self.type == self.TYPE_MATH_FUNCTION return "math_function"
elif self.type == self.TYPE_USER_FUNCTION return "user_function"
elif self.type == self.TYPE_VALUE_PROVIDER_CONSTRUCTOR return "value_provider_constructor"
elif self.type == self.TYPE_VALUE_PROVIDER return "value_provider"
elif self.type == self.TYPE_ANIMATION_CONSTRUCTOR return "animation_constructor"
elif self.type == self.TYPE_ANIMATION return "animation"
elif self.type == self.TYPE_COLOR_CONSTRUCTOR return "color_constructor"
elif self.type == self.TYPE_COLOR return "color"
elif self.type == self.TYPE_VARIABLE return "variable"
elif self.type == self.TYPE_SEQUENCE return "sequence"
elif self.type == self.TYPE_TEMPLATE return "template"
else return f"unknown({self.type})"
end
end
# Get the resolved symbol reference for code generation
def get_reference()
# Generate appropriate reference based on whether it's built-in
if self.is_builtin
# Special handling for math functions
if self.type == self.TYPE_MATH_FUNCTION
return f"animation._math.{self.name}"
else
return f"animation.{self.name}"
end
else
# User-defined symbols get underscore suffix
return f"{self.name}_"
end
end
# String representation for debugging
def tostring()
import string
var instance_str = "nil"
if self.instance != nil
var instance_type = type(self.instance)
if instance_type == "instance"
instance_str = f"<{classname(self.instance)}>"
else
instance_str = f"<{instance_type}:{str(self.instance)}>"
end
end
var param_types_str = ""
if size(self.param_types) > 0
var params_list = ""
var first = true
for key : self.param_types.keys()
if !first
params_list += ","
end
params_list += f"{key}:{self.param_types[key]}"
first = false
end
param_types_str = f" params=[{params_list}]"
end
return f"SymbolEntry(name='{self.name}', type='{self.type_to_string()}', instance={instance_str}, " +
f"takes_args={self.takes_args}, arg_type='{self.arg_type}', " +
f"is_builtin={self.is_builtin}, is_dangerous={self.is_dangerous}{param_types_str})"
end
# Create a symbol entry for a palette constant (built-in like PALETTE_RAINBOW)
static def create_palette_constant(name, instance, is_builtin)
return _class(name, _class.TYPE_PALETTE_CONSTANT, instance, is_builtin)
end
# Create a symbol entry for a palette instance (user-defined)
static def create_palette_instance(name, instance, is_builtin)
return _class(name, _class.TYPE_PALETTE, instance, is_builtin)
end
# Create a symbol entry for an integer constant
static def create_constant(name, instance, is_builtin)
return _class(name, _class.TYPE_CONSTANT, instance, is_builtin)
end
# Create a symbol entry for a math function
static def create_math_function(name, is_builtin)
return _class(name, _class.TYPE_MATH_FUNCTION, nil, is_builtin)
end
# Create a symbol entry for a user function
static def create_user_function(name, is_builtin)
return _class(name, _class.TYPE_USER_FUNCTION, nil, is_builtin)
end
# Create a symbol entry for a value provider constructor (built-in like triangle, smooth)
static def create_value_provider_constructor(name, instance, is_builtin)
return _class(name, _class.TYPE_VALUE_PROVIDER_CONSTRUCTOR, instance, is_builtin)
end
# Create a symbol entry for a value provider instance (user-defined)
static def create_value_provider_instance(name, instance, is_builtin)
return _class(name, _class.TYPE_VALUE_PROVIDER, instance, is_builtin)
end
# Create a symbol entry for an animation constructor (built-in like solid, breathe)
static def create_animation_constructor(name, instance, is_builtin)
return _class(name, _class.TYPE_ANIMATION_CONSTRUCTOR, instance, is_builtin)
end
# Create a symbol entry for an animation instance (user-defined)
static def create_animation_instance(name, instance, is_builtin)
return _class(name, _class.TYPE_ANIMATION, instance, is_builtin)
end
# Create a symbol entry for a color constructor (built-in like color_cycle, breathe_color)
static def create_color_constructor(name, instance, is_builtin)
return _class(name, _class.TYPE_COLOR_CONSTRUCTOR, instance, is_builtin)
end
# Create a symbol entry for a color instance (user-defined)
static def create_color_instance(name, instance, is_builtin)
return _class(name, _class.TYPE_COLOR, instance, is_builtin)
end
# Create a symbol entry for a variable
static def create_variable(name, is_builtin)
return _class(name, _class.TYPE_VARIABLE, nil, is_builtin)
end
# Create a symbol entry for a sequence
static def create_sequence(name, is_builtin)
return _class(name, _class.TYPE_SEQUENCE, nil, is_builtin)
end
# Create a symbol entry for a template
static def create_template(name, is_builtin)
return _class(name, _class.TYPE_TEMPLATE, nil, is_builtin)
end
end
# Mock engine class for parameter validation during transpilation
class MockEngine
var time_ms
var strip_length
def init()
self.time_ms = 0
self.strip_length = 30 # Default strip length for validation
end
def get_strip_length()
return self.strip_length
end
def add(obj)
return true
end
end
# Enhanced symbol table class for holistic symbol management and caching
class SymbolTable
var entries # Map of name -> SymbolEntry
var mock_engine # MockEngine for validation
def init()
import animation_dsl
self.entries = {}
self.mock_engine = animation_dsl.MockEngine()
end
# Dynamically detect and cache symbol type when first encountered
def _detect_and_cache_symbol(name)
import animation_dsl
if self.entries.contains(name)
return self.entries[name] # Already cached
end
try
import introspect
# Check for named colors first (from animation_dsl.named_colors)
if animation_dsl.named_colors.contains(name)
var entry = animation_dsl._symbol_entry.create_color_instance(name, nil, true) # true = is_builtin
self.entries[name] = entry
return entry
end
# Check for special built-in functions like 'log'
if name == "log"
var entry = animation_dsl._symbol_entry.create_user_function("log", true) # true = is_builtin
self.entries[name] = entry
return entry
end
# Check for user functions (they might not be in animation module directly)
if animation.is_user_function(name)
var entry = animation_dsl._symbol_entry.create_user_function(name, true)
self.entries[name] = entry
return entry
end
# Check for math functions (they are in animation._math, not directly in animation)
if introspect.contains(animation._math, name)
var entry = animation_dsl._symbol_entry.create_math_function(name, true)
self.entries[name] = entry
return entry
end
# Check if it exists in animation module
if introspect.contains(animation, name)
var obj = animation.(name)
var obj_type = type(obj)
# Detect palette objects (bytes() instances)
if isinstance(obj, bytes)
var entry = animation_dsl._symbol_entry.create_palette_constant(name, obj, true)
self.entries[name] = entry
return entry
end
# Detect integer constants (like LINEAR, SINE, COSINE, etc.)
if obj_type == "int"
var entry = animation_dsl._symbol_entry.create_constant(name, obj, true)
self.entries[name] = entry
return entry
end
# Detect constructors (functions/classes that create instances)
if obj_type == "function" || obj_type == "class"
try
var instance = obj(self.mock_engine)
if isinstance(instance, animation.color_provider)
# Color providers are a subclass of value providers, check them first
var entry = animation_dsl._symbol_entry.create_color_constructor(name, instance, true)
self.entries[name] = entry
return entry
elif isinstance(instance, animation.value_provider)
var entry = animation_dsl._symbol_entry.create_value_provider_constructor(name, instance, true)
self.entries[name] = entry
return entry
elif isinstance(instance, animation.animation)
var entry = animation_dsl._symbol_entry.create_animation_constructor(name, instance, true)
self.entries[name] = entry
return entry
end
except .. as e, msg
# If instance creation fails, it might still be a valid function
# but not a constructor we can validate
end
end
end
# If not found in animation module, return nil (will be handled as user-defined)
return nil
except .. as e, msg
# If detection fails, return nil
return nil
end
end
# Add a symbol entry to the table (with conflict detection) - returns the entry
def add(name, entry)
# First check if there's a built-in symbol with this name
var builtin_entry = self._detect_and_cache_symbol(name)
if builtin_entry != nil && builtin_entry.type != entry.type
raise "symbol_redefinition_error", f"Cannot define '{name}' as {entry.type_to_string()} - it conflicts with built-in {builtin_entry.type_to_string()}"
end
# Check existing user-defined symbols
var existing = self.entries.find(name)
if existing != nil
# Check if it's the same type
if existing.type != entry.type
raise "symbol_redefinition_error", f"Cannot redefine symbol '{name}' as {entry.type_to_string()} - it's already defined as {existing.type_to_string()}"
end
# If same type, allow update (for cases like reassignment)
end
self.entries[name] = entry
return entry
end
# Check if a symbol exists (with dynamic detection)
def contains(name)
if self.entries.contains(name)
return true
end
# Try to detect and cache it
var entry = self._detect_and_cache_symbol(name)
return entry != nil
end
# Get a symbol entry (with dynamic detection)
def get(name)
var entry = self.entries.find(name)
if entry != nil
return entry
end
# Try to detect and cache it
return self._detect_and_cache_symbol(name)
end
# Get symbol reference for code generation (with dynamic detection)
def get_reference(name)
import animation_dsl
# Try to get from cache or detect dynamically (includes named colors)
var entry = self.get(name)
if entry != nil
# For builtin color entries, return the actual color value directly
if entry.is_builtin && entry.type == 11 #-animation_dsl._symbol_entry.TYPE_COLOR-#
var color_value = animation_dsl.named_colors[name]
# Convert integer to hex string format for transpiler
return f"0x{color_value:08X}"
end
return entry.get_reference()
end
# Default to user-defined format
return f"{name}_"
end
# Check if symbol exists (including named colors, with dynamic detection)
def symbol_exists(name)
# Use proper discovery through _detect_and_cache_symbol via contains()
return self.contains(name)
end
# Create and register a palette instance symbol (user-defined)
def create_palette(name, instance)
import animation_dsl
var entry = animation_dsl._symbol_entry.create_palette_instance(name, instance, false)
return self.add(name, entry)
end
# Create and register a color instance symbol (user-defined)
def create_color(name, instance)
import animation_dsl
var entry = animation_dsl._symbol_entry.create_color_instance(name, instance, false)
return self.add(name, entry)
end
# Create and register an animation instance symbol (user-defined)
def create_animation(name, instance)
import animation_dsl
var entry = animation_dsl._symbol_entry.create_animation_instance(name, instance, false)
return self.add(name, entry)
end
# Create and register a value provider instance symbol (user-defined)
def create_value_provider(name, instance)
import animation_dsl
var entry = animation_dsl._symbol_entry.create_value_provider_instance(name, instance, false)
return self.add(name, entry)
end
# Create and register a variable symbol (user-defined)
def create_variable(name)
import animation_dsl
var entry = animation_dsl._symbol_entry.create_variable(name, false)
return self.add(name, entry)
end
# Create and register a sequence symbol (user-defined)
def create_sequence(name)
import animation_dsl
var entry = animation_dsl._symbol_entry.create_sequence(name, false)
return self.add(name, entry)
end
# Create and register a template symbol (user-defined)
def create_template(name, param_types)
import animation_dsl
var entry = animation_dsl._symbol_entry.create_template(name, false)
entry.set_param_types(param_types != nil ? param_types : {})
return self.add(name, entry)
end
# Register a user function (detected at runtime)
def register_user_function(name)
import animation_dsl
if !self.contains(name)
var entry = animation_dsl._symbol_entry.create_user_function(name, false)
self.add(name, entry)
end
end
# Generic create function that can specify name/type/instance/builtin directly
def create_generic(name, typ, instance, is_builtin)
import animation_dsl
var entry = animation_dsl._symbol_entry(name, typ, instance, is_builtin != nil ? is_builtin : false)
return self.add(name, entry)
end
# Get the type of a symbol
def get_type(name)
var entry = self.get(name)
return entry != nil ? entry.type_to_string() : nil
end
# Check if symbol takes arguments
def takes_args(name)
var entry = self.get(name)
return entry != nil ? entry.takes_args : false
end
# Check if symbol takes positional arguments
def takes_positional_args(name)
var entry = self.get(name)
return entry != nil ? entry.takes_positional_args() : false
end
# Check if symbol takes named arguments
def takes_named_args(name)
var entry = self.get(name)
return entry != nil ? entry.takes_named_args() : false
end
# Get instance for validation
def get_instance(name)
var entry = self.get(name)
return entry != nil ? entry.instance : nil
end
# Check if symbol is dangerous (creates new instances when called)
def is_dangerous(name)
var entry = self.get(name)
return entry != nil ? entry.is_dangerous_call() : false
end
# Helper method to get named color value (uses proper discovery)
def _get_named_color_value(color_name)
import animation_dsl
var entry = self.get(color_name) # This will trigger _detect_and_cache_symbol if needed
if entry != nil && entry.is_builtin && entry.type == 11 #-animation_dsl._symbol_entry.TYPE_COLOR-#
var color_value = animation_dsl.named_colors[color_name]
# Convert integer to hex string format for transpiler
return f"0x{color_value:08X}"
end
return "0xFFFFFFFF" # Default fallback
end
# Debug method to list all symbols
def list_symbols()
var result = []
for name : self.entries.keys()
var entry = self.entries[name]
result.push(f"{name}: {entry.type_to_string()}")
end
return result
end
end
# Return module exports
return {
"_symbol_entry": SymbolEntry,
"_symbol_table": SymbolTable,
"MockEngine": MockEngine
}