pcl-lang 0.1.0__tar.gz
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.
- pcl_lang-0.1.0/.gitignore +24 -0
- pcl_lang-0.1.0/DEVELOPMENT.md +64 -0
- pcl_lang-0.1.0/LICENSE +21 -0
- pcl_lang-0.1.0/PKG-INFO +255 -0
- pcl_lang-0.1.0/README.md +236 -0
- pcl_lang-0.1.0/examples/agent.pcl +32 -0
- pcl_lang-0.1.0/examples/persona.pcl +11 -0
- pcl_lang-0.1.0/examples/test.pcl +46 -0
- pcl_lang-0.1.0/examples/tools.pcl +15 -0
- pcl_lang-0.1.0/pyproject.toml +33 -0
- pcl_lang-0.1.0/src/pcl/__init__.py +5 -0
- pcl_lang-0.1.0/src/pcl/cli.py +192 -0
- pcl_lang-0.1.0/src/pcl/compiler.py +430 -0
- pcl_lang-0.1.0/src/pcl/errors.py +23 -0
- pcl_lang-0.1.0/src/pcl/parser.py +323 -0
- pcl_lang-0.1.0/test.pclc +0 -0
- pcl_lang-0.1.0/tests/__init__.py +0 -0
- pcl_lang-0.1.0/tests/test_cli.py +245 -0
- pcl_lang-0.1.0/tests/test_compiler.py +386 -0
- pcl_lang-0.1.0/tests/test_parser.py +368 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
|
|
8
|
+
# uv / virtualenv
|
|
9
|
+
.venv/
|
|
10
|
+
.python-version
|
|
11
|
+
uv.lock
|
|
12
|
+
|
|
13
|
+
# Build / dist
|
|
14
|
+
*.egg-info/
|
|
15
|
+
dist/
|
|
16
|
+
build/
|
|
17
|
+
|
|
18
|
+
# pytest / coverage
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
htmlcov/
|
|
22
|
+
|
|
23
|
+
# macOS
|
|
24
|
+
.DS_Store
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- Python 3.11+
|
|
6
|
+
- [uv](https://docs.astral.sh/uv/) (package manager)
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
cd src/pcl
|
|
12
|
+
|
|
13
|
+
# Create virtual environment and activate it
|
|
14
|
+
uv venv && source .venv/bin/activate
|
|
15
|
+
|
|
16
|
+
# Install the package in editable mode with dev dependencies
|
|
17
|
+
uv pip install -e ".[dev]"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running Tests
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv run pytest tests/ -q
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
With coverage:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv run pytest tests/ --cov=pcl --cov-report=term-missing
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Using the CLI
|
|
33
|
+
|
|
34
|
+
After installation, the `pcl` command is available:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pcl check examples/agent.pcl
|
|
38
|
+
pcl compile examples/agent.pcl
|
|
39
|
+
pcl render examples/agent.pcl --var date=2025-01-01 --var query="hello" --var premium=true
|
|
40
|
+
pcl watch examples/agent.pcl --var date=2025-01-01
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Building a Distribution
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv run python -m build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This produces a wheel and sdist in `dist/`.
|
|
50
|
+
|
|
51
|
+
## Project Layout
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
src/pcl/
|
|
55
|
+
src/pcl/ # package source
|
|
56
|
+
__init__.py # public API (compile, render)
|
|
57
|
+
parser.py # line-by-line recursive descent parser
|
|
58
|
+
compiler.py # two-phase IR compiler
|
|
59
|
+
cli.py # Typer CLI
|
|
60
|
+
errors.py # PCLError with line/file context
|
|
61
|
+
tests/ # pytest test suite
|
|
62
|
+
examples/ # sample .pcl files
|
|
63
|
+
pyproject.toml # build config (hatchling)
|
|
64
|
+
```
|
pcl_lang-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tanay Deshmukh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pcl_lang-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pcl-lang
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A DSL for authoring, composing, and managing LLM prompts as modular, version-controlled artifacts
|
|
5
|
+
Project-URL: Homepage, https://github.com/cybergla/prompt-composition-language
|
|
6
|
+
Project-URL: Repository, https://github.com/cybergla/prompt-composition-language
|
|
7
|
+
Author: Tanay Deshmukh
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: cbor2>=5.0
|
|
12
|
+
Requires-Dist: pyyaml>=6.0
|
|
13
|
+
Requires-Dist: typer>=0.12
|
|
14
|
+
Requires-Dist: watchdog>=4.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# PCL — Prompt Composition Language
|
|
21
|
+
|
|
22
|
+
PCL is a small DSL for authoring, composing, and managing LLM prompts as modular, version-controlled artifacts. A `.pcl` file compiles to a plain text string ready to use as a system prompt with any LLM API.
|
|
23
|
+
|
|
24
|
+
# Why PCL?
|
|
25
|
+
|
|
26
|
+
I came up with PCL as a way to manage prompts when they get too large and unwieldy. Most agentic applications today involve persisting prompts as a bunch of text/markdown files across disparate locations which are then read at runtime before using them for an LLM inference request. Parts of the prompt are often static (rules, persona, tools) while some can be more dynamic (user messages, context). Managing multiple sets of prompt .md files, with different versions, becomes a real engineering challenge. Template engines like Jinja alleviate this problem somewhat but I wanted something that went beyond simple variable substitution and was specifically tailored for LLM applications.
|
|
27
|
+
|
|
28
|
+
With PCL you get in-built support for templating, composition, compile time checks etc. Think of prompts that same way as you do code. You define your prompts as a modular set of text files (`.pcl` files), then have the compiler compile and weave the prompts together into a single IR template, which can be rendered at runtime with variable substitution.
|
|
29
|
+
|
|
30
|
+
pcl files are just like any other source- version controlled, with a well defined syntax.
|
|
31
|
+
|
|
32
|
+
### Author
|
|
33
|
+
|
|
34
|
+
Tanay Deshmukh (& vibe-coded with Claude)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **Blocks** — define named fragments with `@block name:`, compose them with `@include`
|
|
41
|
+
- **Imports** — split prompts across files with `@import ./file.pcl [as ns]`
|
|
42
|
+
- **Variables** — `${var}` resolved at render time; `${var | default}` for fallbacks
|
|
43
|
+
- **Conditionals** — `@if variable:` / `@if not variable:` for truthiness-based branching
|
|
44
|
+
- **Raw blocks** — `@raw` / `@end` passes content through unmodified (no interpolation)
|
|
45
|
+
- **Comments** — lines starting with `#` are stripped from output
|
|
46
|
+
- **Frontmatter** — optional YAML metadata (`version`, `description`, arbitrary keys)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
Requires Python 3.11+. Recommend using [uv](https://github.com/astral-sh/uv) for environment management.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone <repo>
|
|
56
|
+
cd pcl
|
|
57
|
+
uv venv
|
|
58
|
+
source .venv/bin/activate
|
|
59
|
+
uv pip install -e ".[dev]"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Verify:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pcl --help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## CLI
|
|
71
|
+
|
|
72
|
+
### `pcl compile`
|
|
73
|
+
|
|
74
|
+
Compile a `.pcl` file and dump its intermediate representation — a tree of `TEXT`, `VAR`, and `IF` segments. Useful for inspecting template structure before rendering.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pcl compile examples/agent.pcl
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Example output:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Metadata:
|
|
84
|
+
version: 1.0
|
|
85
|
+
description: Research assistant system prompt
|
|
86
|
+
|
|
87
|
+
Segments:
|
|
88
|
+
TEXT 'You are an expert research assistant...'
|
|
89
|
+
VAR ${date}
|
|
90
|
+
IF premium:
|
|
91
|
+
TEXT 'You have access to the premium document index.'
|
|
92
|
+
IF not premium:
|
|
93
|
+
TEXT 'Upgrade to unlock the premium document index.'
|
|
94
|
+
VAR ${query | no query provided}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use `-o`/`--output` to write the compiled IR to a binary `.pclc` file instead of printing it. This enables compile-once-render-many workflows without re-parsing source files.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pcl compile examples/agent.pcl -o agent.pclc
|
|
101
|
+
# Compiled to agent.pclc
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `pcl render`
|
|
105
|
+
|
|
106
|
+
Render a `.pcl` or `.pclc` file with explicit variable values.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# From source
|
|
110
|
+
pcl render examples/agent.pcl \
|
|
111
|
+
--var date=2026-02-28 \
|
|
112
|
+
--var query="What is alignment?" \
|
|
113
|
+
--var premium=true
|
|
114
|
+
|
|
115
|
+
# From pre-compiled .pclc (no re-parsing)
|
|
116
|
+
pcl render agent.pclc \
|
|
117
|
+
--var date=2026-02-28 \
|
|
118
|
+
--var query="What is alignment?" \
|
|
119
|
+
--var premium=true
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`--var` accepts `key=value`. Values `true` and `false` are coerced to booleans.
|
|
123
|
+
|
|
124
|
+
### `pcl check`
|
|
125
|
+
|
|
126
|
+
Validate a `.pcl` file without producing output. Exits `0` on success, `1` on error. Prints the error message and line number on failure.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pcl check examples/agent.pcl
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `pcl watch`
|
|
133
|
+
|
|
134
|
+
Recompile whenever the file changes. Accepts the same `--var` flags as `render`.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pcl watch examples/agent.pcl \
|
|
138
|
+
--var date=2026-02-28 \
|
|
139
|
+
--var premium=false
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Python API
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from pcl import compile, render, serialize, deserialize, CompiledTemplate
|
|
148
|
+
import cbor2
|
|
149
|
+
|
|
150
|
+
# compile() — produces a CompiledTemplate (IR) with metadata and segments
|
|
151
|
+
template = compile("examples/agent.pcl")
|
|
152
|
+
print(template.metadata) # {"version": 1.0, "description": "..."}
|
|
153
|
+
print(template.segments) # [str, VarRef, Conditional, ...]
|
|
154
|
+
|
|
155
|
+
# render() — resolves variables and conditionals, returns final string
|
|
156
|
+
prompt = render("examples/agent.pcl", variables={
|
|
157
|
+
"date": "2026-02-28",
|
|
158
|
+
"query": "What is alignment?",
|
|
159
|
+
"premium": True,
|
|
160
|
+
})
|
|
161
|
+
print(prompt)
|
|
162
|
+
|
|
163
|
+
# compile once, render many — avoids re-parsing for each variable set
|
|
164
|
+
template = compile("examples/agent.pcl")
|
|
165
|
+
for query in queries:
|
|
166
|
+
prompt = render(template, variables={"date": "2026-02-28", "query": query})
|
|
167
|
+
|
|
168
|
+
# serialize to .pclc — write compiled IR to disk
|
|
169
|
+
with open("agent.pclc", "wb") as f:
|
|
170
|
+
f.write(cbor2.dumps(serialize(template)))
|
|
171
|
+
|
|
172
|
+
# deserialize from .pclc — reconstruct IR without re-parsing source
|
|
173
|
+
with open("agent.pclc", "rb") as f:
|
|
174
|
+
template = deserialize(cbor2.loads(f.read()))
|
|
175
|
+
prompt = render(template, variables={"date": "2026-02-28", "query": "..."})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Language Quick Reference
|
|
181
|
+
|
|
182
|
+
```pcl
|
|
183
|
+
---
|
|
184
|
+
version: 1.0
|
|
185
|
+
description: Optional YAML frontmatter — available as metadata, not in output
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
@import ./other.pcl
|
|
189
|
+
@import ./lib.pcl as lib
|
|
190
|
+
|
|
191
|
+
# This is a comment — stripped from output
|
|
192
|
+
|
|
193
|
+
@block intro:
|
|
194
|
+
Defined here, emitted only when @include'd.
|
|
195
|
+
|
|
196
|
+
@include intro # emit a local block
|
|
197
|
+
@include lib.greet # emit a named block from an imported file
|
|
198
|
+
@include other # emit the entire body of other.pcl
|
|
199
|
+
|
|
200
|
+
@if premium:
|
|
201
|
+
Shown only when premium is truthy.
|
|
202
|
+
|
|
203
|
+
@if not premium:
|
|
204
|
+
Shown only when premium is falsy or absent.
|
|
205
|
+
|
|
206
|
+
Hello ${name}! # required variable — error if missing
|
|
207
|
+
Hello ${name | world}! # variable with default
|
|
208
|
+
|
|
209
|
+
@raw
|
|
210
|
+
${not_interpolated} @not_a_directive # this is not a comment
|
|
211
|
+
@end
|
|
212
|
+
|
|
213
|
+
\@block → literal @block in output
|
|
214
|
+
\# → literal # in output
|
|
215
|
+
\${ → literal ${ in output
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Examples
|
|
221
|
+
|
|
222
|
+
The `examples/` directory contains a multi-file prompt:
|
|
223
|
+
|
|
224
|
+
| File | Purpose |
|
|
225
|
+
|------|---------|
|
|
226
|
+
| `examples/agent.pcl` | Main agent prompt — imports persona and tools |
|
|
227
|
+
| `examples/persona.pcl` | Persona blocks (`researcher`, `brief`) |
|
|
228
|
+
| `examples/tools.pcl` | Tool description blocks (`search`, `browse`, `premium_index`) |
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pcl render examples/agent.pcl \
|
|
232
|
+
--var date=2026-02-28 \
|
|
233
|
+
--var query="Explain quantum entanglement" \
|
|
234
|
+
--var premium=false
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Tests
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
uv run pytest tests/ -v
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
With coverage:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
uv run pytest tests/ --cov=pcl --cov-report=term-missing
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## VS Code Extension
|
|
254
|
+
|
|
255
|
+
Syntax highlighting is in a separate repo: [`src/pcl-vscode`](https://github.com/cybergla/pcl-vscode).
|
pcl_lang-0.1.0/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# PCL — Prompt Composition Language
|
|
2
|
+
|
|
3
|
+
PCL is a small DSL for authoring, composing, and managing LLM prompts as modular, version-controlled artifacts. A `.pcl` file compiles to a plain text string ready to use as a system prompt with any LLM API.
|
|
4
|
+
|
|
5
|
+
# Why PCL?
|
|
6
|
+
|
|
7
|
+
I came up with PCL as a way to manage prompts when they get too large and unwieldy. Most agentic applications today involve persisting prompts as a bunch of text/markdown files across disparate locations which are then read at runtime before using them for an LLM inference request. Parts of the prompt are often static (rules, persona, tools) while some can be more dynamic (user messages, context). Managing multiple sets of prompt .md files, with different versions, becomes a real engineering challenge. Template engines like Jinja alleviate this problem somewhat but I wanted something that went beyond simple variable substitution and was specifically tailored for LLM applications.
|
|
8
|
+
|
|
9
|
+
With PCL you get in-built support for templating, composition, compile time checks etc. Think of prompts that same way as you do code. You define your prompts as a modular set of text files (`.pcl` files), then have the compiler compile and weave the prompts together into a single IR template, which can be rendered at runtime with variable substitution.
|
|
10
|
+
|
|
11
|
+
pcl files are just like any other source- version controlled, with a well defined syntax.
|
|
12
|
+
|
|
13
|
+
### Author
|
|
14
|
+
|
|
15
|
+
Tanay Deshmukh (& vibe-coded with Claude)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Blocks** — define named fragments with `@block name:`, compose them with `@include`
|
|
22
|
+
- **Imports** — split prompts across files with `@import ./file.pcl [as ns]`
|
|
23
|
+
- **Variables** — `${var}` resolved at render time; `${var | default}` for fallbacks
|
|
24
|
+
- **Conditionals** — `@if variable:` / `@if not variable:` for truthiness-based branching
|
|
25
|
+
- **Raw blocks** — `@raw` / `@end` passes content through unmodified (no interpolation)
|
|
26
|
+
- **Comments** — lines starting with `#` are stripped from output
|
|
27
|
+
- **Frontmatter** — optional YAML metadata (`version`, `description`, arbitrary keys)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Requires Python 3.11+. Recommend using [uv](https://github.com/astral-sh/uv) for environment management.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone <repo>
|
|
37
|
+
cd pcl
|
|
38
|
+
uv venv
|
|
39
|
+
source .venv/bin/activate
|
|
40
|
+
uv pip install -e ".[dev]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Verify:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pcl --help
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## CLI
|
|
52
|
+
|
|
53
|
+
### `pcl compile`
|
|
54
|
+
|
|
55
|
+
Compile a `.pcl` file and dump its intermediate representation — a tree of `TEXT`, `VAR`, and `IF` segments. Useful for inspecting template structure before rendering.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pcl compile examples/agent.pcl
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Example output:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Metadata:
|
|
65
|
+
version: 1.0
|
|
66
|
+
description: Research assistant system prompt
|
|
67
|
+
|
|
68
|
+
Segments:
|
|
69
|
+
TEXT 'You are an expert research assistant...'
|
|
70
|
+
VAR ${date}
|
|
71
|
+
IF premium:
|
|
72
|
+
TEXT 'You have access to the premium document index.'
|
|
73
|
+
IF not premium:
|
|
74
|
+
TEXT 'Upgrade to unlock the premium document index.'
|
|
75
|
+
VAR ${query | no query provided}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use `-o`/`--output` to write the compiled IR to a binary `.pclc` file instead of printing it. This enables compile-once-render-many workflows without re-parsing source files.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pcl compile examples/agent.pcl -o agent.pclc
|
|
82
|
+
# Compiled to agent.pclc
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `pcl render`
|
|
86
|
+
|
|
87
|
+
Render a `.pcl` or `.pclc` file with explicit variable values.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# From source
|
|
91
|
+
pcl render examples/agent.pcl \
|
|
92
|
+
--var date=2026-02-28 \
|
|
93
|
+
--var query="What is alignment?" \
|
|
94
|
+
--var premium=true
|
|
95
|
+
|
|
96
|
+
# From pre-compiled .pclc (no re-parsing)
|
|
97
|
+
pcl render agent.pclc \
|
|
98
|
+
--var date=2026-02-28 \
|
|
99
|
+
--var query="What is alignment?" \
|
|
100
|
+
--var premium=true
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--var` accepts `key=value`. Values `true` and `false` are coerced to booleans.
|
|
104
|
+
|
|
105
|
+
### `pcl check`
|
|
106
|
+
|
|
107
|
+
Validate a `.pcl` file without producing output. Exits `0` on success, `1` on error. Prints the error message and line number on failure.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pcl check examples/agent.pcl
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `pcl watch`
|
|
114
|
+
|
|
115
|
+
Recompile whenever the file changes. Accepts the same `--var` flags as `render`.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pcl watch examples/agent.pcl \
|
|
119
|
+
--var date=2026-02-28 \
|
|
120
|
+
--var premium=false
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Python API
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from pcl import compile, render, serialize, deserialize, CompiledTemplate
|
|
129
|
+
import cbor2
|
|
130
|
+
|
|
131
|
+
# compile() — produces a CompiledTemplate (IR) with metadata and segments
|
|
132
|
+
template = compile("examples/agent.pcl")
|
|
133
|
+
print(template.metadata) # {"version": 1.0, "description": "..."}
|
|
134
|
+
print(template.segments) # [str, VarRef, Conditional, ...]
|
|
135
|
+
|
|
136
|
+
# render() — resolves variables and conditionals, returns final string
|
|
137
|
+
prompt = render("examples/agent.pcl", variables={
|
|
138
|
+
"date": "2026-02-28",
|
|
139
|
+
"query": "What is alignment?",
|
|
140
|
+
"premium": True,
|
|
141
|
+
})
|
|
142
|
+
print(prompt)
|
|
143
|
+
|
|
144
|
+
# compile once, render many — avoids re-parsing for each variable set
|
|
145
|
+
template = compile("examples/agent.pcl")
|
|
146
|
+
for query in queries:
|
|
147
|
+
prompt = render(template, variables={"date": "2026-02-28", "query": query})
|
|
148
|
+
|
|
149
|
+
# serialize to .pclc — write compiled IR to disk
|
|
150
|
+
with open("agent.pclc", "wb") as f:
|
|
151
|
+
f.write(cbor2.dumps(serialize(template)))
|
|
152
|
+
|
|
153
|
+
# deserialize from .pclc — reconstruct IR without re-parsing source
|
|
154
|
+
with open("agent.pclc", "rb") as f:
|
|
155
|
+
template = deserialize(cbor2.loads(f.read()))
|
|
156
|
+
prompt = render(template, variables={"date": "2026-02-28", "query": "..."})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Language Quick Reference
|
|
162
|
+
|
|
163
|
+
```pcl
|
|
164
|
+
---
|
|
165
|
+
version: 1.0
|
|
166
|
+
description: Optional YAML frontmatter — available as metadata, not in output
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
@import ./other.pcl
|
|
170
|
+
@import ./lib.pcl as lib
|
|
171
|
+
|
|
172
|
+
# This is a comment — stripped from output
|
|
173
|
+
|
|
174
|
+
@block intro:
|
|
175
|
+
Defined here, emitted only when @include'd.
|
|
176
|
+
|
|
177
|
+
@include intro # emit a local block
|
|
178
|
+
@include lib.greet # emit a named block from an imported file
|
|
179
|
+
@include other # emit the entire body of other.pcl
|
|
180
|
+
|
|
181
|
+
@if premium:
|
|
182
|
+
Shown only when premium is truthy.
|
|
183
|
+
|
|
184
|
+
@if not premium:
|
|
185
|
+
Shown only when premium is falsy or absent.
|
|
186
|
+
|
|
187
|
+
Hello ${name}! # required variable — error if missing
|
|
188
|
+
Hello ${name | world}! # variable with default
|
|
189
|
+
|
|
190
|
+
@raw
|
|
191
|
+
${not_interpolated} @not_a_directive # this is not a comment
|
|
192
|
+
@end
|
|
193
|
+
|
|
194
|
+
\@block → literal @block in output
|
|
195
|
+
\# → literal # in output
|
|
196
|
+
\${ → literal ${ in output
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Examples
|
|
202
|
+
|
|
203
|
+
The `examples/` directory contains a multi-file prompt:
|
|
204
|
+
|
|
205
|
+
| File | Purpose |
|
|
206
|
+
|------|---------|
|
|
207
|
+
| `examples/agent.pcl` | Main agent prompt — imports persona and tools |
|
|
208
|
+
| `examples/persona.pcl` | Persona blocks (`researcher`, `brief`) |
|
|
209
|
+
| `examples/tools.pcl` | Tool description blocks (`search`, `browse`, `premium_index`) |
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
pcl render examples/agent.pcl \
|
|
213
|
+
--var date=2026-02-28 \
|
|
214
|
+
--var query="Explain quantum entanglement" \
|
|
215
|
+
--var premium=false
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Tests
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
uv run pytest tests/ -v
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
With coverage:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
uv run pytest tests/ --cov=pcl --cov-report=term-missing
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## VS Code Extension
|
|
235
|
+
|
|
236
|
+
Syntax highlighting is in a separate repo: [`src/pcl-vscode`](https://github.com/cybergla/pcl-vscode).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1.0
|
|
3
|
+
description: Research assistant system prompt
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
@import ./persona.pcl
|
|
7
|
+
@import ./tools.pcl as tools
|
|
8
|
+
|
|
9
|
+
# Compose the agent setup from reusable fragments
|
|
10
|
+
@block agent_setup:
|
|
11
|
+
@include persona.researcher
|
|
12
|
+
@include tools.search
|
|
13
|
+
@include tools.browse
|
|
14
|
+
|
|
15
|
+
# Main prompt body
|
|
16
|
+
@include agent_setup
|
|
17
|
+
|
|
18
|
+
Today's date is ${date}.
|
|
19
|
+
|
|
20
|
+
@if premium:
|
|
21
|
+
@include tools.premium_index
|
|
22
|
+
|
|
23
|
+
@if not premium:
|
|
24
|
+
Upgrade to unlock the premium document index.
|
|
25
|
+
|
|
26
|
+
# Pass tool call format through literally
|
|
27
|
+
@raw
|
|
28
|
+
When calling a tool, respond with JSON only:
|
|
29
|
+
{"tool": "${tool_name}", "input": {"query": "..."}}
|
|
30
|
+
@end
|
|
31
|
+
|
|
32
|
+
The user's query is: ${query | no query provided}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Reusable persona fragment
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
@block researcher:
|
|
6
|
+
You are an expert research assistant with deep knowledge across science,
|
|
7
|
+
technology, and the humanities. You reason carefully, cite your uncertainty,
|
|
8
|
+
and never fabricate sources.
|
|
9
|
+
|
|
10
|
+
@block brief:
|
|
11
|
+
You are a concise research assistant. Answer directly and cite sources.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1.0
|
|
3
|
+
description: Test prompt
|
|
4
|
+
something: Other metadata
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# This comment should disappear
|
|
8
|
+
#
|
|
9
|
+
# comment
|
|
10
|
+
|
|
11
|
+
# another comment
|
|
12
|
+
@include greeting
|
|
13
|
+
@include say_hello
|
|
14
|
+
|
|
15
|
+
Something else
|
|
16
|
+
I want to say block
|
|
17
|
+
block this
|
|
18
|
+
|
|
19
|
+
Something something this will be block: Not really a block but want to say block: here
|
|
20
|
+
indent doesn't mean anything
|
|
21
|
+
|
|
22
|
+
@block greeting:
|
|
23
|
+
Hello, ${name}!
|
|
24
|
+
this is more indent
|
|
25
|
+
hjkfdj
|
|
26
|
+
slkj klds
|
|
27
|
+
jkdshfkj
|
|
28
|
+
sdfkl
|
|
29
|
+
this is some ither text should come after
|
|
30
|
+
sdfasdf
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@if premium:
|
|
34
|
+
You have premium access.
|
|
35
|
+
|
|
36
|
+
@if not premium:
|
|
37
|
+
Upgrade for more features.
|
|
38
|
+
|
|
39
|
+
@raw
|
|
40
|
+
Example JSON: {"key": "${not_interpolated}"}
|
|
41
|
+
@end
|
|
42
|
+
|
|
43
|
+
Today is ${date|2021-04-05}.
|
|
44
|
+
|
|
45
|
+
@block say_hello:
|
|
46
|
+
Hello manas!
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Tool description fragments
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
@block search:
|
|
6
|
+
You have access to a web search tool. Use it when the user asks about
|
|
7
|
+
current events or information you may not have.
|
|
8
|
+
|
|
9
|
+
@block browse:
|
|
10
|
+
You have access to a browser tool. Use it to read specific URLs the
|
|
11
|
+
user provides.
|
|
12
|
+
|
|
13
|
+
@block premium_index:
|
|
14
|
+
You have access to the premium document index, which contains
|
|
15
|
+
curated research papers and proprietary datasets.
|