tactus 0.31.2__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.
Files changed (160) hide show
  1. tactus/__init__.py +49 -0
  2. tactus/adapters/__init__.py +9 -0
  3. tactus/adapters/broker_log.py +76 -0
  4. tactus/adapters/cli_hitl.py +189 -0
  5. tactus/adapters/cli_log.py +223 -0
  6. tactus/adapters/cost_collector_log.py +56 -0
  7. tactus/adapters/file_storage.py +367 -0
  8. tactus/adapters/http_callback_log.py +109 -0
  9. tactus/adapters/ide_log.py +71 -0
  10. tactus/adapters/lua_tools.py +336 -0
  11. tactus/adapters/mcp.py +289 -0
  12. tactus/adapters/mcp_manager.py +196 -0
  13. tactus/adapters/memory.py +53 -0
  14. tactus/adapters/plugins.py +419 -0
  15. tactus/backends/http_backend.py +58 -0
  16. tactus/backends/model_backend.py +35 -0
  17. tactus/backends/pytorch_backend.py +110 -0
  18. tactus/broker/__init__.py +12 -0
  19. tactus/broker/client.py +247 -0
  20. tactus/broker/protocol.py +183 -0
  21. tactus/broker/server.py +1123 -0
  22. tactus/broker/stdio.py +12 -0
  23. tactus/cli/__init__.py +7 -0
  24. tactus/cli/app.py +2245 -0
  25. tactus/cli/commands/__init__.py +0 -0
  26. tactus/core/__init__.py +32 -0
  27. tactus/core/config_manager.py +790 -0
  28. tactus/core/dependencies/__init__.py +14 -0
  29. tactus/core/dependencies/registry.py +180 -0
  30. tactus/core/dsl_stubs.py +2117 -0
  31. tactus/core/exceptions.py +66 -0
  32. tactus/core/execution_context.py +480 -0
  33. tactus/core/lua_sandbox.py +508 -0
  34. tactus/core/message_history_manager.py +236 -0
  35. tactus/core/mocking.py +286 -0
  36. tactus/core/output_validator.py +291 -0
  37. tactus/core/registry.py +499 -0
  38. tactus/core/runtime.py +2907 -0
  39. tactus/core/template_resolver.py +142 -0
  40. tactus/core/yaml_parser.py +301 -0
  41. tactus/docker/Dockerfile +61 -0
  42. tactus/docker/entrypoint.sh +69 -0
  43. tactus/dspy/__init__.py +39 -0
  44. tactus/dspy/agent.py +1144 -0
  45. tactus/dspy/broker_lm.py +181 -0
  46. tactus/dspy/config.py +212 -0
  47. tactus/dspy/history.py +196 -0
  48. tactus/dspy/module.py +405 -0
  49. tactus/dspy/prediction.py +318 -0
  50. tactus/dspy/signature.py +185 -0
  51. tactus/formatting/__init__.py +7 -0
  52. tactus/formatting/formatter.py +437 -0
  53. tactus/ide/__init__.py +9 -0
  54. tactus/ide/coding_assistant.py +343 -0
  55. tactus/ide/server.py +2223 -0
  56. tactus/primitives/__init__.py +49 -0
  57. tactus/primitives/control.py +168 -0
  58. tactus/primitives/file.py +229 -0
  59. tactus/primitives/handles.py +378 -0
  60. tactus/primitives/host.py +94 -0
  61. tactus/primitives/human.py +342 -0
  62. tactus/primitives/json.py +189 -0
  63. tactus/primitives/log.py +187 -0
  64. tactus/primitives/message_history.py +157 -0
  65. tactus/primitives/model.py +163 -0
  66. tactus/primitives/procedure.py +564 -0
  67. tactus/primitives/procedure_callable.py +318 -0
  68. tactus/primitives/retry.py +155 -0
  69. tactus/primitives/session.py +152 -0
  70. tactus/primitives/state.py +182 -0
  71. tactus/primitives/step.py +209 -0
  72. tactus/primitives/system.py +93 -0
  73. tactus/primitives/tool.py +375 -0
  74. tactus/primitives/tool_handle.py +279 -0
  75. tactus/primitives/toolset.py +229 -0
  76. tactus/protocols/__init__.py +38 -0
  77. tactus/protocols/chat_recorder.py +81 -0
  78. tactus/protocols/config.py +97 -0
  79. tactus/protocols/cost.py +31 -0
  80. tactus/protocols/hitl.py +71 -0
  81. tactus/protocols/log_handler.py +27 -0
  82. tactus/protocols/models.py +355 -0
  83. tactus/protocols/result.py +33 -0
  84. tactus/protocols/storage.py +90 -0
  85. tactus/providers/__init__.py +13 -0
  86. tactus/providers/base.py +92 -0
  87. tactus/providers/bedrock.py +117 -0
  88. tactus/providers/google.py +105 -0
  89. tactus/providers/openai.py +98 -0
  90. tactus/sandbox/__init__.py +63 -0
  91. tactus/sandbox/config.py +171 -0
  92. tactus/sandbox/container_runner.py +1099 -0
  93. tactus/sandbox/docker_manager.py +433 -0
  94. tactus/sandbox/entrypoint.py +227 -0
  95. tactus/sandbox/protocol.py +213 -0
  96. tactus/stdlib/__init__.py +10 -0
  97. tactus/stdlib/io/__init__.py +13 -0
  98. tactus/stdlib/io/csv.py +88 -0
  99. tactus/stdlib/io/excel.py +136 -0
  100. tactus/stdlib/io/file.py +90 -0
  101. tactus/stdlib/io/fs.py +154 -0
  102. tactus/stdlib/io/hdf5.py +121 -0
  103. tactus/stdlib/io/json.py +109 -0
  104. tactus/stdlib/io/parquet.py +83 -0
  105. tactus/stdlib/io/tsv.py +88 -0
  106. tactus/stdlib/loader.py +274 -0
  107. tactus/stdlib/tac/tactus/tools/done.tac +33 -0
  108. tactus/stdlib/tac/tactus/tools/log.tac +50 -0
  109. tactus/testing/README.md +273 -0
  110. tactus/testing/__init__.py +61 -0
  111. tactus/testing/behave_integration.py +380 -0
  112. tactus/testing/context.py +486 -0
  113. tactus/testing/eval_models.py +114 -0
  114. tactus/testing/evaluation_runner.py +222 -0
  115. tactus/testing/evaluators.py +634 -0
  116. tactus/testing/events.py +94 -0
  117. tactus/testing/gherkin_parser.py +134 -0
  118. tactus/testing/mock_agent.py +315 -0
  119. tactus/testing/mock_dependencies.py +234 -0
  120. tactus/testing/mock_hitl.py +171 -0
  121. tactus/testing/mock_registry.py +168 -0
  122. tactus/testing/mock_tools.py +133 -0
  123. tactus/testing/models.py +115 -0
  124. tactus/testing/pydantic_eval_runner.py +508 -0
  125. tactus/testing/steps/__init__.py +13 -0
  126. tactus/testing/steps/builtin.py +902 -0
  127. tactus/testing/steps/custom.py +69 -0
  128. tactus/testing/steps/registry.py +68 -0
  129. tactus/testing/test_runner.py +489 -0
  130. tactus/tracing/__init__.py +5 -0
  131. tactus/tracing/trace_manager.py +417 -0
  132. tactus/utils/__init__.py +1 -0
  133. tactus/utils/cost_calculator.py +72 -0
  134. tactus/utils/model_pricing.py +132 -0
  135. tactus/utils/safe_file_library.py +502 -0
  136. tactus/utils/safe_libraries.py +234 -0
  137. tactus/validation/LuaLexerBase.py +66 -0
  138. tactus/validation/LuaParserBase.py +23 -0
  139. tactus/validation/README.md +224 -0
  140. tactus/validation/__init__.py +7 -0
  141. tactus/validation/error_listener.py +21 -0
  142. tactus/validation/generated/LuaLexer.interp +231 -0
  143. tactus/validation/generated/LuaLexer.py +5548 -0
  144. tactus/validation/generated/LuaLexer.tokens +124 -0
  145. tactus/validation/generated/LuaLexerBase.py +66 -0
  146. tactus/validation/generated/LuaParser.interp +173 -0
  147. tactus/validation/generated/LuaParser.py +6439 -0
  148. tactus/validation/generated/LuaParser.tokens +124 -0
  149. tactus/validation/generated/LuaParserBase.py +23 -0
  150. tactus/validation/generated/LuaParserVisitor.py +118 -0
  151. tactus/validation/generated/__init__.py +7 -0
  152. tactus/validation/grammar/LuaLexer.g4 +123 -0
  153. tactus/validation/grammar/LuaParser.g4 +178 -0
  154. tactus/validation/semantic_visitor.py +817 -0
  155. tactus/validation/validator.py +157 -0
  156. tactus-0.31.2.dist-info/METADATA +1809 -0
  157. tactus-0.31.2.dist-info/RECORD +160 -0
  158. tactus-0.31.2.dist-info/WHEEL +4 -0
  159. tactus-0.31.2.dist-info/entry_points.txt +2 -0
  160. tactus-0.31.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,234 @@
