IncludeCPP 3.4.2__py3-none-any.whl → 3.4.8__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.
- includecpp/__init__.py +59 -59
- includecpp/core/cssl/__init__.py +4 -4
- includecpp/core/cssl/cssl_parser.py +304 -6
- includecpp/core/cssl/cssl_runtime.py +50 -12
- includecpp/core/cssl/cssl_types.py +50 -2
- includecpp/core/cssl_bridge.py +278 -1
- includecpp/core/cssl_bridge.pyi +311 -0
- {includecpp-3.4.2.dist-info → includecpp-3.4.8.dist-info}/METADATA +1 -1
- {includecpp-3.4.2.dist-info → includecpp-3.4.8.dist-info}/RECORD +13 -12
- {includecpp-3.4.2.dist-info → includecpp-3.4.8.dist-info}/WHEEL +0 -0
- {includecpp-3.4.2.dist-info → includecpp-3.4.8.dist-info}/entry_points.txt +0 -0
- {includecpp-3.4.2.dist-info → includecpp-3.4.8.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.4.2.dist-info → includecpp-3.4.8.dist-info}/top_level.txt +0 -0
includecpp/__init__.py
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
from .core.cpp_api import CppApi
|
|
2
|
-
from .core import cssl_bridge as CSSL
|
|
3
|
-
import warnings
|
|
4
|
-
|
|
5
|
-
__version__ = "3.4.
|
|
6
|
-
__all__ = ["CppApi", "CSSL"]
|
|
7
|
-
|
|
8
|
-
# Module-level cache for C++ modules
|
|
9
|
-
_api_instance = None
|
|
10
|
-
_loaded_modules = {}
|
|
11
|
-
|
|
12
|
-
def _get_api():
|
|
13
|
-
"""Get or create singleton CppApi instance."""
|
|
14
|
-
global _api_instance
|
|
15
|
-
if _api_instance is None:
|
|
16
|
-
_api_instance = CppApi()
|
|
17
|
-
return _api_instance
|
|
18
|
-
|
|
19
|
-
def __getattr__(name: str):
|
|
20
|
-
"""Enable: from includecpp import fast_list
|
|
21
|
-
|
|
22
|
-
This hook is called when Python cannot find an attribute in this module.
|
|
23
|
-
It allows dynamic C++ module loading via the import system.
|
|
24
|
-
"""
|
|
25
|
-
if name.startswith('_'):
|
|
26
|
-
raise AttributeError(f"module 'includecpp' has no attribute '{name}'")
|
|
27
|
-
|
|
28
|
-
if name in _loaded_modules:
|
|
29
|
-
return _loaded_modules[name]
|
|
30
|
-
|
|
31
|
-
api = _get_api()
|
|
32
|
-
|
|
33
|
-
if name not in api.registry:
|
|
34
|
-
available = list(api.registry.keys())
|
|
35
|
-
raise AttributeError(
|
|
36
|
-
f"Module '{name}' not found. "
|
|
37
|
-
f"Available: {available}. "
|
|
38
|
-
f"Run 'includecpp rebuild' first."
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
if api.need_update(name):
|
|
42
|
-
warnings.warn(
|
|
43
|
-
f"Module '{name}' source files changed. "
|
|
44
|
-
f"Run 'includecpp rebuild' to update.",
|
|
45
|
-
UserWarning
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
module = api.include(name)
|
|
49
|
-
_loaded_modules[name] = module
|
|
50
|
-
return module
|
|
51
|
-
|
|
52
|
-
def __dir__():
|
|
53
|
-
"""List available attributes including C++ modules."""
|
|
54
|
-
base = ['CppApi', 'CSSL', '__version__']
|
|
55
|
-
try:
|
|
56
|
-
api = _get_api()
|
|
57
|
-
return sorted(set(base + list(api.registry.keys())))
|
|
58
|
-
except Exception:
|
|
59
|
-
return base
|
|
1
|
+
from .core.cpp_api import CppApi
|
|
2
|
+
from .core import cssl_bridge as CSSL
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
__version__ = "3.4.8"
|
|
6
|
+
__all__ = ["CppApi", "CSSL"]
|
|
7
|
+
|
|
8
|
+
# Module-level cache for C++ modules
|
|
9
|
+
_api_instance = None
|
|
10
|
+
_loaded_modules = {}
|
|
11
|
+
|
|
12
|
+
def _get_api():
|
|
13
|
+
"""Get or create singleton CppApi instance."""
|
|
14
|
+
global _api_instance
|
|
15
|
+
if _api_instance is None:
|
|
16
|
+
_api_instance = CppApi()
|
|
17
|
+
return _api_instance
|
|
18
|
+
|
|
19
|
+
def __getattr__(name: str):
|
|
20
|
+
"""Enable: from includecpp import fast_list
|
|
21
|
+
|
|
22
|
+
This hook is called when Python cannot find an attribute in this module.
|
|
23
|
+
It allows dynamic C++ module loading via the import system.
|
|
24
|
+
"""
|
|
25
|
+
if name.startswith('_'):
|
|
26
|
+
raise AttributeError(f"module 'includecpp' has no attribute '{name}'")
|
|
27
|
+
|
|
28
|
+
if name in _loaded_modules:
|
|
29
|
+
return _loaded_modules[name]
|
|
30
|
+
|
|
31
|
+
api = _get_api()
|
|
32
|
+
|
|
33
|
+
if name not in api.registry:
|
|
34
|
+
available = list(api.registry.keys())
|
|
35
|
+
raise AttributeError(
|
|
36
|
+
f"Module '{name}' not found. "
|
|
37
|
+
f"Available: {available}. "
|
|
38
|
+
f"Run 'includecpp rebuild' first."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if api.need_update(name):
|
|
42
|
+
warnings.warn(
|
|
43
|
+
f"Module '{name}' source files changed. "
|
|
44
|
+
f"Run 'includecpp rebuild' to update.",
|
|
45
|
+
UserWarning
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
module = api.include(name)
|
|
49
|
+
_loaded_modules[name] = module
|
|
50
|
+
return module
|
|
51
|
+
|
|
52
|
+
def __dir__():
|
|
53
|
+
"""List available attributes including C++ modules."""
|
|
54
|
+
base = ['CppApi', 'CSSL', '__version__']
|
|
55
|
+
try:
|
|
56
|
+
api = _get_api()
|
|
57
|
+
return sorted(set(base + list(api.registry.keys())))
|
|
58
|
+
except Exception:
|
|
59
|
+
return base
|
includecpp/core/cssl/__init__.py
CHANGED
|
@@ -19,9 +19,9 @@ from .cssl_parser import (
|
|
|
19
19
|
from .cssl_runtime import CSSLRuntime, CSSLRuntimeError, CSSLServiceRunner, run_cssl, run_cssl_file
|
|
20
20
|
from .cssl_types import (
|
|
21
21
|
DataStruct, Shuffled, Iterator, Combo, DataSpace, OpenQuote,
|
|
22
|
-
OpenFind,
|
|
22
|
+
OpenFind, Parameter,
|
|
23
23
|
create_datastruct, create_shuffled, create_iterator,
|
|
24
|
-
create_combo, create_dataspace, create_openquote
|
|
24
|
+
create_combo, create_dataspace, create_openquote, create_parameter
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
@@ -34,7 +34,7 @@ __all__ = [
|
|
|
34
34
|
'run_cssl', 'run_cssl_file',
|
|
35
35
|
# Data Types
|
|
36
36
|
'DataStruct', 'Shuffled', 'Iterator', 'Combo', 'DataSpace', 'OpenQuote',
|
|
37
|
-
'OpenFind',
|
|
37
|
+
'OpenFind', 'Parameter',
|
|
38
38
|
'create_datastruct', 'create_shuffled', 'create_iterator',
|
|
39
|
-
'create_combo', 'create_dataspace', 'create_openquote'
|
|
39
|
+
'create_combo', 'create_dataspace', 'create_openquote', 'create_parameter'
|
|
40
40
|
]
|
|
@@ -216,6 +216,9 @@ class CSSLLexer:
|
|
|
216
216
|
self.column = 1
|
|
217
217
|
elif char in '"\'':
|
|
218
218
|
self._read_string(char)
|
|
219
|
+
elif char == '`':
|
|
220
|
+
# Raw string (no escape processing) - useful for JSON
|
|
221
|
+
self._read_raw_string()
|
|
219
222
|
elif char.isdigit() or (char == '-' and self._peek(1).isdigit()):
|
|
220
223
|
self._read_number()
|
|
221
224
|
elif char == 'r' and self._peek(1) == '@':
|
|
@@ -224,7 +227,7 @@ class CSSLLexer:
|
|
|
224
227
|
elif char == 's' and self._peek(1) == '@':
|
|
225
228
|
# s@<name> self-reference to global struct
|
|
226
229
|
self._read_self_ref()
|
|
227
|
-
elif char.isalpha() or char == '_'
|
|
230
|
+
elif char.isalpha() or char == '_':
|
|
228
231
|
self._read_identifier()
|
|
229
232
|
elif char == '@':
|
|
230
233
|
self._add_token(TokenType.AT, '@')
|
|
@@ -346,13 +349,52 @@ class CSSLLexer:
|
|
|
346
349
|
def _read_string(self, quote_char: str):
|
|
347
350
|
self._advance()
|
|
348
351
|
start = self.pos
|
|
352
|
+
result = []
|
|
349
353
|
while self.pos < len(self.source) and self.source[self.pos] != quote_char:
|
|
350
|
-
if self.source[self.pos] == '\\':
|
|
354
|
+
if self.source[self.pos] == '\\' and self.pos + 1 < len(self.source):
|
|
355
|
+
# Handle escape sequences
|
|
356
|
+
next_char = self.source[self.pos + 1]
|
|
357
|
+
if next_char == 'n':
|
|
358
|
+
result.append('\n')
|
|
359
|
+
elif next_char == 't':
|
|
360
|
+
result.append('\t')
|
|
361
|
+
elif next_char == 'r':
|
|
362
|
+
result.append('\r')
|
|
363
|
+
elif next_char == '\\':
|
|
364
|
+
result.append('\\')
|
|
365
|
+
elif next_char == quote_char:
|
|
366
|
+
result.append(quote_char)
|
|
367
|
+
elif next_char == '"':
|
|
368
|
+
result.append('"')
|
|
369
|
+
elif next_char == "'":
|
|
370
|
+
result.append("'")
|
|
371
|
+
else:
|
|
372
|
+
result.append(self.source[self.pos])
|
|
373
|
+
result.append(next_char)
|
|
374
|
+
self._advance()
|
|
375
|
+
self._advance()
|
|
376
|
+
else:
|
|
377
|
+
result.append(self.source[self.pos])
|
|
351
378
|
self._advance()
|
|
379
|
+
value = ''.join(result)
|
|
380
|
+
self._add_token(TokenType.STRING, value)
|
|
381
|
+
self._advance()
|
|
382
|
+
|
|
383
|
+
def _read_raw_string(self):
|
|
384
|
+
"""Read raw string with backticks - no escape processing.
|
|
385
|
+
|
|
386
|
+
Useful for JSON: `{"id": "2819e1", "name": "test"}`
|
|
387
|
+
"""
|
|
388
|
+
self._advance() # Skip opening backtick
|
|
389
|
+
start = self.pos
|
|
390
|
+
while self.pos < len(self.source) and self.source[self.pos] != '`':
|
|
391
|
+
if self.source[self.pos] == '\n':
|
|
392
|
+
self.line += 1
|
|
393
|
+
self.column = 0
|
|
352
394
|
self._advance()
|
|
353
395
|
value = self.source[start:self.pos]
|
|
354
396
|
self._add_token(TokenType.STRING, value)
|
|
355
|
-
self._advance()
|
|
397
|
+
self._advance() # Skip closing backtick
|
|
356
398
|
|
|
357
399
|
def _read_number(self):
|
|
358
400
|
start = self.pos
|
|
@@ -368,7 +410,7 @@ class CSSLLexer:
|
|
|
368
410
|
|
|
369
411
|
def _read_identifier(self):
|
|
370
412
|
start = self.pos
|
|
371
|
-
while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos]
|
|
413
|
+
while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
|
|
372
414
|
self._advance()
|
|
373
415
|
value = self.source[start:self.pos]
|
|
374
416
|
|
|
@@ -588,6 +630,176 @@ class CSSLParser:
|
|
|
588
630
|
self._match(TokenType.BLOCK_END)
|
|
589
631
|
return root
|
|
590
632
|
|
|
633
|
+
def _is_function_modifier(self, value: str) -> bool:
|
|
634
|
+
"""Check if a keyword is a function modifier"""
|
|
635
|
+
return value in ('undefined', 'open', 'meta', 'super', 'closed', 'private', 'virtual', 'sqlbased')
|
|
636
|
+
|
|
637
|
+
def _is_type_keyword(self, value: str) -> bool:
|
|
638
|
+
"""Check if a keyword is a type declaration"""
|
|
639
|
+
return value in ('int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
|
|
640
|
+
'dynamic', 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo', 'structure')
|
|
641
|
+
|
|
642
|
+
def _looks_like_function_declaration(self) -> bool:
|
|
643
|
+
"""Check if current position looks like a C-style function declaration.
|
|
644
|
+
|
|
645
|
+
Patterns:
|
|
646
|
+
- int funcName(...)
|
|
647
|
+
- undefined int funcName(...)
|
|
648
|
+
- vector<string> funcName(...)
|
|
649
|
+
- undefined void funcName(...)
|
|
650
|
+
"""
|
|
651
|
+
saved_pos = self.pos
|
|
652
|
+
|
|
653
|
+
# Skip modifiers (undefined, open, meta, super, closed, private, virtual)
|
|
654
|
+
while self._check(TokenType.KEYWORD) and self._is_function_modifier(self._current().value):
|
|
655
|
+
self._advance()
|
|
656
|
+
|
|
657
|
+
# Check for type keyword
|
|
658
|
+
if self._check(TokenType.KEYWORD) and self._is_type_keyword(self._current().value):
|
|
659
|
+
self._advance()
|
|
660
|
+
|
|
661
|
+
# Skip generic type parameters <T>
|
|
662
|
+
if self._check(TokenType.COMPARE_LT):
|
|
663
|
+
depth = 1
|
|
664
|
+
self._advance()
|
|
665
|
+
while depth > 0 and not self._is_at_end():
|
|
666
|
+
if self._check(TokenType.COMPARE_LT):
|
|
667
|
+
depth += 1
|
|
668
|
+
elif self._check(TokenType.COMPARE_GT):
|
|
669
|
+
depth -= 1
|
|
670
|
+
self._advance()
|
|
671
|
+
|
|
672
|
+
# Check for identifier followed by (
|
|
673
|
+
if self._check(TokenType.IDENTIFIER):
|
|
674
|
+
self._advance()
|
|
675
|
+
is_func = self._check(TokenType.PAREN_START)
|
|
676
|
+
self.pos = saved_pos
|
|
677
|
+
return is_func
|
|
678
|
+
|
|
679
|
+
self.pos = saved_pos
|
|
680
|
+
return False
|
|
681
|
+
|
|
682
|
+
def _parse_typed_function(self) -> ASTNode:
|
|
683
|
+
"""Parse C-style typed function declaration.
|
|
684
|
+
|
|
685
|
+
Patterns:
|
|
686
|
+
- int Add(int a, int b) { }
|
|
687
|
+
- undefined int Func() { }
|
|
688
|
+
- open void Handler(open Params) { }
|
|
689
|
+
- vector<string> GetNames() { }
|
|
690
|
+
"""
|
|
691
|
+
modifiers = []
|
|
692
|
+
return_type = None
|
|
693
|
+
generic_type = None
|
|
694
|
+
|
|
695
|
+
# Collect modifiers (undefined, open, meta, super, closed, private, virtual)
|
|
696
|
+
while self._check(TokenType.KEYWORD) and self._is_function_modifier(self._current().value):
|
|
697
|
+
modifiers.append(self._advance().value)
|
|
698
|
+
|
|
699
|
+
# Get return type
|
|
700
|
+
if self._check(TokenType.KEYWORD) and self._is_type_keyword(self._current().value):
|
|
701
|
+
return_type = self._advance().value
|
|
702
|
+
|
|
703
|
+
# Check for generic type <T>
|
|
704
|
+
if self._check(TokenType.COMPARE_LT):
|
|
705
|
+
self._advance() # skip <
|
|
706
|
+
generic_parts = []
|
|
707
|
+
depth = 1
|
|
708
|
+
while depth > 0 and not self._is_at_end():
|
|
709
|
+
if self._check(TokenType.COMPARE_LT):
|
|
710
|
+
depth += 1
|
|
711
|
+
generic_parts.append('<')
|
|
712
|
+
elif self._check(TokenType.COMPARE_GT):
|
|
713
|
+
depth -= 1
|
|
714
|
+
if depth > 0:
|
|
715
|
+
generic_parts.append('>')
|
|
716
|
+
elif self._check(TokenType.COMMA):
|
|
717
|
+
generic_parts.append(',')
|
|
718
|
+
else:
|
|
719
|
+
generic_parts.append(self._current().value)
|
|
720
|
+
self._advance()
|
|
721
|
+
generic_type = ''.join(generic_parts)
|
|
722
|
+
|
|
723
|
+
# Get function name
|
|
724
|
+
name = self._advance().value
|
|
725
|
+
|
|
726
|
+
# Parse parameters
|
|
727
|
+
params = []
|
|
728
|
+
self._expect(TokenType.PAREN_START)
|
|
729
|
+
|
|
730
|
+
while not self._check(TokenType.PAREN_END) and not self._is_at_end():
|
|
731
|
+
param_info = {}
|
|
732
|
+
|
|
733
|
+
# Handle 'open' keyword for open parameters
|
|
734
|
+
if self._match_keyword('open'):
|
|
735
|
+
param_info['open'] = True
|
|
736
|
+
|
|
737
|
+
# Handle type annotations
|
|
738
|
+
if self._check(TokenType.KEYWORD) and self._is_type_keyword(self._current().value):
|
|
739
|
+
param_info['type'] = self._advance().value
|
|
740
|
+
|
|
741
|
+
# Check for generic type parameter <T>
|
|
742
|
+
if self._check(TokenType.COMPARE_LT):
|
|
743
|
+
self._advance()
|
|
744
|
+
generic_parts = []
|
|
745
|
+
depth = 1
|
|
746
|
+
while depth > 0 and not self._is_at_end():
|
|
747
|
+
if self._check(TokenType.COMPARE_LT):
|
|
748
|
+
depth += 1
|
|
749
|
+
generic_parts.append('<')
|
|
750
|
+
elif self._check(TokenType.COMPARE_GT):
|
|
751
|
+
depth -= 1
|
|
752
|
+
if depth > 0:
|
|
753
|
+
generic_parts.append('>')
|
|
754
|
+
elif self._check(TokenType.COMMA):
|
|
755
|
+
generic_parts.append(',')
|
|
756
|
+
else:
|
|
757
|
+
generic_parts.append(self._current().value)
|
|
758
|
+
self._advance()
|
|
759
|
+
param_info['generic'] = ''.join(generic_parts)
|
|
760
|
+
|
|
761
|
+
# Handle reference operator &
|
|
762
|
+
if self._match(TokenType.AMPERSAND):
|
|
763
|
+
param_info['ref'] = True
|
|
764
|
+
|
|
765
|
+
# Get parameter name
|
|
766
|
+
if self._check(TokenType.IDENTIFIER):
|
|
767
|
+
param_name = self._advance().value
|
|
768
|
+
if param_info:
|
|
769
|
+
params.append({'name': param_name, **param_info})
|
|
770
|
+
else:
|
|
771
|
+
params.append(param_name)
|
|
772
|
+
elif self._check(TokenType.KEYWORD):
|
|
773
|
+
# Parameter name could be a keyword
|
|
774
|
+
param_name = self._advance().value
|
|
775
|
+
if param_info:
|
|
776
|
+
params.append({'name': param_name, **param_info})
|
|
777
|
+
else:
|
|
778
|
+
params.append(param_name)
|
|
779
|
+
|
|
780
|
+
self._match(TokenType.COMMA)
|
|
781
|
+
|
|
782
|
+
self._expect(TokenType.PAREN_END)
|
|
783
|
+
|
|
784
|
+
# Parse function body
|
|
785
|
+
node = ASTNode('function', value={
|
|
786
|
+
'name': name,
|
|
787
|
+
'params': params,
|
|
788
|
+
'return_type': return_type,
|
|
789
|
+
'generic_type': generic_type,
|
|
790
|
+
'modifiers': modifiers
|
|
791
|
+
}, children=[])
|
|
792
|
+
|
|
793
|
+
self._expect(TokenType.BLOCK_START)
|
|
794
|
+
|
|
795
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
796
|
+
stmt = self._parse_statement()
|
|
797
|
+
if stmt:
|
|
798
|
+
node.children.append(stmt)
|
|
799
|
+
|
|
800
|
+
self._expect(TokenType.BLOCK_END)
|
|
801
|
+
return node
|
|
802
|
+
|
|
591
803
|
def parse_program(self) -> ASTNode:
|
|
592
804
|
"""Parse a standalone program (no service wrapper)"""
|
|
593
805
|
root = ASTNode('program', children=[])
|
|
@@ -597,6 +809,30 @@ class CSSLParser:
|
|
|
597
809
|
root.children.append(self._parse_struct())
|
|
598
810
|
elif self._match_keyword('define'):
|
|
599
811
|
root.children.append(self._parse_define())
|
|
812
|
+
# Check for C-style typed function declarations
|
|
813
|
+
elif self._looks_like_function_declaration():
|
|
814
|
+
root.children.append(self._parse_typed_function())
|
|
815
|
+
# Handle service blocks
|
|
816
|
+
elif self._match_keyword('service-init'):
|
|
817
|
+
root.children.append(self._parse_service_init())
|
|
818
|
+
elif self._match_keyword('service-include'):
|
|
819
|
+
root.children.append(self._parse_service_include())
|
|
820
|
+
elif self._match_keyword('service-run'):
|
|
821
|
+
root.children.append(self._parse_service_run())
|
|
822
|
+
elif self._match_keyword('package'):
|
|
823
|
+
root.children.append(self._parse_package())
|
|
824
|
+
elif self._match_keyword('package-includes'):
|
|
825
|
+
root.children.append(self._parse_package_includes())
|
|
826
|
+
# Handle global declarations
|
|
827
|
+
elif self._match_keyword('global'):
|
|
828
|
+
stmt = self._parse_expression_statement()
|
|
829
|
+
if stmt:
|
|
830
|
+
root.children.append(stmt)
|
|
831
|
+
elif self._check(TokenType.GLOBAL_REF):
|
|
832
|
+
stmt = self._parse_expression_statement()
|
|
833
|
+
if stmt:
|
|
834
|
+
root.children.append(stmt)
|
|
835
|
+
# Handle statements
|
|
600
836
|
elif self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or self._check(TokenType.SELF_REF):
|
|
601
837
|
stmt = self._parse_expression_statement()
|
|
602
838
|
if stmt:
|
|
@@ -609,6 +845,9 @@ class CSSLParser:
|
|
|
609
845
|
root.children.append(self._parse_for())
|
|
610
846
|
elif self._match_keyword('foreach'):
|
|
611
847
|
root.children.append(self._parse_foreach())
|
|
848
|
+
# Skip comments and newlines
|
|
849
|
+
elif self._check(TokenType.COMMENT) or self._check(TokenType.NEWLINE):
|
|
850
|
+
self._advance()
|
|
612
851
|
else:
|
|
613
852
|
self._advance()
|
|
614
853
|
|
|
@@ -837,8 +1076,31 @@ class CSSLParser:
|
|
|
837
1076
|
|
|
838
1077
|
if self._match(TokenType.PAREN_START):
|
|
839
1078
|
while not self._check(TokenType.PAREN_END):
|
|
1079
|
+
param_info = {}
|
|
1080
|
+
# Handle 'open' keyword for open parameters
|
|
1081
|
+
if self._match_keyword('open'):
|
|
1082
|
+
param_info['open'] = True
|
|
1083
|
+
# Handle type annotations (e.g., string, int, dynamic, etc.)
|
|
1084
|
+
if self._check(TokenType.KEYWORD):
|
|
1085
|
+
param_info['type'] = self._advance().value
|
|
1086
|
+
# Handle reference operator &
|
|
1087
|
+
if self._match(TokenType.AMPERSAND):
|
|
1088
|
+
param_info['ref'] = True
|
|
1089
|
+
# Get parameter name
|
|
840
1090
|
if self._check(TokenType.IDENTIFIER):
|
|
841
|
-
|
|
1091
|
+
param_name = self._advance().value
|
|
1092
|
+
if param_info:
|
|
1093
|
+
params.append({'name': param_name, **param_info})
|
|
1094
|
+
else:
|
|
1095
|
+
params.append(param_name)
|
|
1096
|
+
self._match(TokenType.COMMA)
|
|
1097
|
+
elif self._check(TokenType.KEYWORD):
|
|
1098
|
+
# Parameter name could be a keyword like 'Params'
|
|
1099
|
+
param_name = self._advance().value
|
|
1100
|
+
if param_info:
|
|
1101
|
+
params.append({'name': param_name, **param_info})
|
|
1102
|
+
else:
|
|
1103
|
+
params.append(param_name)
|
|
842
1104
|
self._match(TokenType.COMMA)
|
|
843
1105
|
else:
|
|
844
1106
|
break
|
|
@@ -878,6 +1140,12 @@ class CSSLParser:
|
|
|
878
1140
|
return self._parse_try()
|
|
879
1141
|
elif self._match_keyword('await'):
|
|
880
1142
|
return self._parse_await()
|
|
1143
|
+
elif self._match_keyword('define'):
|
|
1144
|
+
# Nested define function
|
|
1145
|
+
return self._parse_define()
|
|
1146
|
+
elif self._looks_like_function_declaration():
|
|
1147
|
+
# Nested typed function (e.g., void Level2() { ... })
|
|
1148
|
+
return self._parse_typed_function()
|
|
881
1149
|
elif self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT):
|
|
882
1150
|
return self._parse_expression_statement()
|
|
883
1151
|
else:
|
|
@@ -1077,6 +1345,9 @@ class CSSLParser:
|
|
|
1077
1345
|
# Check for define statements inside action block
|
|
1078
1346
|
if self._match_keyword('define'):
|
|
1079
1347
|
node.children.append(self._parse_define())
|
|
1348
|
+
# Check for typed function definitions (nested functions)
|
|
1349
|
+
elif self._looks_like_function_declaration():
|
|
1350
|
+
node.children.append(self._parse_typed_function())
|
|
1080
1351
|
else:
|
|
1081
1352
|
stmt = self._parse_statement()
|
|
1082
1353
|
if stmt:
|
|
@@ -1320,12 +1591,39 @@ class CSSLParser:
|
|
|
1320
1591
|
if self._match(TokenType.MINUS):
|
|
1321
1592
|
operand = self._parse_unary()
|
|
1322
1593
|
return ASTNode('unary', value={'op': '-', 'operand': operand})
|
|
1594
|
+
if self._match(TokenType.AMPERSAND):
|
|
1595
|
+
# Reference operator: &variable or &@module
|
|
1596
|
+
operand = self._parse_unary()
|
|
1597
|
+
return ASTNode('reference', value=operand)
|
|
1323
1598
|
|
|
1324
1599
|
return self._parse_primary()
|
|
1325
1600
|
|
|
1326
1601
|
def _parse_primary(self) -> ASTNode:
|
|
1327
1602
|
if self._match(TokenType.AT):
|
|
1328
|
-
|
|
1603
|
+
node = self._parse_module_reference()
|
|
1604
|
+
# Continue to check for calls, indexing, member access on module refs
|
|
1605
|
+
while True:
|
|
1606
|
+
if self._match(TokenType.PAREN_START):
|
|
1607
|
+
# Function call on module ref: @Module.method()
|
|
1608
|
+
args = []
|
|
1609
|
+
while not self._check(TokenType.PAREN_END) and not self._is_at_end():
|
|
1610
|
+
args.append(self._parse_expression())
|
|
1611
|
+
if not self._check(TokenType.PAREN_END):
|
|
1612
|
+
self._expect(TokenType.COMMA)
|
|
1613
|
+
self._expect(TokenType.PAREN_END)
|
|
1614
|
+
node = ASTNode('call', value={'callee': node, 'args': args})
|
|
1615
|
+
elif self._match(TokenType.DOT):
|
|
1616
|
+
# Member access: @Module.property
|
|
1617
|
+
member = self._advance().value
|
|
1618
|
+
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
1619
|
+
elif self._match(TokenType.BRACKET_START):
|
|
1620
|
+
# Index access: @Module[index]
|
|
1621
|
+
index = self._parse_expression()
|
|
1622
|
+
self._expect(TokenType.BRACKET_END)
|
|
1623
|
+
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
1624
|
+
else:
|
|
1625
|
+
break
|
|
1626
|
+
return node
|
|
1329
1627
|
|
|
1330
1628
|
if self._check(TokenType.SELF_REF):
|
|
1331
1629
|
# s@<name> self-reference to global struct
|
|
@@ -12,6 +12,7 @@ from .cssl_parser import ASTNode, parse_cssl, parse_cssl_program, CSSLSyntaxErro
|
|
|
12
12
|
from .cssl_events import CSSLEventManager, EventType, EventData, get_event_manager
|
|
13
13
|
from .cssl_builtins import CSSLBuiltins
|
|
14
14
|
from .cssl_modules import get_module_registry, get_standard_module
|
|
15
|
+
from .cssl_types import Parameter, DataStruct, Shuffled, Iterator, Combo
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class CSSLRuntimeError(Exception):
|
|
@@ -567,16 +568,22 @@ class CSSLRuntime:
|
|
|
567
568
|
"""Call a function node with arguments"""
|
|
568
569
|
func_info = func_node.value
|
|
569
570
|
params = func_info.get('params', [])
|
|
571
|
+
modifiers = func_info.get('modifiers', [])
|
|
572
|
+
|
|
573
|
+
# Check for undefined modifier - suppress errors if present
|
|
574
|
+
is_undefined = 'undefined' in modifiers
|
|
570
575
|
|
|
571
576
|
# Create new scope
|
|
572
577
|
new_scope = Scope(parent=self.scope)
|
|
573
578
|
|
|
574
|
-
# Bind parameters
|
|
579
|
+
# Bind parameters - handle both string and dict formats
|
|
575
580
|
for i, param in enumerate(params):
|
|
581
|
+
# Extract param name from dict format: {'name': 'a', 'type': 'int'}
|
|
582
|
+
param_name = param['name'] if isinstance(param, dict) else param
|
|
576
583
|
if i < len(args):
|
|
577
|
-
new_scope.set(
|
|
584
|
+
new_scope.set(param_name, args[i])
|
|
578
585
|
else:
|
|
579
|
-
new_scope.set(
|
|
586
|
+
new_scope.set(param_name, None)
|
|
580
587
|
|
|
581
588
|
# Execute body
|
|
582
589
|
old_scope = self.scope
|
|
@@ -587,6 +594,11 @@ class CSSLRuntime:
|
|
|
587
594
|
self._execute_node(child)
|
|
588
595
|
except CSSLReturn as ret:
|
|
589
596
|
return ret.value
|
|
597
|
+
except Exception as e:
|
|
598
|
+
# If undefined modifier, suppress all errors
|
|
599
|
+
if is_undefined:
|
|
600
|
+
return None
|
|
601
|
+
raise
|
|
590
602
|
finally:
|
|
591
603
|
self.scope = old_scope
|
|
592
604
|
|
|
@@ -881,7 +893,11 @@ class CSSLRuntime:
|
|
|
881
893
|
# Get current target value for add/move modes
|
|
882
894
|
current_value = None
|
|
883
895
|
if mode in ('add', 'move'):
|
|
884
|
-
|
|
896
|
+
try:
|
|
897
|
+
current_value = self._evaluate(target)
|
|
898
|
+
except Exception:
|
|
899
|
+
# Target might not exist yet, that's okay for add mode
|
|
900
|
+
current_value = None
|
|
885
901
|
|
|
886
902
|
# Determine final value based on mode
|
|
887
903
|
if mode == 'replace':
|
|
@@ -1188,6 +1204,17 @@ class CSSLRuntime:
|
|
|
1188
1204
|
if node.type == 'object':
|
|
1189
1205
|
return {k: self._evaluate(v) for k, v in node.value.items()}
|
|
1190
1206
|
|
|
1207
|
+
if node.type == 'reference':
|
|
1208
|
+
# &variable - return a reference object wrapping the actual value
|
|
1209
|
+
inner = node.value
|
|
1210
|
+
if isinstance(inner, ASTNode):
|
|
1211
|
+
if inner.type == 'identifier':
|
|
1212
|
+
# Return a reference wrapper with the variable name
|
|
1213
|
+
return {'__ref__': True, 'name': inner.value, 'value': self.scope.get(inner.value)}
|
|
1214
|
+
elif inner.type == 'module_ref':
|
|
1215
|
+
return {'__ref__': True, 'name': inner.value, 'value': self.get_module(inner.value)}
|
|
1216
|
+
return {'__ref__': True, 'value': self._evaluate(inner)}
|
|
1217
|
+
|
|
1191
1218
|
return None
|
|
1192
1219
|
|
|
1193
1220
|
def _eval_binary(self, node: ASTNode) -> Any:
|
|
@@ -1388,15 +1415,26 @@ class CSSLRuntime:
|
|
|
1388
1415
|
|
|
1389
1416
|
# NEW: Execute injected code for a function
|
|
1390
1417
|
def _execute_function_injections(self, func_name: str):
|
|
1391
|
-
"""Execute all injected code blocks for a function - NEW
|
|
1418
|
+
"""Execute all injected code blocks for a function - NEW
|
|
1419
|
+
|
|
1420
|
+
Includes protection against recursive execution to prevent doubled output.
|
|
1421
|
+
"""
|
|
1422
|
+
# Prevent recursive injection execution (fixes doubled output bug)
|
|
1423
|
+
if getattr(self, '_injection_executing', False):
|
|
1424
|
+
return
|
|
1425
|
+
|
|
1392
1426
|
if func_name in self._function_injections:
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1427
|
+
self._injection_executing = True
|
|
1428
|
+
try:
|
|
1429
|
+
for code_block in self._function_injections[func_name]:
|
|
1430
|
+
if isinstance(code_block, ASTNode):
|
|
1431
|
+
if code_block.type == 'action_block':
|
|
1432
|
+
for child in code_block.children:
|
|
1433
|
+
self._execute_node(child)
|
|
1434
|
+
else:
|
|
1435
|
+
self._execute_node(code_block)
|
|
1436
|
+
finally:
|
|
1437
|
+
self._injection_executing = False
|
|
1400
1438
|
|
|
1401
1439
|
# Output functions for builtins
|
|
1402
1440
|
def set_output_callback(self, callback: Callable[[str, str], None]):
|