IncludeCPP 4.0.3__py3-none-any.whl → 4.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -883,6 +883,18 @@ class CSSLRuntime:
883
883
  class_params = class_info.get('class_params', [])
884
884
  extends_args = class_info.get('extends_args', [])
885
885
 
886
+ # v4.2.0: Handle 'supports' language transformation for raw_body
887
+ supports_language = class_info.get('supports_language')
888
+ raw_body = class_info.get('raw_body')
889
+
890
+ if raw_body and supports_language:
891
+ # Transform raw body from target language to CSSL and parse
892
+ transformed_children = self._transform_and_parse_class_body(
893
+ raw_body, supports_language, class_name
894
+ )
895
+ # Add transformed children to node's children
896
+ node.children = transformed_children
897
+
886
898
  for child in node.children:
887
899
  if child.type == 'constructor':
888
900
  # New-style constructor from 'constr' keyword
@@ -1007,6 +1019,119 @@ class CSSLRuntime:
1007
1019
  self._current_instance = old_instance
1008
1020
  return wrapper
1009
1021
 
1022
+ def _transform_and_parse_class_body(self, raw_body: str, language: str, class_name: str) -> list:
1023
+ """Transform source code from another language to CSSL and parse as class body.
1024
+
1025
+ v4.2.0: Used for 'supports <lang>' in class definitions.
1026
+
1027
+ Args:
1028
+ raw_body: Raw source code in the target language
1029
+ language: Language identifier (py, python, cpp, c++, js, javascript, etc.)
1030
+ class_name: Name of the class (for constructor recognition)
1031
+
1032
+ Returns:
1033
+ List of parsed AST nodes representing methods, constructors, and members
1034
+ """
1035
+ import textwrap
1036
+ from .cssl_languages import get_language
1037
+ from .cssl_parser import parse_cssl_program, ASTNode
1038
+
1039
+ # Normalize language ID
1040
+ lang_id = language.lstrip('@').lower()
1041
+
1042
+ # Get language support and transformer
1043
+ lang_support = get_language(lang_id)
1044
+ if lang_support is None:
1045
+ raise CSSLRuntimeError(f"Unknown language '{lang_id}' in 'supports' clause")
1046
+
1047
+ # Dedent the raw body to normalize indentation
1048
+ # This fixes the issue where code inside CSSL {} has relative indentation
1049
+ dedented_body = textwrap.dedent(raw_body)
1050
+
1051
+ # Transform the raw body to CSSL syntax
1052
+ transformer = lang_support.get_transformer()
1053
+ transformed_source = transformer.transform_source(dedented_body)
1054
+
1055
+ # Wrap in a dummy class for parsing
1056
+ wrapper_source = f"class _TempClass {{\n{transformed_source}\n}}"
1057
+
1058
+ try:
1059
+ ast = parse_cssl_program(wrapper_source)
1060
+ except Exception as e:
1061
+ raise CSSLRuntimeError(
1062
+ f"Failed to parse transformed '{lang_id}' code: {e}\n"
1063
+ f"Dedented:\n{dedented_body}\n"
1064
+ f"Transformed:\n{transformed_source}"
1065
+ )
1066
+
1067
+ # Extract children from the parsed temp class
1068
+ children = []
1069
+ for top_level in ast.children:
1070
+ if top_level.type == 'class':
1071
+ for child in top_level.children:
1072
+ # Mark constructor if method name matches class_name or is __init__
1073
+ if child.type == 'function':
1074
+ func_info = child.value
1075
+ method_name = func_info.get('name')
1076
+ if method_name == class_name or method_name == '__init__':
1077
+ child.value['is_constructor'] = True
1078
+ children.append(child)
1079
+ break
1080
+
1081
+ return children
1082
+
1083
+ def _transform_and_parse_function_body(self, raw_body: str, language: str) -> list:
1084
+ """Transform source code from another language to CSSL and parse as function body.
1085
+
1086
+ v4.2.0: Used for 'supports <lang>' in function definitions.
1087
+
1088
+ Args:
1089
+ raw_body: Raw source code in the target language
1090
+ language: Language identifier (py, python, cpp, c++, js, javascript, etc.)
1091
+
1092
+ Returns:
1093
+ List of parsed AST nodes representing statements in the function body
1094
+ """
1095
+ import textwrap
1096
+ from .cssl_languages import get_language
1097
+ from .cssl_parser import parse_cssl_program
1098
+
1099
+ # Normalize language ID
1100
+ lang_id = language.lstrip('@').lower()
1101
+
1102
+ # Get language support and transformer
1103
+ lang_support = get_language(lang_id)
1104
+ if lang_support is None:
1105
+ raise CSSLRuntimeError(f"Unknown language '{lang_id}' in 'supports' clause")
1106
+
1107
+ # Dedent the raw body to normalize indentation
1108
+ dedented_body = textwrap.dedent(raw_body)
1109
+
1110
+ # Transform the raw body to CSSL syntax
1111
+ transformer = lang_support.get_transformer()
1112
+ transformed_source = transformer.transform_source(dedented_body)
1113
+
1114
+ # Wrap in a dummy function for parsing
1115
+ wrapper_source = f"define _TempFunc() {{\n{transformed_source}\n}}"
1116
+
1117
+ try:
1118
+ ast = parse_cssl_program(wrapper_source)
1119
+ except Exception as e:
1120
+ raise CSSLRuntimeError(
1121
+ f"Failed to parse transformed '{lang_id}' code: {e}\n"
1122
+ f"Dedented:\n{dedented_body}\n"
1123
+ f"Transformed:\n{transformed_source}"
1124
+ )
1125
+
1126
+ # Extract children from the parsed temp function
1127
+ children = []
1128
+ for top_level in ast.children:
1129
+ if top_level.type == 'function':
1130
+ children = top_level.children
1131
+ break
1132
+
1133
+ return children
1134
+
1010
1135
  def _exec_function(self, node: ASTNode) -> Any:
