IncludeCPP 4.2.2__py3-none-any.whl → 4.3.0__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.
@@ -350,22 +350,35 @@ class CSSLBuiltins:
350
350
 
351
351
  def builtin_print(self, *args, **kwargs) -> None:
352
352
  """Print without newline"""
353
+ import sys
353
354
  sep = kwargs.get('sep', ' ')
354
355
  end = kwargs.get('end', '')
355
356
  output = sep.join(str(a) for a in args) + end
356
357
  if self.runtime and hasattr(self.runtime, 'output'):
357
358
  self.runtime.output(output)
358
359
  else:
359
- print(output, end='')
360
+ # Handle encoding issues on Windows console
361
+ try:
362
+ print(output, end='')
363
+ except UnicodeEncodeError:
364
+ encoded = output.encode(sys.stdout.encoding or 'utf-8', errors='replace')
365
+ print(encoded.decode(sys.stdout.encoding or 'utf-8', errors='replace'), end='')
360
366
 
361
367
  def builtin_println(self, *args, **kwargs) -> None:
362
368
  """Print with newline"""
369
+ import sys
363
370
  sep = kwargs.get('sep', ' ')
364
371
  output = sep.join(str(a) for a in args)
365
372
  if self.runtime and hasattr(self.runtime, 'output'):
366
373
  self.runtime.output(output + '\n')
367
374
  else:
368
- print(output)
375
+ # Handle encoding issues on Windows console
376
+ try:
377
+ print(output)
378
+ except UnicodeEncodeError:
379
+ # Fallback: encode with errors='replace' for unsupported chars
380
+ encoded = output.encode(sys.stdout.encoding or 'utf-8', errors='replace')
381
+ print(encoded.decode(sys.stdout.encoding or 'utf-8', errors='replace'))
369
382
 
370
383
  def builtin_input(self, prompt: str = "") -> str:
371
384
  """Read user input from console.
@@ -2111,9 +2124,9 @@ class CSSLBuiltins:
2111
2124
 
2112
2125
  return CppModuleStub(name, source)
2113
2126
 
2114
- def builtin_payload(self, filepath: str) -> None:
2127
+ def builtin_payload(self, filepath: str, libname: str = None) -> None:
2115
2128
  """
2116
- Load a CSSL payload file (.cssl-pl) and execute it in the current scope.
2129
+ Load a CSSL payload file (.cssl-pl) and execute it.
2117
2130
 
2118
2131
  Payloads are like header files but for CSSL:
2119
2132
  - Define global variables (accessible via @name)
@@ -2122,8 +2135,19 @@ class CSSLBuiltins:
2122
2135
  - Set configuration values
2123
2136
 
2124
2137
  Usage in .cssl file:
2138
+ // Standard: everything is applied globally
2125
2139
  payload("myconfig.cssl-pl");
2126
- // Now all globals, functions, and injections are active
2140
+
2141
+ // Namespaced: everything goes into a namespace
2142
+ payload("myconfig.cssl-pl", "mylib");
2143
+ mylib::myFunction(); // Access via namespace
2144
+
2145
+ Namespace behavior:
2146
+ - Without libname: All changes are global (embedded replaces globally)
2147
+ - With libname: All definitions go into namespace
2148
+ - embedded new_exit &exit {} -> mylib::exit is new_exit
2149
+ - Global exit remains unchanged
2150
+ - Access via libname::function(), libname::MyClass
2127
2151
 
2128
2152
  Usage in Python:
2129
2153
  cssl = CSSL.CsslLang()
@@ -2155,6 +2179,10 @@ class CSSLBuiltins:
2155
2179
  if hasattr(self.runtime, '_inline_payloads') and filepath in self.runtime._inline_payloads:
2156
2180
  source = self.runtime._inline_payloads[filepath]
2157
2181
  else:
2182
+ # v4.2.6: Auto-append .cssl-pl extension if not specified
2183
+ # payload("engine") -> looks for engine.cssl-pl
2184
+ original_filepath = filepath
2185
+
2158
2186
  # Handle absolute paths
2159
2187
  is_absolute = os.path.isabs(filepath) or (len(filepath) > 2 and filepath[1] == ':')
2160
2188
 
@@ -2164,42 +2192,124 @@ class CSSLBuiltins:
2164
2192
  if os.path.exists(cwd_path):
2165
2193
  filepath = cwd_path
2166
2194
  else:
2167
- # Fall back to cso_root for service context
2168
- filepath = self.builtin_cso_root(filepath)
2169
-
2170
- # Check file exists
2195
+ # Try with .cssl-pl extension
2196
+ cwd_path_pl = os.path.join(os.getcwd(), filepath + '.cssl-pl')
2197
+ if os.path.exists(cwd_path_pl):
2198
+ filepath = cwd_path_pl
2199
+ else:
2200
+ # Fall back to cso_root for service context
2201
+ filepath = self.builtin_cso_root(filepath)
2202
+
2203
+ # Check file exists, try with .cssl-pl extension if not
2171
2204
  if not os.path.exists(filepath):
