@voodocs/cli 2.5.2 → 3.0.0
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.
- package/CHANGELOG.md +156 -235
- package/README.md +215 -399
- package/lib/cli/__init__.py +1 -3
- package/lib/cli/init.py +11 -13
- package/lib/darkarts/annotations/darkarts_parser.py +159 -142
- package/lib/darkarts/annotations/darkarts_parser_v2.py.bak +238 -0
- package/lib/darkarts/companion_files.py +62 -9
- package/lib/darkarts/context/ai_instructions.py +385 -571
- package/lib/darkarts/context/ai_instructions_v2.py.bak +741 -0
- package/lib/darkarts/darkarts_abbreviations.py +324 -0
- package/lib/darkarts/darkarts_parser_v3.py +486 -0
- package/lib/darkarts/darkarts_patterns.py +249 -0
- package/lib/darkarts/darkarts_symbols.py +276 -0
- package/lib/darkarts/plugins/voodocs/documentation_generator.py +31 -2
- package/lib/darkarts/validation/semantic.py +135 -18
- package/package.json +5 -4
- package/lib/cli/convert.py +0 -131
- package/lib/darkarts/voodocs_lite_dict.py +0 -216
- package/lib/darkarts/voodocs_lite_dict_v2.py +0 -198
- package/lib/darkarts/voodocs_lite_parser.py +0 -343
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"""@darkarts
|
|
2
|
+
⊢{darkarts:parser-v3}
|
|
3
|
+
∂{re,typing,dataclasses}
|
|
4
|
+
⚠{@darkarts∈docstrings,unicode-support}
|
|
5
|
+
⊨{∀parse→structured-output,¬modify-src,handle-errors}
|
|
6
|
+
⊕{validates annotations}
|
|
7
|
+
⚡{O(n)|n=annotation-length}
|
|
8
|
+
🔒{read-only}
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
DarkArts v3.0.0 - Enhanced Parser
|
|
13
|
+
|
|
14
|
+
Parses @darkarts symbolic annotations with support for:
|
|
15
|
+
- Pattern shortcuts (:uuid, :email, ≥N, etc.)
|
|
16
|
+
- Enhanced abbreviations (200+ terms)
|
|
17
|
+
- New symbols (⇄, ⊕, ⊗, ≈, ∴, ∀, ∃)
|
|
18
|
+
- Better validation and error messages
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from typing import Dict, List, Optional, Any, Tuple
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
|
|
25
|
+
from .darkarts_symbols import (
|
|
26
|
+
ALL_SYMBOLS,
|
|
27
|
+
get_symbol_name,
|
|
28
|
+
get_symbol_description,
|
|
29
|
+
is_valid_symbol,
|
|
30
|
+
get_symbol_rules,
|
|
31
|
+
)
|
|
32
|
+
from .darkarts_patterns import (
|
|
33
|
+
compress_pattern,
|
|
34
|
+
expand_pattern,
|
|
35
|
+
validate_pattern,
|
|
36
|
+
)
|
|
37
|
+
from .darkarts_abbreviations import (
|
|
38
|
+
compress,
|
|
39
|
+
expand,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class DarkArtsAnnotation:
|
|
45
|
+
"""Parsed DarkArts annotation with v3.0.0 features."""
|
|
46
|
+
|
|
47
|
+
# Core fields (v2.x)
|
|
48
|
+
purpose: Optional[str] = None # ⊢
|
|
49
|
+
dependencies: List[str] = field(default_factory=list) # ∂
|
|
50
|
+
assumptions: List[str] = field(default_factory=list) # ⚠
|
|
51
|
+
preconditions: List[str] = field(default_factory=list) # ⊳
|
|
52
|
+
postconditions: List[str] = field(default_factory=list) # ⊲
|
|
53
|
+
invariants: List[str] = field(default_factory=list) # ⊨
|
|
54
|
+
complexity: Optional[str] = None # ⚡
|
|
55
|
+
security: List[str] = field(default_factory=list) # 🔒
|
|
56
|
+
|
|
57
|
+
# New fields (v3.0.0)
|
|
58
|
+
bidirectional: List[str] = field(default_factory=list) # ⇄
|
|
59
|
+
side_effects: List[str] = field(default_factory=list) # ⊕
|
|
60
|
+
forbidden: List[str] = field(default_factory=list) # ⊗
|
|
61
|
+
approximation: List[str] = field(default_factory=list) # ≈
|
|
62
|
+
consequence: List[str] = field(default_factory=list) # ∴
|
|
63
|
+
universal: List[str] = field(default_factory=list) # ∀
|
|
64
|
+
existential: List[str] = field(default_factory=list) # ∃
|
|
65
|
+
|
|
66
|
+
# Metadata
|
|
67
|
+
raw_text: str = ""
|
|
68
|
+
line_number: int = 0
|
|
69
|
+
validation_errors: List[str] = field(default_factory=list)
|
|
70
|
+
validation_warnings: List[str] = field(default_factory=list)
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
73
|
+
"""Convert to dictionary."""
|
|
74
|
+
return {
|
|
75
|
+
'purpose': self.purpose,
|
|
76
|
+
'dependencies': self.dependencies,
|
|
77
|
+
'assumptions': self.assumptions,
|
|
78
|
+
'preconditions': self.preconditions,
|
|
79
|
+
'postconditions': self.postconditions,
|
|
80
|
+
'invariants': self.invariants,
|
|
81
|
+
'complexity': self.complexity,
|
|
82
|
+
'security': self.security,
|
|
83
|
+
'bidirectional': self.bidirectional,
|
|
84
|
+
'side_effects': self.side_effects,
|
|
85
|
+
'forbidden': self.forbidden,
|
|
86
|
+
'approximation': self.approximation,
|
|
87
|
+
'consequence': self.consequence,
|
|
88
|
+
'universal': self.universal,
|
|
89
|
+
'existential': self.existential,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def has_errors(self) -> bool:
|
|
93
|
+
"""Check if annotation has validation errors."""
|
|
94
|
+
return len(self.validation_errors) > 0
|
|
95
|
+
|
|
96
|
+
def has_warnings(self) -> bool:
|
|
97
|
+
"""Check if annotation has validation warnings."""
|
|
98
|
+
return len(self.validation_warnings) > 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class DarkArtsParserV3:
|
|
102
|
+
"""
|
|
103
|
+
Enhanced parser for DarkArts v3.0.0 annotations.
|
|
104
|
+
|
|
105
|
+
Features:
|
|
106
|
+
- Pattern shortcuts (:uuid, :email, ≥N, etc.)
|
|
107
|
+
- Enhanced abbreviations (200+ terms)
|
|
108
|
+
- New symbols (⇄, ⊕, ⊗, ≈, ∴, ∀, ∃)
|
|
109
|
+
- Better validation and error messages
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
# Pattern to match @darkarts annotations
|
|
113
|
+
PATTERN = r'/\*\*@darkarts\s*(.*?)\s*\*/'
|
|
114
|
+
PATTERN_MULTILINE = r'"""@darkarts\s*(.*?)\s*"""'
|
|
115
|
+
|
|
116
|
+
# Symbol to field mapping
|
|
117
|
+
SYMBOL_FIELDS = {
|
|
118
|
+
'⊢': 'purpose',
|
|
119
|
+
'∂': 'dependencies',
|
|
120
|
+
'⚠': 'assumptions',
|
|
121
|
+
'⊳': 'preconditions',
|
|
122
|
+
'⊲': 'postconditions',
|
|
123
|
+
'⊨': 'invariants',
|
|
124
|
+
'⚡': 'complexity',
|
|
125
|
+
'🔒': 'security',
|
|
126
|
+
'⇄': 'bidirectional',
|
|
127
|
+
'⊕': 'side_effects',
|
|
128
|
+
'⊗': 'forbidden',
|
|
129
|
+
'≈': 'approximation',
|
|
130
|
+
'∴': 'consequence',
|
|
131
|
+
'∀': 'universal',
|
|
132
|
+
'∃': 'existential',
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
def __init__(self, enable_compression: bool = False, enable_validation: bool = True):
|
|
136
|
+
"""
|
|
137
|
+
Initialize parser.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
enable_compression: Enable abbreviation compression
|
|
141
|
+
enable_validation: Enable validation checks
|
|
142
|
+
"""
|
|
143
|
+
self.enable_compression = enable_compression
|
|
144
|
+
self.enable_validation = enable_validation
|
|
145
|
+
|
|
146
|
+
def parse(self, source_code: str) -> List[DarkArtsAnnotation]:
|
|
147
|
+
"""
|
|
148
|
+
Parse all @darkarts annotations from source code.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
source_code: Source code containing @darkarts annotations
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List of parsed annotations
|
|
155
|
+
"""
|
|
156
|
+
annotations = []
|
|
157
|
+
|
|
158
|
+
# Try both patterns (JavaScript-style and Python-style)
|
|
159
|
+
patterns = [
|
|
160
|
+
(self.PATTERN, re.DOTALL),
|
|
161
|
+
(self.PATTERN_MULTILINE, re.DOTALL),
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
for pattern, flags in patterns:
|
|
165
|
+
matches = re.finditer(pattern, source_code, flags)
|
|
166
|
+
|
|
167
|
+
for match in matches:
|
|
168
|
+
annotation_text = match.group(1)
|
|
169
|
+
line_number = source_code[:match.start()].count('\n') + 1
|
|
170
|
+
annotation = self.parse_annotation(annotation_text, line_number)
|
|
171
|
+
annotations.append(annotation)
|
|
172
|
+
|
|
173
|
+
return annotations
|
|
174
|
+
|
|
175
|
+
def parse_annotation(self, text: str, line_number: int = 0) -> DarkArtsAnnotation:
|
|
176
|
+
"""
|
|
177
|
+
Parse a single @darkarts annotation.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
text: Annotation text (without @darkarts marker)
|
|
181
|
+
line_number: Line number in source file
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Parsed annotation
|
|
185
|
+
"""
|
|
186
|
+
annotation = DarkArtsAnnotation(raw_text=text, line_number=line_number)
|
|
187
|
+
|
|
188
|
+
# Split into lines
|
|
189
|
+
lines = text.strip().split('\n')
|
|
190
|
+
|
|
191
|
+
# Track which symbols we've seen
|
|
192
|
+
seen_symbols = set()
|
|
193
|
+
|
|
194
|
+
for line_idx, line in enumerate(lines):
|
|
195
|
+
line = line.strip()
|
|
196
|
+
if not line:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
# Find symbol at start of line
|
|
200
|
+
symbol = self._extract_symbol(line)
|
|
201
|
+
|
|
202
|
+
if symbol:
|
|
203
|
+
# Validate symbol
|
|
204
|
+
if not is_valid_symbol(symbol):
|
|
205
|
+
annotation.validation_errors.append(
|
|
206
|
+
f"Line {line_number + line_idx}: Unknown symbol '{symbol}'"
|
|
207
|
+
)
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
# Check for duplicate symbols (if not allowed)
|
|
211
|
+
rules = get_symbol_rules(symbol)
|
|
212
|
+
if not rules.get('multiple', False) and symbol in seen_symbols:
|
|
213
|
+
annotation.validation_errors.append(
|
|
214
|
+
f"Line {line_number + line_idx}: Duplicate symbol '{symbol}'. "
|
|
215
|
+
f"Only one {get_symbol_name(symbol)} allowed."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
seen_symbols.add(symbol)
|
|
219
|
+
|
|
220
|
+
# Extract content after symbol
|
|
221
|
+
content = line[len(symbol):].strip()
|
|
222
|
+
|
|
223
|
+
# Remove surrounding braces if present
|
|
224
|
+
if content.startswith('{') and content.endswith('}'):
|
|
225
|
+
content = content[1:-1]
|
|
226
|
+
|
|
227
|
+
# Parse field
|
|
228
|
+
self._parse_field(annotation, symbol, content, line_number + line_idx)
|
|
229
|
+
|
|
230
|
+
# Validate annotation
|
|
231
|
+
if self.enable_validation:
|
|
232
|
+
self._validate_annotation(annotation)
|
|
233
|
+
|
|
234
|
+
return annotation
|
|
235
|
+
|
|
236
|
+
def _extract_symbol(self, line: str) -> Optional[str]:
|
|
237
|
+
"""Extract symbol from start of line."""
|
|
238
|
+
for symbol in ALL_SYMBOLS.keys():
|
|
239
|
+
if line.startswith(symbol):
|
|
240
|
+
return symbol
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
def _parse_field(self, annotation: DarkArtsAnnotation, symbol: str,
|
|
244
|
+
content: str, line_number: int):
|
|
245
|
+
"""
|
|
246
|
+
Parse a field and update annotation.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
annotation: Annotation to update
|
|
250
|
+
symbol: Symbol (⊢, ∂, etc.)
|
|
251
|
+
content: Content to parse
|
|
252
|
+
line_number: Line number for error messages
|
|
253
|
+
"""
|
|
254
|
+
field_name = self.SYMBOL_FIELDS.get(symbol)
|
|
255
|
+
if not field_name:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
# Apply compression if enabled
|
|
259
|
+
if self.enable_compression:
|
|
260
|
+
content = compress(content)
|
|
261
|
+
|
|
262
|
+
# Parse based on field type
|
|
263
|
+
if field_name == 'purpose':
|
|
264
|
+
annotation.purpose = content
|
|
265
|
+
|
|
266
|
+
elif field_name == 'complexity':
|
|
267
|
+
annotation.complexity = content
|
|
268
|
+
|
|
269
|
+
elif field_name in ['dependencies', 'assumptions', 'preconditions',
|
|
270
|
+
'postconditions', 'invariants', 'security',
|
|
271
|
+
'bidirectional', 'side_effects', 'forbidden',
|
|
272
|
+
'approximation', 'consequence', 'universal', 'existential']:
|
|
273
|
+
# Parse as list
|
|
274
|
+
items = self._parse_list(content)
|
|
275
|
+
|
|
276
|
+
# Validate patterns in preconditions
|
|
277
|
+
if field_name == 'preconditions' and self.enable_validation:
|
|
278
|
+
for item in items:
|
|
279
|
+
# Check if item contains a pattern
|
|
280
|
+
if ':' in item or any(op in item for op in ['≥', '≤', '>', '<', '∈', '[']):
|
|
281
|
+
is_valid, error = validate_pattern(item)
|
|
282
|
+
if not is_valid:
|
|
283
|
+
annotation.validation_errors.append(
|
|
284
|
+
f"Line {line_number}: {error}"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
setattr(annotation, field_name, items)
|
|
288
|
+
|
|
289
|
+
def _parse_list(self, content: str) -> List[str]:
|
|
290
|
+
"""
|
|
291
|
+
Parse comma-separated list.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
content: Content like "a,b,c" or "{a,b,c}"
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
List of items
|
|
298
|
+
"""
|
|
299
|
+
# Split by comma, respecting nested braces
|
|
300
|
+
items = []
|
|
301
|
+
current = []
|
|
302
|
+
depth = 0
|
|
303
|
+
|
|
304
|
+
for char in content:
|
|
305
|
+
if char == '{':
|
|
306
|
+
depth += 1
|
|
307
|
+
elif char == '}':
|
|
308
|
+
depth -= 1
|
|
309
|
+
elif char == ',' and depth == 0:
|
|
310
|
+
item = ''.join(current).strip()
|
|
311
|
+
if item:
|
|
312
|
+
items.append(item)
|
|
313
|
+
current = []
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
current.append(char)
|
|
317
|
+
|
|
318
|
+
# Add last item
|
|
319
|
+
item = ''.join(current).strip()
|
|
320
|
+
if item:
|
|
321
|
+
items.append(item)
|
|
322
|
+
|
|
323
|
+
return items
|
|
324
|
+
|
|
325
|
+
def _validate_annotation(self, annotation: DarkArtsAnnotation):
|
|
326
|
+
"""
|
|
327
|
+
Validate annotation and add warnings/errors.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
annotation: Annotation to validate
|
|
331
|
+
"""
|
|
332
|
+
# Check required fields
|
|
333
|
+
if not annotation.purpose:
|
|
334
|
+
annotation.validation_errors.append(
|
|
335
|
+
f"Line {annotation.line_number}: Missing required purpose (⊢)"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Check for contradictions
|
|
339
|
+
self._check_contradictions(annotation)
|
|
340
|
+
|
|
341
|
+
# Check for missing common fields
|
|
342
|
+
self._check_missing_fields(annotation)
|
|
343
|
+
|
|
344
|
+
def _check_contradictions(self, annotation: DarkArtsAnnotation):
|
|
345
|
+
"""Check for logical contradictions."""
|
|
346
|
+
# Check invariants vs side effects
|
|
347
|
+
if annotation.invariants and annotation.side_effects:
|
|
348
|
+
for invariant in annotation.invariants:
|
|
349
|
+
if 'no db mod' in invariant or 'no modification' in invariant:
|
|
350
|
+
for effect in annotation.side_effects:
|
|
351
|
+
if 'db' in effect or 'database' in effect:
|
|
352
|
+
annotation.validation_errors.append(
|
|
353
|
+
f"Line {annotation.line_number}: Contradiction - "
|
|
354
|
+
f"invariant says no db modification, but side effect modifies db"
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Check forbidden vs side effects
|
|
358
|
+
if annotation.forbidden and annotation.side_effects:
|
|
359
|
+
for forbidden in annotation.forbidden:
|
|
360
|
+
for effect in annotation.side_effects:
|
|
361
|
+
# Simple overlap check
|
|
362
|
+
forbidden_words = set(forbidden.lower().split())
|
|
363
|
+
effect_words = set(effect.lower().split())
|
|
364
|
+
if forbidden_words & effect_words:
|
|
365
|
+
annotation.validation_warnings.append(
|
|
366
|
+
f"Line {annotation.line_number}: Possible contradiction - "
|
|
367
|
+
f"forbidden operation '{forbidden}' but side effect '{effect}'"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def _check_missing_fields(self, annotation: DarkArtsAnnotation):
|
|
371
|
+
"""Check for commonly missing fields."""
|
|
372
|
+
# If has side effects, should probably have security considerations
|
|
373
|
+
if annotation.side_effects and not annotation.security:
|
|
374
|
+
annotation.validation_warnings.append(
|
|
375
|
+
f"Line {annotation.line_number}: Side effects present but no security considerations"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# If has dependencies, should probably have assumptions
|
|
379
|
+
if annotation.dependencies and not annotation.assumptions:
|
|
380
|
+
annotation.validation_warnings.append(
|
|
381
|
+
f"Line {annotation.line_number}: Dependencies present but no assumptions about their availability"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def expand_annotation(self, annotation: DarkArtsAnnotation) -> DarkArtsAnnotation:
|
|
385
|
+
"""
|
|
386
|
+
Expand abbreviated annotation to full form.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
annotation: Annotation with abbreviations
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Annotation with expanded text
|
|
393
|
+
"""
|
|
394
|
+
expanded = DarkArtsAnnotation(
|
|
395
|
+
raw_text=annotation.raw_text,
|
|
396
|
+
line_number=annotation.line_number,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Expand purpose
|
|
400
|
+
if annotation.purpose:
|
|
401
|
+
expanded.purpose = expand(annotation.purpose)
|
|
402
|
+
|
|
403
|
+
# Expand lists
|
|
404
|
+
for field_name in ['dependencies', 'assumptions', 'preconditions',
|
|
405
|
+
'postconditions', 'invariants', 'security',
|
|
406
|
+
'bidirectional', 'side_effects', 'forbidden',
|
|
407
|
+
'approximation', 'consequence', 'universal', 'existential']:
|
|
408
|
+
items = getattr(annotation, field_name)
|
|
409
|
+
if items:
|
|
410
|
+
expanded_items = [expand(item) for item in items]
|
|
411
|
+
# Also expand patterns in preconditions
|
|
412
|
+
if field_name == 'preconditions':
|
|
413
|
+
expanded_items = [expand_pattern(item) for item in expanded_items]
|
|
414
|
+
setattr(expanded, field_name, expanded_items)
|
|
415
|
+
|
|
416
|
+
# Copy complexity as-is
|
|
417
|
+
expanded.complexity = annotation.complexity
|
|
418
|
+
|
|
419
|
+
return expanded
|
|
420
|
+
|
|
421
|
+
def format_annotation(self, annotation: DarkArtsAnnotation,
|
|
422
|
+
expanded: bool = False) -> str:
|
|
423
|
+
"""
|
|
424
|
+
Format annotation back to DarkArts notation.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
annotation: Annotation to format
|
|
428
|
+
expanded: Use expanded form (no abbreviations)
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
Formatted annotation string
|
|
432
|
+
"""
|
|
433
|
+
if expanded:
|
|
434
|
+
annotation = self.expand_annotation(annotation)
|
|
435
|
+
|
|
436
|
+
lines = []
|
|
437
|
+
|
|
438
|
+
# Add purpose (required)
|
|
439
|
+
if annotation.purpose:
|
|
440
|
+
lines.append(f'⊢{{{annotation.purpose}}}')
|
|
441
|
+
|
|
442
|
+
# Add other fields if present
|
|
443
|
+
if annotation.dependencies:
|
|
444
|
+
lines.append(f'∂{{{",".join(annotation.dependencies)}}}')
|
|
445
|
+
|
|
446
|
+
if annotation.assumptions:
|
|
447
|
+
lines.append(f'⚠{{{",".join(annotation.assumptions)}}}')
|
|
448
|
+
|
|
449
|
+
if annotation.preconditions:
|
|
450
|
+
lines.append(f'⊳{{{",".join(annotation.preconditions)}}}')
|
|
451
|
+
|
|
452
|
+
if annotation.postconditions:
|
|
453
|
+
lines.append(f'⊲{{{",".join(annotation.postconditions)}}}')
|
|
454
|
+
|
|
455
|
+
if annotation.invariants:
|
|
456
|
+
lines.append(f'⊨{{{",".join(annotation.invariants)}}}')
|
|
457
|
+
|
|
458
|
+
if annotation.complexity:
|
|
459
|
+
lines.append(f'⚡{{{annotation.complexity}}}')
|
|
460
|
+
|
|
461
|
+
if annotation.security:
|
|
462
|
+
lines.append(f'🔒{{{",".join(annotation.security)}}}')
|
|
463
|
+
|
|
464
|
+
# v3.0.0 fields
|
|
465
|
+
if annotation.bidirectional:
|
|
466
|
+
lines.append(f'⇄{{{",".join(annotation.bidirectional)}}}')
|
|
467
|
+
|
|
468
|
+
if annotation.side_effects:
|
|
469
|
+
lines.append(f'⊕{{{",".join(annotation.side_effects)}}}')
|
|
470
|
+
|
|
471
|
+
if annotation.forbidden:
|
|
472
|
+
lines.append(f'⊗{{{",".join(annotation.forbidden)}}}')
|
|
473
|
+
|
|
474
|
+
if annotation.approximation:
|
|
475
|
+
lines.append(f'≈{{{",".join(annotation.approximation)}}}')
|
|
476
|
+
|
|
477
|
+
if annotation.consequence:
|
|
478
|
+
lines.append(f'∴{{{",".join(annotation.consequence)}}}')
|
|
479
|
+
|
|
480
|
+
if annotation.universal:
|
|
481
|
+
lines.append(f'∀{{{",".join(annotation.universal)}}}')
|
|
482
|
+
|
|
483
|
+
if annotation.existential:
|
|
484
|
+
lines.append(f'∃{{{",".join(annotation.existential)}}}')
|
|
485
|
+
|
|
486
|
+
return '\n'.join(lines)
|