1011
1136
  """Execute function definition - registers it and handles extends/overwrites.
1012
1137
 
@@ -1172,16 +1297,22 @@ class CSSLRuntime:
1172
1297
  if ref_member and hasattr(ref_obj, ref_member):
1173
1298
  original_method = getattr(ref_obj, ref_member)
1174
1299
  runtime = self
1300
+ # Store the original method before wrapping
1301
+ _saved_original = original_method
1175
1302
  def appended_wrapper(*args, **kwargs):
1176
- # Run original first
1303
+ # Run original first (use saved reference to avoid recursion)
1177
1304
  result = None
1178
- if callable(original_method):
1305
+ if callable(_saved_original):
1179
1306
  try:
1180
- result = original_method(*args, **kwargs)
1307
+ result = _saved_original(*args, **kwargs)
1181
1308
  except:
1182
1309
  pass
1183
- # Then run appended code
1184
- return runtime._call_function(append_node, list(args), kwargs)
1310
+ # Then run appended code - disable append_mode to prevent recursion
1311
+ append_node.value['append_mode'] = False
1312
+ try:
1313
+ return runtime._call_function(append_node, list(args), kwargs)
1314
+ finally:
1315
+ append_node.value['append_mode'] = True
1185
1316
  try:
1186
1317
  setattr(ref_obj, ref_member, appended_wrapper)
1187
1318
  except (AttributeError, TypeError):
@@ -1546,13 +1677,15 @@ class CSSLRuntime:
1546
1677
 
1547
1678
  # Bind parameters - handle both positional and named arguments
1548
1679
  for i, param in enumerate(params):
1549
- # Extract param name and type from dict format: {'name': 'a', 'type': 'int'}
1680
+ # Extract param name, type, and default from dict format: {'name': 'a', 'type': 'int', 'default': ...}
1550
1681
  if isinstance(param, dict):
1551
1682
  param_name = param['name']
1552
1683
  param_type = param.get('type', '')
1684
+ param_default = param.get('default') # v4.2.0: Default value AST node
1553
1685
  else:
1554
1686
  param_name = param
1555
1687
  param_type = ''
1688
+ param_default = None
1556
1689
 
1557
1690
  # Check if this is an 'open' parameter - receives all args as a list
1558
1691
  # The parser sets param['open'] = True for 'open' keyword
@@ -1575,6 +1708,10 @@ class CSSLRuntime:
1575
1708
  elif i < len(args):
1576
1709
  # Positional argument
1577
1710
  new_scope.set(param_name, args[i])
1711
+ elif param_default is not None:
1712
+ # v4.2.0: Use default value if no argument provided
1713
+ default_value = self._evaluate(param_default)
1714
+ new_scope.set(param_name, default_value)
1578
1715
  else:
1579
1716
  new_scope.set(param_name, None)
1580
1717
 
@@ -1594,11 +1731,24 @@ class CSSLRuntime:
1594
1731
  args, kwargs, {}, is_constructor=False
1595
1732
  )
1596
1733
 
1597
- for child in func_node.children:
1598
- # Check if exit() was called
1599
- if not self._running:
1600
- break
1601
- self._execute_node(child)
1734
+ # v4.2.0: Handle raw_body with supports_language (multi-language support)
1735
+ raw_body = func_info.get('raw_body')
1736
+ supports_language = func_info.get('supports_language')
1737
+
1738
+ if raw_body and supports_language:
1739
+ # Transform and parse the raw body from the target language
1740
+ body_children = self._transform_and_parse_function_body(raw_body, supports_language)
1741
+ for child in body_children:
1742
+ if not self._running:
1743
+ break
1744
+ self._execute_node(child)
1745
+ else:
1746
+ # Normal CSSL function body
1747
+ for child in func_node.children:
1748
+ # Check if exit() was called
1749
+ if not self._running:
1750
+ break
1751
+ self._execute_node(child)
1602
1752
  except CSSLReturn as ret:
1603
1753
  return_value = ret.value
1604
1754
 
@@ -1981,6 +2131,71 @@ class CSSLRuntime:
1981
2131
 
1982
2132
  return None
1983
2133
 
2134
+ def _exec_supports_block(self, node: ASTNode) -> Any:
2135
+ """Execute standalone supports block for multi-language syntax.
2136
+
2137
+ v4.2.0: Allows 'supports' to be used anywhere, not just in class/function.
2138
+
2139
+ Syntax:
2140
+ supports py {
2141
+ for i in range(10):
2142
+ print(i)
2143
+ }
2144
+
2145
+ supports cpp {
2146
+ int x = 42;
2147
+ std::cout << x << std::endl;
2148
+ }
2149
+ """
2150
+ block_info = node.value
2151
+ language = block_info.get('language')
2152
+ raw_source = block_info.get('raw_source')
2153
+
2154
+ # If we have raw_source, transform and execute
2155
+ if raw_source and language:
2156
+ import textwrap
2157
+ from .cssl_languages import get_language
2158
+ from .cssl_parser import parse_cssl_program
2159
+
2160
+ # Normalize language ID
2161
+ lang_id = language.lstrip('@').lower()
2162
+
2163
+ # Get language support and transformer
2164
+ lang_support = get_language(lang_id)
2165
+ if lang_support is None:
2166
+ raise CSSLRuntimeError(f"Unknown language '{lang_id}' in 'supports' block")
2167
+
2168
+ # Dedent the raw source to normalize indentation
2169
+ dedented_source = textwrap.dedent(raw_source)
2170
+
2171
+ # Transform the raw source to CSSL
2172
+ transformer = lang_support.get_transformer()
2173
+ transformed_source = transformer.transform_source(dedented_source)
2174
+
2175
+ # Parse the transformed CSSL
2176
+ try:
2177
+ ast = parse_cssl_program(transformed_source)
2178
+ except Exception as e:
2179
+ raise CSSLRuntimeError(
2180
+ f"Failed to parse transformed '{lang_id}' code in supports block: {e}\n"
2181
+ f"Dedented:\n{dedented_source}\n"
2182
+ f"Transformed:\n{transformed_source}"
2183
+ )
2184
+
2185
+ # Execute the transformed AST
2186
+ result = None
2187
+ for child in ast.children:
2188
+ result = self._execute_node(child)
2189
+
2190
+ return result
2191
+
2192
+ # Fallback: execute already-parsed children (CSSL syntax)
2193
+ result = None
2194
+ for child in node.children:
2195
+ result = self._execute_node(child)
2196
+
2197
+ return result
2198
+
1984
2199
  def _exec_createcmd_inject(self, node: ASTNode) -> Any:
1985
2200
  """Execute createcmd injection: createcmd('cmd') <== { action }"""
1986
2201
  command_call = node.value.get('command_call')
@@ -2549,24 +2764,60 @@ class CSSLRuntime:
2549
2764
  if filter_info:
2550
2765
  source = self._apply_injection_filter(source, filter_info)
2551
2766
 
2552
- # Get current target value for add/move modes
2767
+ # Get current target value for add/move/replace modes (needed for UniversalInstance handling)
2553
2768
  current_value = None
2554
- if mode in ('add', 'move'):
2555
- try:
2556
- current_value = self._evaluate(target)
2557
- except Exception:
2558
- # Target might not exist yet, that's okay for add mode
2559
- current_value = None
2769
+ try:
2770
+ current_value = self._evaluate(target)
2771
+ except Exception:
2772
+ # Target might not exist yet, that's okay for add mode
2773
+ current_value = None
2560
2774
 
2561
2775
  # Determine final value based on mode
2562
2776
  if mode == 'replace':
2563
- final_value = source
2777
+ from .cssl_types import CSSLInstance, UniversalInstance, CSSLClass
2778
+ # Special handling for UniversalInstance targets - inject instead of replace
2779
+ if isinstance(current_value, UniversalInstance):
2780
+ if isinstance(source, CSSLClass):
2781
+ current_value.set_member(source.name, source)
2782
+ final_value = current_value
2783
+ elif isinstance(source, ASTNode) and source.type == 'function':
2784
+ func_info = source.value
2785
+ func_name = func_info.get('name') if isinstance(func_info, dict) else None
2786
+ if func_name:
2787
+ current_value.set_method(func_name, source, self)
2788
+ final_value = current_value
2789
+ elif isinstance(source, CSSLInstance):
2790
+ current_value.set_member(source._class.name, source)
2791
+ final_value = current_value
2792
+ else:
2793
+ # For other types, store as member with source type name
2794
+ final_value = source
2795
+ else:
2796
+ final_value = source
2564
2797
  elif mode == 'add':
2565
2798
  # Copy & add - preserve target and add source
2566
- from .cssl_types import CSSLInstance
2799
+ from .cssl_types import CSSLInstance, UniversalInstance, CSSLClass
2567
2800
 
2801
+ # Special handling for UniversalInstance + CSSLClass
2802
+ if isinstance(current_value, UniversalInstance) and isinstance(source, CSSLClass):
2803
+ # Inject class definition into universal instance
2804
+ current_value.set_member(source.name, source)
2805
+ final_value = current_value
2806
+ # Special handling for UniversalInstance + Function (AST node)
2807
+ elif isinstance(current_value, UniversalInstance) and isinstance(source, ASTNode) and source.type == 'function':
2808
+ # Inject function as a method into universal instance
2809
+ func_info = source.value
2810
+ func_name = func_info.get('name') if isinstance(func_info, dict) else None
2811
+ if func_name:
2812
+ current_value.set_method(func_name, source, self)
2813
+ final_value = current_value
2814
+ # Special handling for UniversalInstance + CSSLInstance
2815
+ elif isinstance(current_value, UniversalInstance) and isinstance(source, CSSLInstance):
2816
+ class_name = source._class.name
2817
+ current_value.set_member(class_name, source)
2818
+ final_value = current_value
2568
2819
  # Special handling for CSSLInstance - merge classes
2569
- if isinstance(current_value, CSSLInstance) and isinstance(source, CSSLInstance):
2820
+ elif isinstance(current_value, CSSLInstance) and isinstance(source, CSSLInstance):
2570
2821
  # Add the new class instance as a member with class name as key
2571
2822
  class_name = source._class.name
2572
2823
  current_value._members[class_name] = source
@@ -2873,7 +3124,7 @@ class CSSLRuntime:
2873
3124
  func_info = child.value
2874
3125
  func_name = func_info.get('name') if isinstance(func_info, dict) else None
2875
3126
  if func_name:
2876
- instance.set_method(func_name, child)
3127
+ instance.set_method(func_name, child, self)
2877
3128
  elif child.type == 'var_declaration':
2878
3129
  # Extract variable and value
2879
3130
  var_info = child.value
@@ -2981,7 +3232,13 @@ class CSSLRuntime:
2981
3232
  if self._current_instance is None:
2982
3233
  raise CSSLRuntimeError("'this' used outside of class method context")
2983
3234
  member = target.value.get('member')
2984
- self._current_instance.set_member(member, value)
3235
+ instance = self._current_instance
3236
+ # Check if instance is a CSSL instance or a plain Python object
3237
+ if hasattr(instance, 'set_member'):
3238
+ instance.set_member(member, value)
3239
+ else:
3240
+ # Plain Python object - use setattr
3241
+ setattr(instance, member, value)
2985
3242
  elif isinstance(target, str):
2986
3243
  self.scope.set(target, value)
2987
3244
 
@@ -3214,6 +3471,83 @@ class CSSLRuntime:
3214
3471
  # Return None if instance doesn't exist (can be created via ==>)
3215
3472
  return None
3216
3473
 
3474
+ # v4.1.0/v4.1.1: Cross-language instance reference: cpp$ClassName, py$Object
3475
+ # Enhanced bidirectional access with real runtime bridges
3476
+ if node.type == 'lang_instance_ref':
3477
+ ref = node.value # {'lang': 'cpp', 'instance': 'ClassName'}
3478
+ lang_id = ref['lang']
3479
+ instance_name = ref['instance']
3480
+
3481
+ # First, try to get the language support object from scope
3482
+ lang_support = self.scope.get(lang_id)
3483
+ if lang_support is None:
3484
+ lang_support = self.global_scope.get(lang_id)
3485
+
3486
+ # If not found in scope, try to get from modules
3487
+ if lang_support is None:
3488
+ lang_support = self._modules.get(lang_id)
3489
+
3490
+ # If still not found, try getting default language support
3491
+ if lang_support is None:
3492
+ from .cssl_languages import get_language
3493
+ lang_support = get_language(lang_id)
3494
+
3495
+ if lang_support is not None:
3496
+ # v4.1.1: Check if it's a LanguageSupport object with get_instance method
3497
+ if hasattr(lang_support, 'get_instance'):
3498
+ instance = lang_support.get_instance(instance_name)
3499
+ if instance is not None:
3500
+ return instance
3501
+
3502
+ # v4.1.1: For C++, also try to get class from loaded modules directly
3503
+ if hasattr(lang_support, '_get_bridge'):
3504
+ bridge = lang_support._get_bridge()
3505
+ if bridge is not None:
3506
+ # Try to get from bridge's instances
3507
+ bridge_instance = bridge.get_instance(instance_name)
3508
+ if bridge_instance is not None:
3509
+ return bridge_instance
3510
+
3511
+ # For C++: Try to access class from IncludeCPP modules
3512
+ if hasattr(bridge, '_modules'):
3513
+ for mod in bridge._modules.values():
3514
+ if hasattr(mod, instance_name):
3515
+ cls_or_instance = getattr(mod, instance_name)
3516
+ # Cache it for future access
3517
+ lang_support._instances[instance_name] = cls_or_instance
3518
+ return cls_or_instance
3519
+
3520
+ # Check _instances dict directly as fallback
3521
+ if hasattr(lang_support, '_instances'):
3522
+ if instance_name in lang_support._instances:
3523
+ return lang_support._instances[instance_name]
3524
+
3525
+ # Build helpful error message based on language
3526
+ if lang_id in ('cpp', 'c++'):
3527
+ hint = (f"For C++ access:\n"
3528
+ f" 1. Build your module with 'includecpp build'\n"
3529
+ f" 2. Use cpp.share(\"{instance_name}\", instance) to register\n"
3530
+ f" 3. Or access a class directly: obj = new cpp${instance_name}()")
3531
+ elif lang_id in ('java',):
3532
+ hint = (f"For Java access:\n"
3533
+ f" 1. Install JPype: pip install jpype1\n"
3534
+ f" 2. Add classpath: java.add_classpath(\"path/to/jar\")\n"
3535
+ f" 3. Load class: MyClass = java.load_class(\"com.example.{instance_name}\")\n"
3536
+ f" 4. Share instance: java.share(\"{instance_name}\", instance)")
3537
+ elif lang_id in ('js', 'javascript'):
3538
+ hint = (f"For JavaScript access:\n"
3539
+ f" 1. Make sure Node.js is installed\n"
3540
+ f" 2. Define in JS: js.eval(\"function {instance_name}() {{...}}\")\n"
3541
+ f" 3. Share result: js.share(\"{instance_name}\", result)")
3542
+ else:
3543
+ hint = f"Use '{lang_id}.share(\"{instance_name}\", instance)' to register the instance first."
3544
+
3545
+ raise self._format_error(
3546
+ node.line if hasattr(node, 'line') else 0,
3547
+ f"Cross-language instance '{lang_id}${instance_name}' not found",
3548
+ hint
3549
+ )
3550
+
3217
3551
  if node.type == 'new':
3218
3552
  # Create new instance of a class: new ClassName(args)
3219
3553
  return self._eval_new(node)
@@ -3287,6 +3621,14 @@ class CSSLRuntime:
3287
3621
  if node.type == 'unary':
3288
3622
  return self._eval_unary(node)
3289
3623
 
3624
+ # Increment: ++i or i++
3625
+ if node.type == 'increment':
3626
+ return self._eval_increment(node)
3627
+
3628
+ # Decrement: --i or i--
3629
+ if node.type == 'decrement':
3630
+ return self._eval_decrement(node)
3631
+
3290
3632
  if node.type == 'non_null_assert':
3291
3633
  # *$var, *@module, *identifier - assert value is not null/None
3292
3634
  operand = node.value.get('operand')
@@ -3572,6 +3914,74 @@ class CSSLRuntime:
3572
3914
 
3573
3915
  return None
3574
3916
 
3917
+ def _eval_increment(self, node: ASTNode) -> Any:
3918
+ """Evaluate increment operation (++i or i++)"""
3919
+ op_type = node.value.get('op') # 'prefix' or 'postfix'
3920
+ operand = node.value.get('operand')
3921
+
3922
+ # Get variable name
3923
+ var_name = None
3924
+ if isinstance(operand, ASTNode):
3925
+ if operand.type == 'identifier':
3926
+ var_name = operand.value
3927
+ elif operand.type == 'shared_ref':
3928
+ # Handle $var++
3929
+ var_name = operand.value
3930
+ current = self.shared_vars.get(var_name, 0)
3931
+ if op_type == 'prefix':
3932
+ self.shared_vars[var_name] = current + 1
3933
+ return current + 1
3934
+ else: # postfix
3935
+ self.shared_vars[var_name] = current + 1
3936
+ return current
3937
+
3938
+ if var_name:
3939
+ current = self.scope.get(var_name, 0)
3940
+ if op_type == 'prefix':
3941
+ # ++i: increment then return
3942
+ self.scope.set(var_name, current + 1)
3943
+ return current + 1
3944
+ else:
3945
+ # i++: return then increment
3946
+ self.scope.set(var_name, current + 1)
3947
+ return current
3948
+
3949
+ return None
3950
+
3951
+ def _eval_decrement(self, node: ASTNode) -> Any:
3952
+ """Evaluate decrement operation (--i or i--)"""
3953
+ op_type = node.value.get('op') # 'prefix' or 'postfix'
3954
+ operand = node.value.get('operand')
3955
+
3956
+ # Get variable name
3957
+ var_name = None
3958
+ if isinstance(operand, ASTNode):
3959
+ if operand.type == 'identifier':
3960
+ var_name = operand.value
3961
+ elif operand.type == 'shared_ref':
3962
+ # Handle $var--
3963
+ var_name = operand.value
3964
+ current = self.shared_vars.get(var_name, 0)
3965
+ if op_type == 'prefix':
3966
+ self.shared_vars[var_name] = current - 1
3967
+ return current - 1
3968
+ else: # postfix
3969
+ self.shared_vars[var_name] = current - 1
3970
+ return current
3971
+
3972
+ if var_name:
3973
+ current = self.scope.get(var_name, 0)
3974
+ if op_type == 'prefix':
3975
+ # --i: decrement then return
3976
+ self.scope.set(var_name, current - 1)
3977
+ return current - 1
3978
+ else:
3979
+ # i--: return then decrement
3980
+ self.scope.set(var_name, current - 1)
3981
+ return current
3982
+
3983
+ return None
3984
+
3575
3985
  def _eval_call(self, node: ASTNode) -> Any:
3576
3986
  """Evaluate function call with optional named arguments"""
3577
3987
  callee_node = node.value.get('callee')
@@ -3783,6 +4193,22 @@ class CSSLRuntime:
3783
4193
  hint
3784
4194
  )
3785
4195
 
4196
+ # If we got a variable that holds a class reference, use that class
4197
+ if not isinstance(class_def, CSSLClass):
4198
+ # Check if it's a variable holding a class (dynamic class instantiation)
4199
+ if hasattr(class_def, '__class__') and isinstance(class_def, CSSLClass):
4200
+ pass # Already a CSSLClass, continue
4201
+ elif isinstance(class_def, dict) and 'class_def' in class_def:
4202
+ # Injected class reference from +<== operator
4203
+ class_def = class_def['class_def']
4204
+ else:
4205
+ # Not a class - show error
4206
+ raise CSSLRuntimeError(
4207
+ f"'{class_name}' is not a class",
4208
+ node.line,
4209
+ hint=f"'{class_name}' is of type {type(class_def).__name__}"
4210
+ )
4211
+
3786
4212
  if not isinstance(class_def, CSSLClass):
3787
4213
  raise CSSLRuntimeError(
3788
4214
  f"'{class_name}' is not a class",
@@ -4080,24 +4506,40 @@ class CSSLRuntime:
4080
4506
  # Direct this->member access
4081
4507
  instance = self._current_instance
4082
4508
 
4083
- # Check if it's a member variable
4084
- if instance.has_member(member):
4085
- return instance.get_member(member)
4509
+ # Check if instance is a CSSL instance or a plain Python object
4510
+ is_cssl_instance = hasattr(instance, 'has_member') and hasattr(instance, 'has_method')
4086
4511
 
4087
- # Check if it's a method
4088
- if instance.has_method(member):
4089
- # Return a callable that will invoke the method with instance context
4090
- method_node = instance.get_method(member)
4091
- # Check if this is an inherited Python method
4092
- if isinstance(method_node, tuple) and method_node[0] == 'python_method':
4093
- python_method = method_node[1]
4094
- return lambda *args, **kwargs: python_method(*args, **kwargs)
4095
- return lambda *args, **kwargs: self._call_method(instance, method_node, list(args), kwargs)
4512
+ if is_cssl_instance:
4513
+ # CSSL instance - use CSSL methods
4514
+ if instance.has_member(member):
4515
+ return instance.get_member(member)
4516
+
4517
+ # Check if it's a method
4518
+ if instance.has_method(member):
4519
+ # Return a callable that will invoke the method with instance context
4520
+ method_node = instance.get_method(member)
4521
+ # Check if this is an inherited Python method
4522
+ if isinstance(method_node, tuple) and method_node[0] == 'python_method':
4523
+ python_method = method_node[1]
4524
+ return lambda *args, **kwargs: python_method(*args, **kwargs)
4525
+ return lambda *args, **kwargs: self._call_method(instance, method_node, list(args), kwargs)
4526
+ else:
4527
+ # Plain Python object - use standard attribute access
4528
+ if hasattr(instance, member):
4529
+ return getattr(instance, member)
4530
+ # Also check __dict__ for dynamic attributes
4531
+ if hasattr(instance, '__dict__') and member in instance.__dict__:
4532
+ return instance.__dict__[member]
4096
4533
 
4097
4534
  # Build helpful error with available members
4098
- class_name = instance._class.name
4099
- available_members = list(instance._members.keys()) if hasattr(instance, '_members') else []
4100
- available_methods = list(instance._methods.keys()) if hasattr(instance, '_methods') else []
4535
+ if is_cssl_instance:
4536
+ class_name = instance._class.name
4537
+ available_members = list(instance._members.keys()) if hasattr(instance, '_members') else []
4538
+ available_methods = list(instance._methods.keys()) if hasattr(instance, '_methods') else []
4539
+ else:
4540
+ class_name = type(instance).__name__
4541
+ available_members = [k for k in dir(instance) if not k.startswith('_')]
4542
+ available_methods = []
4101
4543
  all_available = available_members + available_methods
4102
4544
  similar = _find_similar_names(member, all_available)
4103
4545