1
+ """
2
+ Safe Lua standard library implementations with determinism warnings.
3
+
4
+ Provides wrapped versions of math.random, os.time, os.date, os.clock that
5
+ warn when used outside checkpoint boundaries to prevent replay bugs in
6
+ Tactus's checkpoint-and-replay execution system.
7
+ """
8
+
9
+ import warnings
10
+ import time
11
+ import random
12
+ import math
13
+ from datetime import datetime
14
+ from typing import Callable, Any, Optional
15
+ from functools import wraps
16
+
17
+
18
+ class DeterminismWarning(UserWarning):
19
+ """Warning issued when non-deterministic function called outside checkpoint."""
20
+
21
+ pass
22
+
23
+
24
+ class NonDeterministicError(Exception):
25
+ """Raised in strict mode when non-deterministic function called outside checkpoint."""
26
+
27
+ pass
28
+
29
+
30
+ def warn_if_unsafe(operation_name: str, get_context: Callable[[], Optional[Any]]):
31
+ """
32
+ Decorator that warns when non-deterministic operation used outside checkpoint.
33
+
34
+ Args:
35
+ operation_name: Human-readable name of operation (e.g., "math.random()")
36
+ get_context: Callback to retrieve current ExecutionContext
37
+
38
+ Returns:
39
+ Decorated function that checks checkpoint scope
40
+ """
41
+
42
+ def decorator(func):
43
+ @wraps(func)
44
+ def wrapper(*args, **kwargs):
45
+ # Get context via callback
46
+ context = get_context()
47
+
48
+ # If no context, can't enforce (allow silently for REPL/testing)
49
+ if context is None:
50
+ return func(*args, **kwargs)
51
+
52
+ # Check if inside checkpoint
53
+ inside_checkpoint = getattr(context, "_inside_checkpoint", False)
54
+
55
+ if not inside_checkpoint:
56
+ message = (
57
+ f"\n{'=' * 70}\n"
58
+ f"DETERMINISM WARNING: {operation_name} called outside checkpoint\n"
59
+ f"{'=' * 70}\n\n"
60
+ f"Non-deterministic operations must be wrapped in checkpoints "
61
+ f"for durability.\n\n"
62
+ f"To fix, wrap your code in a checkpoint:\n\n"
63
+ f" -- Lua example:\n"
64
+ f" local random_value = Step.checkpoint(function()\n"
65
+ f" return {operation_name}\n"
66
+ f" end)\n\n"
67
+ f"Or use checkpoint() directly:\n\n"
68
+ f" local result = checkpoint(function()\n"
69
+ f" -- Your non-deterministic code here\n"
70
+ f" return {operation_name}\n"
71
+ f" end)\n\n"
72
+ f"Why: Tactus uses checkpointing for durable execution. "
73
+ f"Operations outside\n"
74
+ f"checkpoints may produce different results on replay, "
75
+ f"breaking determinism.\n"
76
+ f"\n{'=' * 70}\n"
77
+ )
78
+
79
+ # Check strict mode
80
+ strict_mode = getattr(context, "strict_determinism", False)
81
+
82
+ if strict_mode:
83
+ raise NonDeterministicError(message)
84
+ else:
85
+ # Use stacklevel=4 to point to Lua code, not wrapper
86
+ warnings.warn(message, DeterminismWarning, stacklevel=4)
87
+
88
+ return func(*args, **kwargs)
89
+
90
+ return wrapper
91
+
92
+ return decorator
93
+
94
+
95
+ def create_safe_math_library(get_context: Callable, strict_mode: bool = False):
96
+ """
97
+ Create safe math library with warnings for random functions.
98
+
99
+ Args:
100
+ get_context: Callback returning current ExecutionContext
101
+ strict_mode: If True, raise errors instead of warnings (not currently used - context has it)
102
+
103
+ Returns:
104
+ Dict with safe math functions (keys are strings for Lua table)
105
+ """
106
+
107
+ @warn_if_unsafe("math.random()", get_context)
108
+ def safe_random(m=None, n=None):
109
+ """
110
+ Safe math.random() with checkpoint warning.
111
+
112
+ Lua's math.random() has three forms:
113
+ - math.random(): returns float in [0, 1)
114
+ - math.random(n): returns integer in [1, n]
115
+ - math.random(m, n): returns integer in [m, n]
116
+ """
117
+ if m is None and n is None:
118
+ # No arguments: return float [0, 1)
119
+ return random.random()
120
+ elif n is None:
121
+ # One argument: return integer [1, m]
122
+ return random.randint(1, int(m))
123
+ else:
124
+ # Two arguments: return integer [m, n]
125
+ return random.randint(int(m), int(n))
126
+
127
+ @warn_if_unsafe("math.randomseed()", get_context)
128
+ def safe_randomseed(seed):
129
+ """Safe math.randomseed() with checkpoint warning."""
130
+ random.seed(int(seed))
131
+ return None
132
+
133
+ # Standard math functions (deterministic - pass through)
134
+ return {
135
+ # Deterministic functions - safe to use anywhere
136
+ "abs": abs,
137
+ "acos": math.acos,
138
+ "asin": math.asin,
139
+ "atan": math.atan,
140
+ "atan2": math.atan2,
141
+ "ceil": math.ceil,
142
+ "cos": math.cos,
143
+ "cosh": math.cosh,
144
+ "deg": math.degrees,
145
+ "exp": math.exp,
146
+ "floor": math.floor,
147
+ "fmod": math.fmod,
148
+ "huge": float("inf"),
149
+ "log": math.log,
150
+ "log10": math.log10,
151
+ "max": max,
152
+ "min": min,
153
+ "modf": math.modf,
154
+ "pi": math.pi,
155
+ "pow": pow,
156
+ "rad": math.radians,
157
+ "sin": math.sin,
158
+ "sinh": math.sinh,
159
+ "sqrt": math.sqrt,
160
+ "tan": math.tan,
161
+ "tanh": math.tanh,
162
+ # Non-deterministic functions (wrapped with warnings)
163
+ "random": safe_random,
164
+ "randomseed": safe_randomseed,
165
+ }
166
+
167
+
168
+ def create_safe_os_library(get_context: Callable, strict_mode: bool = False):
169
+ """
170
+ Create safe os library with warnings for non-deterministic functions.
171
+
172
+ Args:
173
+ get_context: Callback returning current ExecutionContext
174
+ strict_mode: If True, raise errors instead of warnings (not currently used - context has it)
175
+
176
+ Returns:
177
+ Dict with safe os functions
178
+ """
179
+
180
+ @warn_if_unsafe("os.time()", get_context)
181
+ def safe_time(date_table=None):
182
+ """Safe os.time() with checkpoint warning."""
183
+ if date_table is None:
184
+ return int(time.time())
185
+ else:
186
+ # Lua date table format: {year, month, day, hour, min, sec}
187
+ # For simplicity, just return current time
188
+ # Full implementation would parse the table
189
+ return int(time.time())
190
+
191
+ @warn_if_unsafe("os.date()", get_context)
192
+ def safe_date(format_str=None):
193
+ """Safe os.date() with checkpoint warning."""
194
+ now = datetime.utcnow()
195
+
196
+ if format_str is None:
197
+ # Default format like Lua's os.date()
198
+ return now.strftime("%a %b %d %H:%M:%S %Y")
199
+ elif format_str == "%Y-%m-%dT%H:%M:%SZ":
200
+ # ISO 8601 format
201
+ return now.strftime("%Y-%m-%dT%H:%M:%SZ")
202
+ else:
203
+ # Support Python strftime formats
204
+ try:
205
+ return now.strftime(format_str)
206
+ except Exception:
207
+ return now.strftime("%a %b %d %H:%M:%S %Y")
208
+
209
+ @warn_if_unsafe("os.clock()", get_context)
210
+ def safe_clock():
211
+ """Safe os.clock() with checkpoint warning."""
212
+ return time.process_time()
213
+
214
+ @warn_if_unsafe("os.getenv()", get_context)
215
+ def safe_getenv(varname):
216
+ """Safe os.getenv() with checkpoint warning - environment variables can change."""
217
+ import os
218
+
219
+ return os.getenv(varname)
220
+
221
+ @warn_if_unsafe("os.tmpname()", get_context)
222
+ def safe_tmpname():
223
+ """Safe os.tmpname() with checkpoint warning - generates unique temporary filenames."""
224
+ import tempfile
225
+
226
+ return tempfile.mktemp()
227
+
228
+ return {
229
+ "time": safe_time,
230
+ "date": safe_date,
231
+ "clock": safe_clock,
232
+ "getenv": safe_getenv,
233
+ "tmpname": safe_tmpname,
234
+ }
@@ -0,0 +1,66 @@
1
+ from typing import TextIO
2
+ from antlr4 import *
3
+ from antlr4.Token import CommonToken
4
+ import sys
5
+ from typing import TextIO
6
+
7
+
8
+ class LuaLexerBase(Lexer):
9
+ def __init__(self, input: InputStream, output: TextIO = sys.stdout):
10
+ super().__init__(input, output)
11
+ self.start_line = 0
12
+ self.start_col = 0
13
+
14
+ def HandleComment(self):
15
+ self.start_line = self.line
16
+ self.start_col = self.column - 2
17
+ cs = self._input
18
+
19
+ if cs.LA(1) == 91: # '['
20
+ sep = self.skip_sep(cs)
21
+ if sep >= 2:
22
+ self.read_long_string(cs, sep)
23
+ return
24
+
25
+ while cs.LA(1) != 10 and cs.LA(1) != -1: # '\n'
26
+ self._interp.consume(cs)
27
+
28
+ def read_long_string(self, cs: InputStream, sep: int):
29
+ done = False
30
+ self._interp.consume(cs)
31
+
32
+ while not done:
33
+ c = cs.LA(1)
34
+ if c == -1:
35
+ done = True
36
+ elif c == 93: # ']'
37
+ if self.skip_sep(cs) == sep:
38
+ self._interp.consume(cs)
39
+ done = True
40
+ else:
41
+ if cs.LA(1) == -1:
42
+ done = True
43
+ else:
44
+ self._interp.consume(cs)
45
+
46
+ def skip_sep(self, cs: InputStream):
47
+ count = 0
48
+ s = cs.LA(1)
49
+ self._interp.consume(cs)
50
+
51
+ while cs.LA(1) == 61: # '='
52
+ self._interp.consume(cs)
53
+ count += 1
54
+
55
+ if cs.LA(1) == s:
56
+ count += 2
57
+ elif count == 0:
58
+ count = 1
59
+ else:
60
+ count = 0
61
+
62
+ return count
63
+
64
+ def IsLine1Col0(self):
65
+ cs = self._input
66
+ return cs.index == 1
@@ -0,0 +1,23 @@
1
+ import sys
2
+ from antlr4 import *
3
+
4
+ if sys.version_info[1] > 5:
5
+ from typing import TextIO
6
+ else:
7
+ from typing.io import TextIO
8
+
9
+
10
+ class LuaParserBase(Parser):
11
+ debug = False
12
+
13
+ def __init__(self, input: TokenStream, output: TextIO = sys.stdout):
14
+ super().__init__(input, output)
15
+
16
+ def IsFunctionCall(self) -> bool:
17
+ la = self._input.LT(1)
18
+ if la.type != self.NAME:
19
+ return False
20
+ la = self._input.LT(2)
21
+ if la.type == self.OP:
22
+ return False
23
+ return True
@@ -0,0 +1,224 @@
1
+ # Tactus DSL Validation
2
+
3
+ This module provides ANTLR-based validation for `.tac` files.
4
+
5
+ ## Architecture
6
+
7
+ The validation system uses a three-phase approach:
8
+
9
+ ```
10
+ .tac file
11
+
12
+ Phase 1: ANTLR Parsing (Lua 5.4 grammar)
13
+
14
+ Parse Tree
15
+
16
+ Phase 2: Semantic Visitor (DSL pattern recognition)
17
+
18
+ Registry with Declarations
19
+
20
+ Phase 3: Registry Validation (cross-reference checking)
21
+
22
+ ValidationResult
23
+ ```
24
+
25
+ ### Key Components
26
+
27
+ 1. **ANTLR Grammar** (`grammar/LuaLexer.g4`, `grammar/LuaParser.g4`)
28
+ - Standard Lua 5.4 grammar from antlr/grammars-v4
29
+ - Unmodified - semantic layer added on top
30
+ - Same grammar generates both Python and TypeScript parsers
31
+
32
+ 2. **Generated Parsers** (`generated/`)
33
+ - `LuaLexer.py` / `LuaParser.py` - Python parser
34
+ - `LuaLexer.ts` / `LuaParser.ts` - TypeScript parser
35
+ - Generated from grammar using ANTLR4
36
+ - Committed to version control
37
+
38
+ 3. **Semantic Visitor** (`semantic_visitor.py`)
39
+ - Walks ANTLR parse tree
40
+ - Recognizes DSL function calls (name, parameter, agent, etc.)
41
+ - Extracts arguments and builds registry
42
+ - Does NOT execute code
43
+
44
+ 4. **Validator** (`validator.py`)
45
+ - Main entry point for validation
46
+ - Coordinates parsing, semantic analysis, and registry validation
47
+ - Supports quick (syntax only) and full (semantic) modes
48
+
49
+ ## Validation Modes
50
+
51
+ ### Quick Mode
52
+ - ANTLR parse only
53
+ - Catches syntax errors
54
+ - Fast (suitable for IDE real-time validation)
55
+ - Returns immediately after syntax check
56
+
57
+ ### Full Mode
58
+ - ANTLR parse + semantic analysis + registry validation
59
+ - Catches syntax errors, DSL errors, and semantic errors
60
+ - Slower but thorough
61
+ - Used by CLI and pre-execution validation
62
+
63
+ ## Usage
64
+
65
+ ### Python
66
+
67
+ ```python
68
+ from tactus.validation import TactusValidator, ValidationMode
69
+
70
+ validator = TactusValidator()
71
+
72
+ # Quick validation (syntax only)
73
+ result = validator.validate(source, ValidationMode.QUICK)
74
+
75
+ # Full validation (syntax + semantics)
76
+ result = validator.validate(source, ValidationMode.FULL)
77
+
78
+ if result.valid:
79
+ print(f"Valid! Procedure: {result.registry.procedure_name}")
80
+ else:
81
+ for error in result.errors:
82
+ print(f"Error at {error.location}: {error.message}")
83
+ ```
84
+
85
+ ### CLI
86
+
87
+ ```bash
88
+ # Validate a file
89
+ tactus validate examples/01-basics-hello-world.tac
90
+
91
+ # Quick validation (syntax only)
92
+ tactus validate examples/01-basics-hello-world.tac --quick
93
+ ```
94
+
95
+ ## Parser Generation
96
+
97
+ ### Requirements
98
+
99
+ **Docker is REQUIRED** for parser generation:
100
+ - ANTLR4 requires Java Runtime
101
+ - We use Docker to avoid Java installation
102
+ - Image: `eclipse-temurin:17-jre`
103
+
104
+ ### Regenerating Parsers
105
+
106
+ **When to regenerate:**
107
+ - Only when modifying grammar files
108
+ - Generated parsers are committed to repo
109
+ - End users don't need to regenerate
110
+
111
+ **How to regenerate:**
112
+
113
+ ```bash
114
+ # Ensure Docker is running
115
+ make generate-parsers
116
+
117
+ # Or individually:
118
+ make generate-python-parser
119
+ make generate-typescript-parser
120
+ ```
121
+
122
+ ### Manual Generation
123
+
124
+ **Python:**
125
+ ```bash
126
+ docker run --rm \
127
+ -v $(pwd):/work \
128
+ -v /tmp:/tmp \
129
+ -w /work \
130
+ eclipse-temurin:17-jre \
131
+ java -jar /tmp/antlr-4.13.1-complete.jar \
132
+ -Dlanguage=Python3 \
133
+ -visitor \
134
+ -no-listener \
135
+ -o /work/tactus/validation/generated \
136
+ /work/tactus/validation/grammar/LuaLexer.g4 \
137
+ /work/tactus/validation/grammar/LuaParser.g4
138
+
139
+ # Fix 'this' references (ANTLR bug)
140
+ sed -i 's/this\./self./g' tactus/validation/generated/LuaParser.py
141
+ sed -i 's/this\./self./g' tactus/validation/generated/LuaLexer.py
142
+ ```
143
+
144
+ **TypeScript:**
145
+ ```bash
146
+ docker run --rm \
147
+ -v $(pwd):/work \
148
+ -w /work/tactus-web \
149
+ eclipse-temurin:17-jre \
150
+ bash -c "
151
+ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - &&
152
+ apt-get install -y nodejs &&
153
+ npm install &&
154
+ npx antlr4ts -visitor -no-listener \
155
+ -o src/validation/generated \
156
+ /work/tactus/validation/grammar/LuaLexer.g4 \
157
+ /work/tactus/validation/grammar/LuaParser.g4
158
+ "
159
+ ```
160
+
161
+ ## Testing
162
+
163
+ ### Python Tests
164
+
165
+ ```bash
166
+ pytest tests/validation/test_antlr_parser.py -v
167
+ ```
168
+
169
+ Tests cover:
170
+ - Valid/invalid Lua syntax
171
+ - Error location reporting
172
+ - DSL function recognition
173
+ - Parameter/output/agent extraction
174
+ - All example files
175
+ - Missing required fields
176
+ - Quick vs full mode
177
+
178
+ ### TypeScript Tests
179
+
180
+ ```bash
181
+ cd tactus-web && npm test
182
+ ```
183
+
184
+ **Note:** TypeScript parser currently has compilation issues due to antlr4ts code generation bugs. This is being addressed.
185
+
186
+ ## Known Issues
187
+
188
+ ### TypeScript Parser
189
+ The antlr4ts code generator has known issues:
190
+ - Generates code with TypeScript compilation errors
191
+ - Base class imports need manual fixes
192
+ - Some generated code references don't match antlr4ts API
193
+
194
+ **Workaround:** Python parser is fully functional and used for all validation. TypeScript parser is in progress.
195
+
196
+ ## Why ANTLR?
197
+
198
+ 1. **Formal Grammar** - Standard Lua 5.4 grammar, not custom
199
+ 2. **Multi-Language** - Same grammar generates Python and TypeScript parsers
200
+ 3. **No Execution** - Validates without running code
201
+ 4. **IDE Support** - Parse tree enables syntax highlighting, autocomplete, etc.
202
+ 5. **Accurate** - Catches all Lua syntax errors
203
+ 6. **Maintainable** - Grammar is separate from implementation
204
+
205
+ ## Separation of Concerns
206
+
207
+ - **Validation (ANTLR)**: Parse tree analysis, no execution
208
+ - **Runtime (lupa)**: Actual Lua execution with primitives
209
+
210
+ The same `.tac` file works for both:
211
+ - ANTLR validates structure
212
+ - lupa executes the procedure
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
@@ -0,0 +1,7 @@
1
+ """
2
+ Validation module for Tactus DSL files.
3
+ """
4
+
5
+ from .validator import TactusValidator, ValidationMode
6
+
7
+ __all__ = ["TactusValidator", "ValidationMode"]
@@ -0,0 +1,21 @@
1
+ """
2
+ ANTLR error listener for collecting syntax errors.
3
+ """
4
+
5
+ from antlr4.error.ErrorListener import ErrorListener
6
+ from tactus.core.registry import ValidationMessage
7
+
8
+
9
+ class TactusErrorListener(ErrorListener):
10
+ """Collects syntax errors from ANTLR parser."""
11
+
12
+ def __init__(self):
13
+ self.errors = []
14
+
15
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
16
+ """Called when parser encounters a syntax error."""
17
+ self.errors.append(
18
+ ValidationMessage(
19
+ level="error", message=f"Syntax error: {msg}", location=(line, column)
20
+ )
21
+ )