Skip to content

Commit 01bc888

Browse files
committed
Guidelines Block Implementation
1 parent 2c9c555 commit 01bc888

7 files changed

Lines changed: 243 additions & 149 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.0] - 2026-03-06
9+
10+
### Added
11+
- `grammar/synesis.lark`: keyword `KW_GUIDELINES` e regras `guidelines_block` / `guidelines_lines`
12+
para suporte ao bloco `GUIDELINES...END GUIDELINES` dentro de `FIELD...END FIELD`.
13+
- `ast/nodes.py`: campo `guidelines: Optional[str] = None` em `FieldSpec`; serializado
14+
automaticamente via `to_dict()` como `"guidelines"` no JSON exportado.
15+
- `parser/transformer.py`: handler `KW_GUIDELINES`, transformers `guidelines_lines` e
16+
`guidelines_block`, detecção em `field_props()`, extração em `field_def_block()`.
17+
18+
### Notes
19+
- Adição aditiva: templates sem `GUIDELINES` continuam compilando sem alteração.
20+
- Semântica pass-through: o compilador armazena e exporta o conteúdo sem interpretá-lo.
21+
- Consumidores (MCP Server, agentes de IA) lêem `guidelines` via `synesis.load()` → JSON.
22+
823
## [0.2.11] - 2026-03-06
924

1025
### Fixed

bug-report-lark-vargs-meta.md

Lines changed: 0 additions & 148 deletions
This file was deleted.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "synesis"
7-
version = "0.2.11"
7+
version = "0.3.0"
88
description = "The confluence of information into intelligence - A DSL compiler that transforms qualitative research annotations into canonical knowledge structures"
99
readme = "README.md"
1010
requires-python = ">=3.10"

synesis/ast/nodes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class FieldSpec:
9696
values: Optional[List[OrderedValue]] = None
9797
relations: Optional[Dict[str, str]] = None
9898
arity: Optional[str] = None
99+
guidelines: Optional[str] = None
99100
location: Optional[SourceLocation] = None
100101

101102
def to_dict(self) -> Dict[str, Any]:
@@ -108,6 +109,7 @@ def to_dict(self) -> Dict[str, Any]:
108109
"values": [v.to_dict() for v in self.values] if self.values else None,
109110
"relations": self.relations,
110111
"arity": self.arity,
112+
"guidelines": self.guidelines,
111113
"location": self.location.to_dict() if self.location else None,
112114
}
113115

synesis/grammar/synesis.lark

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ KW_SCALE.5: /scale/i
6767
KW_ENUMERATED.5: /enumerated/i
6868
KW_ORDERED.5: /ordered/i
6969
KW_TOPIC.5: /topic/i
70+
KW_GUIDELINES.5: /guidelines/i
7071

7172
// ============================================
7273
// BLOCO PROJECT
@@ -88,6 +89,9 @@ metadata_line: TEXT_LINE NEWLINE
8889
description: KW_DESCRIPTION NEWLINE description_lines KW_END KW_DESCRIPTION NEWLINE?
8990
description_lines: (TEXT_LINE NEWLINE | NEWLINE)+
9091

92+
guidelines_block: KW_GUIDELINES NEWLINE guidelines_lines KW_END KW_GUIDELINES
93+
guidelines_lines: (_INDENT | _DEDENT | TEXT_LINE NEWLINE | NEWLINE)*
94+
9195
// ============================================
9296
// BLOCOS DE ANOTACAO
9397
// ============================================
@@ -144,6 +148,7 @@ field_props: KW_SCOPE scope_type
144148
| KW_ARITY COMPARATOR NUMBER
145149
| KW_VALUES value_list KW_END KW_VALUES
146150
| KW_RELATIONS relation_list KW_END KW_RELATIONS
151+
| guidelines_block
147152

148153
format_spec: IDENTIFIER | scale_format
149154
scale_format: "[" NUMBER ".." NUMBER "]"

synesis/parser/transformer.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ def KW_ORDERED(self, token: Token) -> str: # noqa: N802
378378
def KW_TOPIC(self, token: Token) -> str: # noqa: N802
379379
return token.value.upper()
380380

381+
def KW_GUIDELINES(self, token: Token) -> str: # noqa: N802
382+
return token.value.upper()
383+
381384
def COMPARATOR(self, token: Token) -> str: # noqa: N802
382385
return token.value
383386

@@ -529,6 +532,36 @@ def description(self, items: List[Any]) -> str:
529532
def description_lines(self, items: List[Any]) -> List[Any]:
530533
return items
531534

535+
def guidelines_lines(self, items: List[Any]) -> List[Any]:
536+
return items
537+
538+
def guidelines_block(self, items: List[Any]) -> Tuple[str, Any]:
539+
lines: List[str] = []
540+
pending_blank = False
541+
keywords = {"GUIDELINES", "END"}
542+
flattened: List[Any] = []
543+
for item in items:
544+
if isinstance(item, list):
545+
flattened.extend(item)
546+
else:
547+
flattened.append(item)
548+
for item in flattened:
549+
if isinstance(item, Token) and item.type in {"NEWLINE", "_INDENT", "_DEDENT"}:
550+
if item.type == "NEWLINE":
551+
if pending_blank:
552+
lines.append("")
553+
pending_blank = False
554+
else:
555+
pending_blank = True
556+
continue
557+
if isinstance(item, str):
558+
if item.upper() in keywords:
559+
continue
560+
lines.append(item)
561+
pending_blank = False
562+
text = "\n".join(lines).strip()
563+
return ("guidelines", text if text else None)
564+
532565
@v_args(tree=True)
533566
def source_block(self, tree: Any) -> SourceNode:
534567
meta = tree.meta
@@ -745,6 +778,7 @@ def field_def_block(self, tree: Any) -> FieldSpec:
745778
values = None
746779
relations = None
747780
arity = None
781+
guidelines = None
748782
for prop in props:
749783
key, value = prop
750784
if key == "scope":
@@ -759,6 +793,8 @@ def field_def_block(self, tree: Any) -> FieldSpec:
759793
relations = value
760794
elif key == "arity":
761795
arity = value
796+
elif key == "guidelines":
797+
guidelines = value
762798
if scope is None:
763799
scope = Scope.ITEM
764800
return FieldSpec(
@@ -770,6 +806,7 @@ def field_def_block(self, tree: Any) -> FieldSpec:
770806
values=values,
771807
relations=relations,
772808
arity=arity,
809+
guidelines=guidelines,
773810
location=_source_location(self.file_path, meta),
774811
)
775812

@@ -790,6 +827,8 @@ def field_props(self, items: List[Any]) -> Tuple[str, Any]:
790827
return ("arity", f"{items[1]} {items[2]}")
791828
if items[0] == "VALUES":
792829
return ("values", items[1])
830+
if isinstance(items[0], tuple) and items[0][0] == "guidelines":
831+
return items[0]
793832
return ("relations", items[1])
794833

795834
def scope_type(self, items: List[Any]) -> str:

0 commit comments

Comments
 (0)