You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+19-4Lines changed: 19 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,16 +3,20 @@
3
3
## Pipeline
4
4
5
5
```
6
-
Forward: HCL2 Text → Lark Parse Tree → LarkElement Tree → Python Dict/JSON
6
+
Forward: HCL2 Text → [PostLexer] → Lark Parse Tree → LarkElement Tree → Python Dict/JSON
7
7
Reverse: Python Dict/JSON → LarkElement Tree → Lark Tree → HCL2 Text
8
+
Direct: HCL2 Text → [PostLexer] → Lark Parse Tree → LarkElement Tree → Lark Tree → HCL2 Text
8
9
```
9
10
11
+
The **Direct** pipeline (`parse_to_tree` → `transform` → `to_lark` → `reconstruct`) skips serialization to dict, so all IR nodes (including `NewLineOrCommentRule` nodes for whitespace/comments) directly influence the reconstructed output. Any information discarded before the IR is lost in this pipeline.
12
+
10
13
## Module Map
11
14
12
15
| Module | Role |
13
16
|---|---|
14
17
|`hcl2/hcl2.lark`| Lark grammar definition |
15
18
|`hcl2/api.py`| Public API (`load/loads/dump/dumps` + intermediate stages) |
19
+
|`hcl2/postlexer.py`| Token stream transforms between lexer and parser |
16
20
|`hcl2/parser.py`| Lark parser factory with caching |
17
21
|`hcl2/transformer.py`| Lark parse tree → LarkElement tree |
18
22
|`hcl2/deserializer.py`| Python dict → LarkElement tree |
Add new options as `parser.add_argument()` calls in the relevant entry point module.
75
79
80
+
## PostLexer (`postlexer.py`)
81
+
82
+
Lark's `postlex` parameter accepts a single object with a `process(stream)` method that transforms the token stream between the lexer and LALR parser. The `PostLexer` class is designed for extensibility: each transformation is a private method that accepts and yields tokens, and `process()` chains them together.
83
+
84
+
Current passes:
85
+
86
+
-`_merge_newlines_into_operators`
87
+
88
+
To add a new pass: create a private method with the same `(self, stream) -> generator` signature, and add a `yield from` call in `process()`.
89
+
76
90
## Hard Rules
77
91
78
92
These are project-specific constraints that must not be violated:
@@ -88,6 +102,7 @@ These are project-specific constraints that must not be violated:
88
102
## Adding a New Language Construct
89
103
90
104
1. Add grammar rules to `hcl2.lark`
105
+
1. If the new construct creates LALR ambiguities with `NL_OR_COMMENT`, add a postlexer pass in `postlexer.py`
91
106
1. Create rule class(es) in the appropriate `rules/` file
"expr": "${{for k, v in local.map_a : k => v if lookup(local.map_b[v.id], \"enabled\", false) || (contains(local.map_c, v.id) && contains(local.map_d, v.id))}}",
60
+
"__is_block__": true
61
+
}
62
+
},
51
63
{
52
64
"route53_forwarding_rule_shares": "${{for forwarding_rule_key in keys(var.route53_resolver_forwarding_rule_shares) : \"${forwarding_rule_key}\" => {aws_account_ids = [for account_name in var.route53_resolver_forwarding_rule_shares[forwarding_rule_key].aws_account_names : module.remote_state_subaccounts.map[account_name].outputs[\"aws_account_id\"]]}... if substr(bucket_name, 0, 1) == \"l\"}}",
"expr": "${{for k, v in local.map_a : k => v if lookup(local.map_b[v.id], \"enabled\", false) || (contains(local.map_c, v.id) && contains(local.map_d, v.id))}}",
60
+
"__is_block__": true
61
+
}
62
+
},
51
63
{
52
64
"route53_forwarding_rule_shares": "${{for forwarding_rule_key in keys(var.route53_resolver_forwarding_rule_shares) : \"${forwarding_rule_key}\" => {aws_account_ids = [for account_name in var.route53_resolver_forwarding_rule_shares[forwarding_rule_key].aws_account_names : module.remote_state_subaccounts.map[account_name].outputs[\"aws_account_id\"]]}... if substr(bucket_name, 0, 1) == \"l\"}}",
0 commit comments