2172
- raise CSSLBuiltinError(f"Payload file not found: {filepath}")
2205
+ # Try adding .cssl-pl extension
2206
+ filepath_with_ext = filepath + '.cssl-pl'
2207
+ if os.path.exists(filepath_with_ext):
2208
+ filepath = filepath_with_ext
2209
+ else:
2210
+ raise CSSLBuiltinError(f"Payload file not found: {original_filepath} (tried {filepath} and {filepath_with_ext})")
2173
2211
 
2174
2212
  # Check payload cache to prevent double loading
2175
2213
  if not hasattr(self.runtime, '_payload_cache'):
2176
2214
  self.runtime._payload_cache = set()
2177
2215
 
2178
- if filepath in self.runtime._payload_cache:
2216
+ # For namespaced payloads, use different cache key
2217
+ cache_key = f"{filepath}::{libname}" if libname else filepath
2218
+ if cache_key in self.runtime._payload_cache:
2179
2219
  return # Already loaded
2180
2220
 
2181
2221
  # Read the payload file
2182
2222
  with open(filepath, 'r', encoding='utf-8') as f:
2183
2223
  source = f.read()
2184
2224
 
2185
- self.runtime._payload_cache.add(filepath)
2225
+ self.runtime._payload_cache.add(cache_key)
2186
2226
 
2187
- # Parse and execute the payload in current scope
2188
- # This makes all globals, functions, and injections available
2227
+ # Parse and execute the payload
2189
2228
  try:
2190
2229
  from .cssl_parser import parse_cssl_program
2191
2230
 
2192
2231
  ast = parse_cssl_program(source)
2193
2232
 
2194
- # Execute the payload - this will:
2195
- # - Register global variables (accessible via @name)
2196
- # - Define functions in current scope
2197
- # - Set up any code injections
2198
- self.runtime._execute_node(ast)
2233
+ if libname:
2234
+ # Namespaced execution: execute in isolated scope
2235
+ self._execute_payload_namespaced(ast, libname, source)
2236
+ else:
2237
+ # Standard execution: apply globally
2238
+ # Execute the payload - this will:
2239
+ # - Register global variables (accessible via @name)
2240
+ # - Define functions in current scope
2241
+ # - Set up any code injections
2242
+ self.runtime._execute_node(ast)
2199
2243
 
2200
2244
  except Exception as e:
2201
2245
  raise CSSLBuiltinError(f"Failed to load payload '{filepath}': {e}")
2202
2246
 
2247
+ def _execute_payload_namespaced(self, ast, libname: str, source: str) -> None:
2248
+ """
2249
+ Execute payload in a namespace.
2250
+
2251
+ All definitions go into the namespace dict.
2252
+ embedded replacements are scoped to the namespace only.
2253
+ """
2254
+ # Create namespace dict
2255
+ namespace = {}
2256
+
2257
+ # Save current scope state
2258
+ old_scope = self.runtime.scope.copy()
2259
+ old_global = self.runtime.global_scope.copy()
2260
+
2261
+ # Track what gets added during payload execution
2262
+ scope_before = set(self.runtime.scope.keys())
2263
+ global_before = set(self.runtime.global_scope.keys())
2264
+
2265
+ # Set a flag to tell runtime we're in namespace mode
2266
+ self.runtime._payload_namespace_mode = libname
2267
+
2268
+ try:
2269
+ # Execute the payload
2270
+ self.runtime._execute_node(ast)
2271
+
2272
+ # Collect new definitions from scope
2273
+ for key in self.runtime.scope:
2274
+ if key not in scope_before:
2275
+ namespace[key] = self.runtime.scope[key]
2276
+
2277
+ # Collect new definitions from global_scope (but not the namespace itself)
2278
+ for key in self.runtime.global_scope:
2279
+ if key not in global_before and key != libname:
2280
+ namespace[key] = self.runtime.global_scope[key]
2281
+
2282
+ # Handle embedded replacements: move replaced targets into namespace
2283
+ if hasattr(self.runtime, '_namespace_replacements'):
2284
+ for target_name, new_impl in self.runtime._namespace_replacements.items():
2285
+ # Get the original from before execution
2286
+ original = old_scope.get(target_name) or old_global.get(target_name)
2287
+ if original is not None:
2288
+ # Restore original globally
2289
+ if target_name in old_scope:
2290
+ self.runtime.scope[target_name] = original
2291
+ if target_name in old_global:
2292
+ self.runtime.global_scope[target_name] = original
2293
+ # Put the replacement in namespace
2294
+ namespace[target_name] = new_impl
2295
+ del self.runtime._namespace_replacements
2296
+
2297
+ # Remove new definitions from global scope (they're in namespace now)
2298
+ for key in list(self.runtime.scope.keys()):
2299
+ if key not in scope_before:
2300
+ del self.runtime.scope[key]
2301
+
2302
+ for key in list(self.runtime.global_scope.keys()):
2303
+ if key not in global_before and key != libname:
2304
+ del self.runtime.global_scope[key]
2305
+
2306
+ finally:
2307
+ # Clear namespace mode flag
2308
+ self.runtime._payload_namespace_mode = None
2309
+
2310
+ # Register namespace in global scope
2311
+ self.runtime.global_scope[libname] = namespace
2312
+
2203
2313
  def builtin_get(self, source: Any, key: str = None) -> Any:
2204
2314
  """
2205
2315
  Get value from module, ServiceDefinition, or dict