xvcl 2.6.0__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.
@@ -0,0 +1,1579 @@
1
+ Metadata-Version: 2.4
2
+ Name: xvcl
3
+ Version: 2.6.0
4
+ Summary: Extended VCL compiler with metaprogramming features for Fastly VCL
5
+ Author-email: Frank Denis <github@pureftpd.org>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: compiler,fastly,preprocessor,varnish,vcl
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Compilers
18
+ Requires-Python: >=3.9
19
+ Provides-Extra: dev
20
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
21
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
22
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ <p align="center">
26
+ <img src=".media/logo.png" alt="xvcl logo" width="300">
27
+ </p>
28
+
29
+ # xvcl
30
+
31
+ Supercharge your Fastly VCL with programming constructs like loops, functions, constants, and more.
32
+
33
+ **[Quick Reference Guide](xvcl-quick-reference.md)** - One-page syntax reference for all xvcl features
34
+
35
+ ## Table of Contents
36
+
37
+ - [Introduction](#introduction)
38
+ - [Why Use xvcl?](#why-use-xvcl)
39
+ - [Installation](#installation)
40
+ - [Quick Start](#quick-start)
41
+ - [Core Features](#core-features)
42
+ - [Constants](#constants)
43
+ - [Template Expressions](#template-expressions)
44
+ - [For Loops](#for-loops)
45
+ - [Conditionals](#conditionals)
46
+ - [Variables](#variables)
47
+ - [File Includes](#file-includes)
48
+ - [Advanced Features](#advanced-features)
49
+ - [Inline Macros](#inline-macros)
50
+ - [Functions](#functions)
51
+ - [Command-Line Usage](#command-line-usage)
52
+ - [Integration with Falco](#integration-with-falco)
53
+ - [Best Practices](#best-practices)
54
+ - [Troubleshooting](#troubleshooting)
55
+
56
+ ## Introduction
57
+
58
+ xvcl is a VCL transpiler that extends Fastly VCL with programming constructs that generate standard VCL code.
59
+
60
+ Think of it as a build step for your VCL: write enhanced VCL source files, run xvcl, and get clean, valid VCL output.
61
+
62
+ > **Tip:** For a quick syntax reference, see the [xvcl Quick Reference Guide](xvcl-quick-reference.md).
63
+
64
+ **What you can do:**
65
+
66
+ - Define constants once, use them everywhere
67
+ - Generate repetitive code with for loops
68
+ - Create reusable functions with return values
69
+ - Build zero-overhead macros for common patterns
70
+ - Conditionally compile code for different environments
71
+ - Split large VCL files into modular includes
72
+
73
+ **What you get:**
74
+
75
+ - Standard VCL output that works with Fastly
76
+ - Compile-time safety and error checking
77
+ - Reduced code duplication
78
+ - Better maintainability
79
+
80
+ ## Why Use xvcl?
81
+
82
+ VCL is powerful but limited by design. You can't define functions with return values, you can't use loops, and managing constants means find-and-replace. This leads to:
83
+
84
+ - **Copy-paste errors:** Similar backends? Copy, paste, modify, repeat, make mistakes
85
+ - **Magic numbers:** Hardcoded values scattered throughout your code
86
+ - **Duplication:** Same logic repeated in multiple subroutines
87
+ - **Poor maintainability:** Change one thing, update it in 20 places
88
+
89
+ xvcl solves these problems by adding programming constructs that compile down to clean VCL.
90
+
91
+ ### Real-World Example
92
+
93
+ **Without xvcl (manual, error-prone):**
94
+
95
+ ```vcl
96
+ backend web1 {
97
+ .host = "web1.example.com";
98
+ .port = "80";
99
+ }
100
+
101
+ backend web2 {
102
+ .host = "web2.example.com";
103
+ .port = "80";
104
+ }
105
+
106
+ backend web3 {
107
+ .host = "web3.example.com";
108
+ .port = "80";
109
+ }
110
+
111
+ sub vcl_recv {
112
+ if (req.http.Host == "web1.example.com") {
113
+ set req.backend = web1;
114
+ }
115
+ if (req.http.Host == "web2.example.com") {
116
+ set req.backend = web2;
117
+ }
118
+ if (req.http.Host == "web3.example.com") {
119
+ set req.backend = web3;
120
+ }
121
+ }
122
+ ```
123
+
124
+ **With xvcl (clean, maintainable):**
125
+
126
+ ```vcl
127
+ #const BACKENDS = ["web1", "web2", "web3"]
128
+
129
+ #for backend in BACKENDS
130
+ backend {{backend}} {
131
+ .host = "{{backend}}.example.com";
132
+ .port = "80";
133
+ }
134
+ #endfor
135
+
136
+ sub vcl_recv {
137
+ #for backend in BACKENDS
138
+ if (req.http.Host == "{{backend}}.example.com") {
139
+ set req.backend = {{backend}};
140
+ }
141
+ #endfor
142
+ }
143
+ ```
144
+
145
+ Adding a new backend? Just update the list. xvcl generates all the code.
146
+
147
+ ## Installation
148
+
149
+ xvcl is a Python package. You need Python 3.9 or later.
150
+
151
+ ```bash
152
+ # Using pip
153
+ pip install xvcl
154
+
155
+ # Or install from source
156
+ pip install .
157
+
158
+ # Development installation with dev dependencies
159
+ pip install -e ".[dev]"
160
+
161
+ # Using uv (recommended for faster installation)
162
+ uv pip install xvcl
163
+ ```
164
+
165
+ After installation, the `xvcl` command is available globally.
166
+
167
+ **No external dependencies** — uses only the Python standard library.
168
+
169
+ ### Running without installation
170
+
171
+ You can run xvcl without installing it using `uvx`:
172
+
173
+ ```bash
174
+ # Run xvcl directly (uv will handle dependencies automatically)
175
+ uvx xvcl input.xvcl -o output.vcl
176
+
177
+ # With include paths
178
+ uvx xvcl input.xvcl -o output.vcl -I ./includes
179
+
180
+ # With debug mode
181
+ uvx xvcl input.xvcl -o output.vcl --debug
182
+ ```
183
+
184
+ This is perfect for CI/CD pipelines or one-off usage without polluting your environment.
185
+
186
+ ## Quick Start
187
+
188
+ Create an xvcl source file (use the `.xvcl` extension by convention):
189
+
190
+ **`hello.xvcl`:**
191
+
192
+ ```vcl
193
+ #const MESSAGE = "Hello from xvcl!"
194
+
195
+ sub vcl_recv {
196
+ set req.http.X-Message = "{{MESSAGE}}";
197
+ }
198
+ ```
199
+
200
+ Run xvcl:
201
+
202
+ ```bash
203
+ # If installed
204
+ xvcl hello.xvcl -o hello.vcl
205
+
206
+ # Or run without installing using uvx
207
+ uvx xvcl hello.xvcl -o hello.vcl
208
+ ```
209
+
210
+ Output **`hello.vcl`:**
211
+
212
+ ```vcl
213
+ sub vcl_recv {
214
+ set req.http.X-Message = "Hello from xvcl!";
215
+ }
216
+ ```
217
+
218
+ Validate with Falco:
219
+
220
+ ```bash
221
+ falco lint hello.vcl
222
+ ```
223
+
224
+ ## Core Features
225
+
226
+ ### Constants
227
+
228
+ Define named constants with type checking. Constants are evaluated at preprocessing time and substituted into your code.
229
+
230
+ **Syntax:**
231
+
232
+ ```vcl
233
+ #const NAME TYPE = value
234
+ ```
235
+
236
+ **Supported types:**
237
+ - `INTEGER` - Whole numbers
238
+ - `STRING` - Text strings
239
+ - `FLOAT` - Decimal numbers
240
+ - `BOOL` - True/False
241
+
242
+ **Example:**
243
+
244
+ ```vcl
245
+ #const MAX_AGE INTEGER = 3600
246
+ #const ORIGIN STRING = "origin.example.com"
247
+ #const PRODUCTION BOOL = True
248
+ #const CACHE_VERSION FLOAT = 1.5
249
+ ```
250
+
251
+ **Using constants in templates:**
252
+
253
+ ```vcl
254
+ #const TTL = 300
255
+ #const BACKEND_HOST = "api.example.com"
256
+
257
+ backend F_api {
258
+ .host = "{{BACKEND_HOST}}";
259
+ .port = "443";
260
+ }
261
+
262
+ sub vcl_fetch {
263
+ set beresp.ttl = {{TTL}}s;
264
+ }
265
+ ```
266
+
267
+ **Why use constants?**
268
+
269
+ - Single source of truth for configuration values
270
+ - Easy to update across entire VCL
271
+ - Type safety prevents mistakes
272
+ - Self-documenting code
273
+
274
+ ### Template Expressions
275
+
276
+ Embed Python expressions in your VCL using `{{expression}}` syntax. Expressions are evaluated at preprocessing time.
277
+
278
+ **Example:**
279
+
280
+ ```vcl
281
+ #const PORT = 8080
282
+
283
+ sub vcl_recv {
284
+ set req.http.X-Port = "{{PORT}}";
285
+ set req.http.X-Double = "{{PORT * 2}}";
286
+ set req.http.X-Hex = "{{hex(PORT)}}";
287
+ }
288
+ ```
289
+
290
+ **Output:**
291
+
292
+ ```vcl
293
+ sub vcl_recv {
294
+ set req.http.X-Port = "8080";
295
+ set req.http.X-Double = "16160";
296
+ set req.http.X-Hex = "0x1f90";
297
+ }
298
+ ```
299
+
300
+ **Available functions:**
301
+
302
+ - `range(n)` - Generate number sequences
303
+ - `len(list)` - Get list length
304
+ - `str(x)`, `int(x)` - Type conversions
305
+ - `hex(n)` - Hexadecimal conversion
306
+ - `format(x, fmt)` - Format values
307
+ - `enumerate(iterable)` - Enumerate with indices
308
+ - `min(...)`, `max(...)` - Min/max values
309
+ - `abs(n)` - Absolute value
310
+
311
+ **String formatting:**
312
+
313
+ ```vcl
314
+ #const REGION = "us-east"
315
+ #const INDEX = 1
316
+
317
+ set req.backend = F_backend_{{REGION}}_{{INDEX}};
318
+ ```
319
+
320
+ ### For Loops
321
+
322
+ Generate repetitive VCL code by iterating over ranges or lists.
323
+
324
+ **Syntax:**
325
+
326
+ ```vcl
327
+ #for variable in iterable
328
+ // Code to repeat
329
+ #endfor
330
+
331
+ #for var1, var2 in iterable
332
+ // Tuple unpacking
333
+ #endfor
334
+ ```
335
+
336
+ **Example 1: Range-based loop**
337
+
338
+ ```vcl
339
+ #for i in range(5)
340
+ backend web{{i}} {
341
+ .host = "web{{i}}.example.com";
342
+ .port = "80";
343
+ }
344
+ #endfor
345
+ ```
346
+
347
+ **Output:**
348
+
349
+ ```vcl
350
+ backend web0 {
351
+ .host = "web0.example.com";
352
+ .port = "80";
353
+ }
354
+ backend web1 {
355
+ .host = "web1.example.com";
356
+ .port = "80";
357
+ }
358
+ // ... continues through web4
359
+ ```
360
+
361
+ **Example 2: List iteration**
362
+
363
+ ```vcl
364
+ #const REGIONS = ["us-east", "us-west", "eu-west"]
365
+
366
+ #for region in REGIONS
367
+ backend F_{{region}} {
368
+ .host = "{{region}}.example.com";
369
+ .port = "443";
370
+ .ssl = true;
371
+ }
372
+ #endfor
373
+ ```
374
+
375
+ **Example 3: Nested loops**
376
+
377
+ ```vcl
378
+ #const REGIONS = ["us", "eu"]
379
+ #const ENVS = ["prod", "staging"]
380
+
381
+ #for region in REGIONS
382
+ #for env in ENVS
383
+ backend {{region}}_{{env}} {
384
+ .host = "{{env}}.{{region}}.example.com";
385
+ .port = "443";
386
+ }
387
+ #endfor
388
+ #endfor
389
+ ```
390
+
391
+ **Example 4: Tuple unpacking**
392
+
393
+ ```vcl
394
+ #const BACKENDS = [("web", 8080), ("api", 9000), ("admin", 9001)]
395
+
396
+ #for name, port in BACKENDS
397
+ backend F_{{name}} {
398
+ .host = "{{name}}.example.com";
399
+ .port = "{{port}}";
400
+ }
401
+ #endfor
402
+ ```
403
+
404
+ **Example 5: With enumerate**
405
+
406
+ ```vcl
407
+ #const REGIONS = ["us-east", "us-west", "eu-west"]
408
+
409
+ #for idx, region in enumerate(REGIONS)
410
+ set req.http.X-Region-{{idx}} = "{{region}}";
411
+ #endfor
412
+ ```
413
+
414
+ **Why use for loops?**
415
+
416
+ - Generate multiple similar backends
417
+ - Create ACL entries from lists
418
+ - Build routing logic programmatically
419
+ - Reduce copy-paste errors
420
+
421
+ ### Conditionals
422
+
423
+ Conditionally include or exclude code based on compile-time conditions.
424
+
425
+ **Syntax:**
426
+
427
+ ```vcl
428
+ #if condition
429
+ // Code when true
430
+ #else
431
+ // Code when false (optional)
432
+ #endif
433
+ ```
434
+
435
+ **Example 1: Environment-specific configuration**
436
+
437
+ ```vcl
438
+ #const PRODUCTION = True
439
+ #const DEBUG = False
440
+
441
+ sub vcl_recv {
442
+ #if PRODUCTION
443
+ set req.http.X-Environment = "production";
444
+ unset req.http.X-Debug-Info;
445
+ #else
446
+ set req.http.X-Environment = "development";
447
+ set req.http.X-Debug-Info = "Enabled";
448
+ #endif
449
+
450
+ #if DEBUG
451
+ set req.http.X-Request-ID = randomstr(16, "0123456789abcdef");
452
+ #endif
453
+ }
454
+ ```
455
+
456
+ **Example 2: Feature flags**
457
+
458
+ ```vcl
459
+ #const ENABLE_NEW_ROUTING = True
460
+ #const ENABLE_RATE_LIMITING = False
461
+
462
+ sub vcl_recv {
463
+ #if ENABLE_NEW_ROUTING
464
+ call new_routing_logic;
465
+ #else
466
+ call legacy_routing_logic;
467
+ #endif
468
+
469
+ #if ENABLE_RATE_LIMITING
470
+ if (ratelimit.check_rate("client_" + client.ip, 1, 100, 60s, 1000s)) {
471
+ error 429 "Too Many Requests";
472
+ }
473
+ #endif
474
+ }
475
+ ```
476
+
477
+ **Why use conditionals?**
478
+
479
+ - Single codebase for multiple environments
480
+ - Easy feature flag management
481
+ - Dead code elimination (code in false branches isn't generated)
482
+ - Compile-time optimization
483
+
484
+ ### Variables
485
+
486
+ Declare and initialize local variables in one step.
487
+
488
+ **Syntax:**
489
+
490
+ ```vcl
491
+ #let name TYPE = expression;
492
+ ```
493
+
494
+ **Example:**
495
+
496
+ ```vcl
497
+ sub vcl_recv {
498
+ #let timestamp STRING = std.time(now, now);
499
+ #let cache_key STRING = req.url.path + req.http.Host;
500
+
501
+ set req.http.X-Timestamp = var.timestamp;
502
+ set req.hash = var.cache_key;
503
+ }
504
+ ```
505
+
506
+ **Expands to:**
507
+
508
+ ```vcl
509
+ sub vcl_recv {
510
+ declare local var.timestamp STRING;
511
+ set var.timestamp = std.time(now, now);
512
+ declare local var.cache_key STRING;
513
+ set var.cache_key = req.url.path + req.http.Host;
514
+
515
+ set req.http.X-Timestamp = var.timestamp;
516
+ set req.hash = var.cache_key;
517
+ }
518
+ ```
519
+
520
+ **Why use #let?**
521
+
522
+ - Shorter syntax than separate declare + set
523
+ - Clear initialization point
524
+ - Reduces boilerplate
525
+
526
+ ### File Includes
527
+
528
+ Split large VCL files into modular, reusable components.
529
+
530
+ **Syntax:**
531
+
532
+ ```vcl
533
+ #include "path/to/file.xvcl"
534
+ ```
535
+
536
+ **Example project structure:**
537
+
538
+ ```
539
+ vcl/
540
+ ├── main.xvcl
541
+ ├── includes/
542
+ │ ├── backends.xvcl
543
+ │ ├── security.xvcl
544
+ │ └── routing.xvcl
545
+ ```
546
+
547
+ **`main.xvcl`:**
548
+
549
+ ```vcl
550
+ #include "includes/backends.xvcl"
551
+ #include "includes/security.xvcl"
552
+ #include "includes/routing.xvcl"
553
+
554
+ sub vcl_recv {
555
+ call security_checks;
556
+ call routing_logic;
557
+ }
558
+ ```
559
+
560
+ **`includes/backends.xvcl`:**
561
+
562
+ ```vcl
563
+ #const BACKENDS = ["web1", "web2", "web3"]
564
+
565
+ #for backend in BACKENDS
566
+ backend F_{{backend}} {
567
+ .host = "{{backend}}.example.com";
568
+ .port = "443";
569
+ }
570
+ #endfor
571
+ ```
572
+
573
+ **Include path resolution:**
574
+
575
+ 1. Relative to the current file
576
+ 2. Relative to include paths specified with `-I`
577
+
578
+ **Run with include paths:**
579
+
580
+ ```bash
581
+ xvcl main.xvcl -o main.vcl -I ./vcl/includes
582
+ ```
583
+
584
+ **Features:**
585
+
586
+ - **Include-once semantics:** Files are only included once even if referenced multiple times
587
+ - **Cycle detection:** Prevents circular includes
588
+ - **Shared constants:** Constants defined in included files are available to the parent
589
+
590
+ **Why use includes?**
591
+
592
+ - Organize large VCL projects
593
+ - Share common configurations across multiple VCL files
594
+ - Team collaboration (different files for different concerns)
595
+ - Reusable components library
596
+
597
+ ## Advanced Features
598
+
599
+ ### Inline Macros
600
+
601
+ Create zero-overhead text substitution macros. Unlike functions, macros are expanded inline at compile time with no runtime cost.
602
+
603
+ **Syntax:**
604
+
605
+ ```vcl
606
+ #inline macro_name(param1, param2, ...)
607
+ expression
608
+ #endinline
609
+ ```
610
+
611
+ **Example 1: String concatenation**
612
+
613
+ ```vcl
614
+ #inline add_prefix(s)
615
+ "prefix-" + s
616
+ #endinline
617
+
618
+ #inline add_suffix(s)
619
+ s + "-suffix"
620
+ #endinline
621
+
622
+ sub vcl_recv {
623
+ set req.http.X-Modified = add_prefix("test");
624
+ set req.http.X-Both = add_prefix(add_suffix("middle"));
625
+ }
626
+ ```
627
+
628
+ **Output:**
629
+
630
+ ```vcl
631
+ sub vcl_recv {
632
+ set req.http.X-Modified = "prefix-" + "test";
633
+ set req.http.X-Both = "prefix-" + "middle" + "-suffix";
634
+ }
635
+ ```
636
+
637
+ **Example 2: Common patterns**
638
+
639
+ ```vcl
640
+ #inline normalize_host(host)
641
+ std.tolower(regsub(host, "^www\.", ""))
642
+ #endinline
643
+
644
+ #inline cache_key(url, host)
645
+ digest.hash_md5(url + "|" + host)
646
+ #endinline
647
+
648
+ sub vcl_recv {
649
+ set req.http.X-Normalized = normalize_host(req.http.Host);
650
+ set req.hash = cache_key(req.url, req.http.Host);
651
+ }
652
+ ```
653
+
654
+ **Output:**
655
+
656
+ ```vcl
657
+ sub vcl_recv {
658
+ set req.http.X-Normalized = std.tolower(regsub(req.http.Host, "^www\.", ""));
659
+ set req.hash = digest.hash_md5(req.url + "|" + req.http.Host);
660
+ }
661
+ ```
662
+
663
+ **Example 3: Operator precedence handling**
664
+
665
+ xvcl automatically handles operator precedence:
666
+
667
+ ```vcl
668
+ #inline double(x)
669
+ x + x
670
+ #endinline
671
+
672
+ sub vcl_recv {
673
+ declare local var.result INTEGER;
674
+ set var.result = double(5) * 10; // Correctly expands to (5 + 5) * 10
675
+ }
676
+ ```
677
+
678
+ **Macros vs Functions:**
679
+
680
+ | Feature | Macros | Functions |
681
+ | ------------- | ------------------- | ----------------------------- |
682
+ | Expansion | Compile-time inline | Runtime subroutine call |
683
+ | Overhead | None | Subroutine call + global vars |
684
+ | Return values | Expression only | Single or tuple |
685
+ | Use case | Simple expressions | Complex logic |
686
+
687
+ **When to use macros:**
688
+
689
+ - String manipulation patterns
690
+ - Simple calculations
691
+ - Common expressions repeated throughout code
692
+ - When you need zero runtime overhead
693
+
694
+ **When to use functions:**
695
+
696
+ - Complex logic with multiple statements
697
+ - Need to return multiple values
698
+ - Conditional logic or loops inside the reusable code
699
+
700
+ ### Functions
701
+
702
+ Define reusable functions with parameters and return values. Functions are compiled into VCL subroutines.
703
+
704
+ **Syntax:**
705
+
706
+ ```vcl
707
+ #def function_name(param1 TYPE, param2 TYPE, ...) -> RETURN_TYPE
708
+ // Function body
709
+ return value;
710
+ #enddef
711
+ ```
712
+
713
+ **Example 1: Simple function**
714
+
715
+ ```vcl
716
+ #def add(a INTEGER, b INTEGER) -> INTEGER
717
+ declare local var.sum INTEGER;
718
+ set var.sum = a + b;
719
+ return var.sum;
720
+ #enddef
721
+
722
+ sub vcl_recv {
723
+ declare local var.result INTEGER;
724
+ set var.result = add(5, 10);
725
+ set req.http.X-Sum = var.result;
726
+ }
727
+ ```
728
+
729
+ **Example 2: String processing**
730
+
731
+ ```vcl
732
+ #def normalize_path(path STRING) -> STRING
733
+ declare local var.result STRING;
734
+ set var.result = std.tolower(path);
735
+ set var.result = regsub(var.result, "/$", "");
736
+ return var.result;
737
+ #enddef
738
+
739
+ sub vcl_recv {
740
+ declare local var.clean_path STRING;
741
+ set var.clean_path = normalize_path(req.url.path);
742
+ set req.url = var.clean_path;
743
+ }
744
+ ```
745
+
746
+ **Example 3: Functions with conditionals**
747
+
748
+ ```vcl
749
+ #def should_cache(url STRING) -> BOOL
750
+ declare local var.cacheable BOOL;
751
+
752
+ if (url ~ "^/api/") {
753
+ set var.cacheable = false;
754
+ } else if (url ~ "\.(jpg|png|css|js)$") {
755
+ set var.cacheable = true;
756
+ } else {
757
+ set var.cacheable = false;
758
+ }
759
+
760
+ return var.cacheable;
761
+ #enddef
762
+
763
+ sub vcl_recv {
764
+ declare local var.can_cache BOOL;
765
+ set var.can_cache = should_cache(req.url.path);
766
+
767
+ if (var.can_cache) {
768
+ return(lookup);
769
+ } else {
770
+ return(pass);
771
+ }
772
+ }
773
+ ```
774
+
775
+ **Example 4: Tuple returns (multiple values)**
776
+
777
+ ```vcl
778
+ #def parse_user_agent(ua STRING) -> (STRING, STRING)
779
+ declare local var.browser STRING;
780
+ declare local var.os STRING;
781
+
782
+ if (ua ~ "Chrome") {
783
+ set var.browser = "chrome";
784
+ } else if (ua ~ "Firefox") {
785
+ set var.browser = "firefox";
786
+ } else {
787
+ set var.browser = "other";
788
+ }
789
+
790
+ if (ua ~ "Windows") {
791
+ set var.os = "windows";
792
+ } else if (ua ~ "Mac") {
793
+ set var.os = "macos";
794
+ } else {
795
+ set var.os = "other";
796
+ }
797
+
798
+ return var.browser, var.os;
799
+ #enddef
800
+
801
+ sub vcl_recv {
802
+ declare local var.browser STRING;
803
+ declare local var.os STRING;
804
+
805
+ set var.browser, var.os = parse_user_agent(req.http.User-Agent);
806
+
807
+ set req.http.X-Browser = var.browser;
808
+ set req.http.X-OS = var.os;
809
+ }
810
+ ```
811
+
812
+ **Behind the scenes:**
813
+
814
+ Functions are compiled into VCL subroutines using global headers for parameter passing:
815
+
816
+ ```vcl
817
+ // Your code:
818
+ set var.result = add(5, 10);
819
+
820
+ // Becomes:
821
+ set req.http.X-Func-add-a = std.itoa(5);
822
+ set req.http.X-Func-add-b = std.itoa(10);
823
+ call add;
824
+ set var.result = std.atoi(req.http.X-Func-add-Return);
825
+ ```
826
+
827
+ xvcl generates the `sub add { ... }` implementation and handles all type conversions automatically.
828
+
829
+ **Function features:**
830
+
831
+ - **Type safety:** Parameters and returns are type-checked
832
+ - **Multiple returns:** Use tuple syntax to return multiple values
833
+ - **Automatic conversions:** INTEGER/FLOAT/BOOL are converted to/from STRING automatically
834
+ - **Scope annotations:** Generated subroutines work in all VCL scopes
835
+
836
+ **Why use functions?**
837
+
838
+ - Reusable complex logic
839
+ - Reduce code duplication
840
+ - Easier testing (test the function once)
841
+ - Better code organization
842
+
843
+ ## Command-Line Usage
844
+
845
+ **Basic usage:**
846
+
847
+ ```bash
848
+ xvcl input.xvcl -o output.vcl
849
+ ```
850
+
851
+ **Options:**
852
+
853
+ | Option | Description |
854
+ | --------------- | --------------------------------------------------- |
855
+ | `input` | Input xvcl source file (required) |
856
+ | `-o, --output` | Output VCL file (default: replaces .xvcl with .vcl) |
857
+ | `-I, --include` | Add an include search path (repeatable) |
858
+ | `--debug` | Enable debug output with expansion traces |
859
+ | `--source-maps` | Add source map comments to output |
860
+ | `-v, --verbose` | Verbose output (alias for --debug) |
861
+
862
+ **Examples:**
863
+
864
+ ```bash
865
+ # Basic compilation
866
+ xvcl main.xvcl -o main.vcl
867
+
868
+ # With include paths
869
+ xvcl main.xvcl -o main.vcl \
870
+ -I ./includes \
871
+ -I ./shared
872
+
873
+ # Debug mode (see expansion traces)
874
+ xvcl main.xvcl -o main.vcl --debug
875
+
876
+ # With source maps (track generated code origin)
877
+ xvcl main.xvcl -o main.vcl --source-maps
878
+ ```
879
+
880
+ **Automatic output naming:**
881
+
882
+ If you don't specify `-o`, xvcl replaces `.xvcl` with `.vcl`:
883
+
884
+ ```bash
885
+ # These are equivalent:
886
+ xvcl main.xvcl
887
+ xvcl main.xvcl -o main.vcl
888
+ ```
889
+
890
+ **Debug mode output:**
891
+
892
+ ```bash
893
+ $ xvcl example.xvcl --debug
894
+
895
+ [DEBUG] Processing file: example.xvcl
896
+ [DEBUG] Pass 1: Extracting constants
897
+ [DEBUG] Defined constant: MAX_AGE = 3600
898
+ [DEBUG] Pass 2: Processing includes
899
+ [DEBUG] Pass 3: Extracting inline macros
900
+ [DEBUG] Defined macro: add_prefix(s)
901
+ [DEBUG] Pass 4: Extracting functions
902
+ [DEBUG] Pass 5: Processing directives and generating code
903
+ [DEBUG] Processing #for at line 10
904
+ [DEBUG] Loop iterating 3 times
905
+ [DEBUG] Iteration 0: backend = web1
906
+ [DEBUG] Iteration 1: backend = web2
907
+ [DEBUG] Iteration 2: backend = web3
908
+ [DEBUG] Pass 6: Generating function subroutines
909
+ ✓ Compiled example.xvcl -> example.vcl
910
+ Constants: 1
911
+ Macros: 1 (add_prefix)
912
+ Functions: 0
913
+ ```
914
+
915
+ ## Integration with Falco
916
+
917
+ xvcl generates standard VCL that you can use with Falco's full toolset.
918
+
919
+ **Recommended workflow:**
920
+
921
+ ```bash
922
+ # 1. Write your xvcl source
923
+ vim main.xvcl
924
+
925
+ # 2. Compile with xvcl
926
+ xvcl main.xvcl -o main.vcl
927
+
928
+ # 3. Lint with Falco
929
+ falco lint main.vcl
930
+
931
+ # 4. Test with Falco
932
+ falco test main.vcl
933
+
934
+ # 5. Simulate with Falco
935
+ falco simulate main.vcl
936
+ ```
937
+
938
+ **Makefile integration:**
939
+
940
+ ```makefile
941
+ # Makefile
942
+ .PHONY: build lint test clean
943
+
944
+ XVCL = xvcl
945
+ SOURCES = $(wildcard *.xvcl)
946
+ OUTPUTS = $(SOURCES:.xvcl=.vcl)
947
+
948
+ build: $(OUTPUTS)
949
+
950
+ %.vcl: %.xvcl
951
+ $(XVCL) $< -o $@ -I ./includes
952
+
953
+ lint: build
954
+ falco lint *.vcl
955
+
956
+ test: build
957
+ falco test *.vcl
958
+
959
+ clean:
960
+ rm -f $(OUTPUTS)
961
+ ```
962
+
963
+ **CI/CD integration:**
964
+
965
+ ```yaml
966
+ # .github/workflows/vcl.yml
967
+ name: VCL CI
968
+
969
+ on: [push, pull_request]
970
+
971
+ jobs:
972
+ test:
973
+ runs-on: ubuntu-latest
974
+ steps:
975
+ - uses: actions/checkout@v3
976
+
977
+ - name: Install uv
978
+ uses: astral-sh/setup-uv@v2
979
+
980
+ - name: Install Falco
981
+ run: |
982
+ wget https://github.com/ysugimoto/falco/releases/latest/download/falco_linux_amd64
983
+ chmod +x falco_linux_amd64
984
+ sudo mv falco_linux_amd64 /usr/local/bin/falco
985
+
986
+ - name: Compile xvcl
987
+ run: |
988
+ uvx xvcl main.xvcl -o main.vcl
989
+
990
+ - name: Lint VCL
991
+ run: falco lint main.vcl
992
+
993
+ - name: Test VCL
994
+ run: falco test main.vcl
995
+ ```
996
+
997
+ Using `uvx` eliminates the need to set up Python and install xvcl separately — it's handled automatically.
998
+
999
+ **Testing compiled VCL:**
1000
+
1001
+ You can write Falco tests for your generated VCL:
1002
+
1003
+ **`main.test.vcl`:**
1004
+
1005
+ ```vcl
1006
+ // @suite: Backend routing tests
1007
+
1008
+ // @test: Should route to correct backend
1009
+ sub test_backend_routing {
1010
+ set req.http.Host = "web1.example.com";
1011
+ call vcl_recv;
1012
+
1013
+ assert.equal(req.backend, "web1");
1014
+ }
1015
+ ```
1016
+
1017
+ Run tests after compilation:
1018
+
1019
+ ```bash
1020
+ xvcl main.xvcl -o main.vcl
1021
+ falco test main.vcl
1022
+ ```
1023
+
1024
+ ## Best Practices
1025
+
1026
+ ### 1. Use the `.xvcl` extension
1027
+
1028
+ Makes it clear which files are xvcl source files:
1029
+
1030
+ ```
1031
+ ✓ main.xvcl → main.vcl
1032
+ ✗ main.vcl → main.vcl.processed
1033
+ ```
1034
+
1035
+ ### 2. Keep constants at the top
1036
+
1037
+ ```vcl
1038
+ // Good: Constants first, easy to find
1039
+ #const MAX_BACKENDS = 10
1040
+ #const PRODUCTION = True
1041
+
1042
+ #for i in range(MAX_BACKENDS)
1043
+ // ... use constant
1044
+ #endfor
1045
+ ```
1046
+
1047
+ ### 3. Use descriptive constant names
1048
+
1049
+ ```vcl
1050
+ // Good
1051
+ #const CACHE_TTL_SECONDS = 3600
1052
+ #const API_BACKEND_HOST = "api.example.com"
1053
+
1054
+ // Bad
1055
+ #const X = 3600
1056
+ #const B = "api.example.com"
1057
+ ```
1058
+
1059
+ ### 4. Comment your macros and functions
1060
+
1061
+ ```vcl
1062
+ // Normalizes a hostname by removing www prefix and converting to lowercase
1063
+ #inline normalize_host(host)
1064
+ std.tolower(regsub(host, "^www\.", ""))
1065
+ #endinline
1066
+
1067
+ // Parses User-Agent and returns (browser, os) tuple
1068
+ #def parse_user_agent(ua STRING) -> (STRING, STRING)
1069
+ // ...
1070
+ #enddef
1071
+ ```
1072
+
1073
+ ### 5. Prefer macros for simple expressions, functions for complex logic
1074
+
1075
+ ```vcl
1076
+ // Good: Simple expression = macro
1077
+ #inline cache_key(url, host)
1078
+ digest.hash_md5(url + "|" + host)
1079
+ #endinline
1080
+
1081
+ // Good: Complex logic = function
1082
+ #def should_cache(url STRING, method STRING) -> BOOL
1083
+ declare local var.result BOOL;
1084
+ if (method != "GET" && method != "HEAD") {
1085
+ set var.result = false;
1086
+ } else if (url ~ "^/api/") {
1087
+ set var.result = false;
1088
+ } else {
1089
+ set var.result = true;
1090
+ }
1091
+ return var.result;
1092
+ #enddef
1093
+ ```
1094
+
1095
+ ### 6. Use includes for organization
1096
+
1097
+ ```
1098
+ vcl/
1099
+ ├── main.xvcl # Main entry point
1100
+ ├── config.xvcl # Constants and configuration
1101
+ ├── includes/
1102
+ │ ├── backends.xvcl
1103
+ │ ├── security.xvcl
1104
+ │ ├── routing.xvcl
1105
+ │ └── caching.xvcl
1106
+ ```
1107
+
1108
+ ### 7. Version control both source and output
1109
+
1110
+ ```gitignore
1111
+ # Include both in git
1112
+ *.xvcl
1113
+ *.vcl
1114
+
1115
+ # But gitignore generated files in CI
1116
+ # (if you regenerate on deploy)
1117
+ ```
1118
+
1119
+ **Or** only version control source files and regenerate on deployment:
1120
+
1121
+ ```gitignore
1122
+ # Version control xvcl source only
1123
+ *.xvcl
1124
+
1125
+ # Ignore generated VCL
1126
+ *.vcl
1127
+ ```
1128
+
1129
+ Choose based on your deployment process.
1130
+
1131
+ ### 8. Add source maps in development
1132
+
1133
+ ```bash
1134
+ # Development: easier debugging
1135
+ xvcl main.xvcl -o main.vcl --source-maps
1136
+
1137
+ # Production: cleaner output
1138
+ xvcl main.xvcl -o main.vcl
1139
+ ```
1140
+
1141
+ Source maps add comments like:
1142
+
1143
+ ```vcl
1144
+ // BEGIN INCLUDE: includes/backends.xvcl
1145
+ backend F_web1 { ... }
1146
+ // END INCLUDE: includes/backends.xvcl
1147
+ ```
1148
+
1149
+ ### 9. Test incrementally
1150
+
1151
+ Don't write a massive source file and compile once. Test as you go:
1152
+
1153
+ ```bash
1154
+ # Write a bit
1155
+ vim main.xvcl
1156
+
1157
+ # Compile
1158
+ xvcl main.xvcl
1159
+
1160
+ # Check output
1161
+ cat main.vcl
1162
+
1163
+ # Lint
1164
+ falco lint main.vcl
1165
+
1166
+ # Repeat
1167
+ ```
1168
+
1169
+ ### 10. Use debug mode when things go wrong
1170
+
1171
+ ```bash
1172
+ xvcl main.xvcl --debug
1173
+ ```
1174
+
1175
+ Shows exactly what xvcl is doing.
1176
+
1177
+ ## Troubleshooting
1178
+
1179
+ ### Error: "Name 'X' is not defined"
1180
+
1181
+ **Problem:** You're using a variable or constant that doesn't exist.
1182
+
1183
+ ```vcl
1184
+ #const PORT = 8080
1185
+
1186
+ set req.http.X-Value = "{{PROT}}"; // Typo!
1187
+ ```
1188
+
1189
+ **Error:**
1190
+
1191
+ ```
1192
+ Error at main.xvcl:3:
1193
+ Name 'PROT' is not defined
1194
+ Did you mean: PORT?
1195
+ ```
1196
+
1197
+ **Solution:** Check spelling. xvcl suggests similar names.
1198
+
1199
+ ### Error: "Invalid #const syntax"
1200
+
1201
+ **Problem:** Malformed constant declaration.
1202
+
1203
+ ```vcl
1204
+ #const PORT 8080 // Missing = sign
1205
+ #const = 8080 // Missing name
1206
+ #const PORT = STRING // Missing value
1207
+ ```
1208
+
1209
+ **Solution:** Use correct syntax:
1210
+
1211
+ ```vcl
1212
+ #const PORT INTEGER = 8080
1213
+ ```
1214
+
1215
+ ### Error: "No matching #endfor for #for"
1216
+
1217
+ **Problem:** Missing closing keyword.
1218
+
1219
+ ```vcl
1220
+ #for i in range(10)
1221
+ backend web{{i}} { ... }
1222
+ // Missing #endfor
1223
+ ```
1224
+
1225
+ **Solution:** Add the closing keyword:
1226
+
1227
+ ```vcl
1228
+ #for i in range(10)
1229
+ backend web{{i}} { ... }
1230
+ #endfor
1231
+ ```
1232
+
1233
+ ### Error: "Circular include detected"
1234
+
1235
+ **Problem:** File A includes file B which includes file A.
1236
+
1237
+ ```
1238
+ main.xvcl includes util.xvcl
1239
+ util.xvcl includes main.xvcl
1240
+ ```
1241
+
1242
+ **Solution:** Restructure your includes. Create a shared file:
1243
+
1244
+ ```
1245
+ main.xvcl includes shared.xvcl
1246
+ util.xvcl includes shared.xvcl
1247
+ ```
1248
+
1249
+ ### Error: "Cannot find included file"
1250
+
1251
+ **Problem:** Include path is wrong or file doesn't exist.
1252
+
1253
+ ```vcl
1254
+ #include "includes/backends.xvcl"
1255
+ ```
1256
+
1257
+ **Solution:** Check path and use `-I` flag:
1258
+
1259
+ ```bash
1260
+ xvcl main.xvcl -o main.vcl -I ./includes
1261
+ ```
1262
+
1263
+ ### Generated VCL has syntax errors
1264
+
1265
+ **Problem:** xvcl generated invalid VCL.
1266
+
1267
+ **Solution:**
1268
+
1269
+ 1. Check the generated output:
1270
+ ```bash
1271
+ cat main.vcl
1272
+ ```
1273
+
1274
+ 2. Find the problematic section
1275
+
1276
+ 3. Trace back to source with `--source-maps`:
1277
+
1278
+ ```bash
1279
+ xvcl main.xvcl -o main.vcl --source-maps
1280
+ ```
1281
+
1282
+ 4. Fix the source file
1283
+
1284
+ ### Macro expansion issues
1285
+
1286
+ **Problem:** Macro expands incorrectly.
1287
+
1288
+ ```vcl
1289
+ #inline double(x)
1290
+ x + x
1291
+ #endinline
1292
+
1293
+ set var.result = double(1 + 2);
1294
+ // Expands to: (1 + 2) + (1 + 2) ✓ Correct
1295
+ ```
1296
+
1297
+ xvcl automatically adds parentheses when needed.
1298
+
1299
+ **If you see issues:** Check operator precedence in your macro definition.
1300
+
1301
+ ### Function calls not working
1302
+
1303
+ **Problem:** Function call doesn't get replaced.
1304
+
1305
+ ```vcl
1306
+ #def add(a INTEGER, b INTEGER) -> INTEGER
1307
+ return a + b;
1308
+ #enddef
1309
+
1310
+ set var.result = add(5, 10);
1311
+ ```
1312
+
1313
+ **Common causes:**
1314
+
1315
+ 1. **Missing semicolon:** Function calls must end with `;`
1316
+ ```vcl
1317
+ set var.result = add(5, 10); // ✓ Correct
1318
+ set var.result = add(5, 10) // ✗ Won't match
1319
+ ```
1320
+
1321
+ 2. **Wrong number of arguments:**
1322
+ ```vcl
1323
+ set var.result = add(5); // ✗ Expects 2 args
1324
+ ```
1325
+
1326
+ 3. **Typo in function name:**
1327
+ ```vcl
1328
+ set var.result = addr(5, 10); // ✗ Function 'addr' not defined
1329
+ ```
1330
+
1331
+ ### Performance issues
1332
+
1333
+ **Problem:** Compilation is slow.
1334
+
1335
+ **Common causes:**
1336
+
1337
+ 1. **Large loops:** `#for i in range(10000)` generates 10,000 copies
1338
+ 2. **Deep nesting:** Multiple nested loops or includes
1339
+ 3. **Complex macros:** Heavily nested macro expansions
1340
+
1341
+ **Solutions:**
1342
+
1343
+ 1. Reduce loop iterations if possible
1344
+ 2. Use functions instead of generating everything inline
1345
+ 3. Split into multiple source files
1346
+ 4. Profile with `--debug` to see what's slow
1347
+
1348
+ ### Getting help
1349
+
1350
+ **Check the error context:**
1351
+
1352
+ Errors show surrounding lines:
1353
+
1354
+ ```
1355
+ Error at main.xvcl:15:
1356
+ Invalid #for syntax: #for in range(10)
1357
+
1358
+ Context:
1359
+ 13: sub vcl_recv {
1360
+ 14: // Generate backends
1361
+ → 15: #for in range(10)
1362
+ 16: backend web{{i}} { ... }
1363
+ 17: #endfor
1364
+ 18: }
1365
+ ```
1366
+
1367
+ **Enable debug mode:**
1368
+
1369
+ ```bash
1370
+ xvcl main.xvcl --debug
1371
+ ```
1372
+
1373
+ **Validate generated VCL:**
1374
+
1375
+ ```bash
1376
+ falco lint main.vcl -vv
1377
+ ```
1378
+
1379
+ The `-vv` flag shows detailed Falco errors.
1380
+
1381
+ ---
1382
+
1383
+ ## Examples Gallery
1384
+
1385
+ Here are complete, working examples you can use as starting points.
1386
+
1387
+ ### Example 1: Multi-region backends
1388
+
1389
+ **`multi-region.xvcl`:**
1390
+
1391
+ ```vcl
1392
+ #const REGIONS = ["us-east", "us-west", "eu-west", "ap-south"]
1393
+ #const DEFAULT_REGION = "us-east"
1394
+
1395
+ #for region in REGIONS
1396
+ backend F_origin_{{region}} {
1397
+ .host = "origin-{{region}}.example.com";
1398
+ .port = "443";
1399
+ .ssl = true;
1400
+ .connect_timeout = 5s;
1401
+ .first_byte_timeout = 30s;
1402
+ .between_bytes_timeout = 10s;
1403
+ }
1404
+ #endfor
1405
+
1406
+ sub vcl_recv {
1407
+ declare local var.region STRING;
1408
+
1409
+ // Detect region from client IP or header
1410
+ if (req.http.X-Region) {
1411
+ set var.region = req.http.X-Region;
1412
+ } else {
1413
+ set var.region = "{{DEFAULT_REGION}}";
1414
+ }
1415
+
1416
+ // Route to appropriate backend
1417
+ #for region in REGIONS
1418
+ if (var.region == "{{region}}") {
1419
+ set req.backend = F_origin_{{region}};
1420
+ }
1421
+ #endfor
1422
+ }
1423
+ ```
1424
+
1425
+ ### Example 2: Feature flag system
1426
+
1427
+ **`feature-flags.xvcl`:**
1428
+
1429
+ ```vcl
1430
+ #const ENABLE_NEW_CACHE_POLICY = True
1431
+ #const ENABLE_WEBP_CONVERSION = True
1432
+ #const ENABLE_ANALYTICS = False
1433
+ #const ENABLE_DEBUG_HEADERS = False
1434
+
1435
+ sub vcl_recv {
1436
+ #if ENABLE_NEW_CACHE_POLICY
1437
+ // New cache policy with fine-grained control
1438
+ if (req.url.path ~ "\.(jpg|png|gif|css|js)$") {
1439
+ set req.http.X-Cache-Policy = "static";
1440
+ } else {
1441
+ set req.http.X-Cache-Policy = "dynamic";
1442
+ }
1443
+ #else
1444
+ // Legacy cache policy
1445
+ set req.http.X-Cache-Policy = "default";
1446
+ #endif
1447
+
1448
+ #if ENABLE_WEBP_CONVERSION
1449
+ if (req.http.Accept ~ "image/webp") {
1450
+ set req.http.X-Image-Format = "webp";
1451
+ }
1452
+ #endif
1453
+
1454
+ #if ENABLE_ANALYTICS
1455
+ set req.http.X-Analytics-ID = uuid.generate();
1456
+ #endif
1457
+ }
1458
+
1459
+ sub vcl_deliver {
1460
+ #if ENABLE_DEBUG_HEADERS
1461
+ set resp.http.X-Cache-Status = resp.http.X-Cache;
1462
+ set resp.http.X-Backend = req.backend;
1463
+ set resp.http.X-Region = req.http.X-Region;
1464
+ #endif
1465
+ }
1466
+ ```
1467
+
1468
+ ### Example 3: URL normalization library
1469
+
1470
+ **`url-utils.xvcl`:**
1471
+
1472
+ ```vcl
1473
+ // Inline macros for common URL operations
1474
+ #inline strip_www(host)
1475
+ regsub(host, "^www\.", "")
1476
+ #endinline
1477
+
1478
+ #inline lowercase_host(host)
1479
+ std.tolower(host)
1480
+ #endinline
1481
+
1482
+ #inline normalize_host(host)
1483
+ lowercase_host(strip_www(host))
1484
+ #endinline
1485
+
1486
+ #inline remove_trailing_slash(path)
1487
+ regsub(path, "/$", "")
1488
+ #endinline
1489
+
1490
+ #inline remove_query_string(url)
1491
+ regsub(url, "\?.*$", "")
1492
+ #endinline
1493
+
1494
+ // Function for complex normalization
1495
+ #def normalize_url(url STRING, host STRING) -> STRING
1496
+ declare local var.result STRING;
1497
+ declare local var.clean_host STRING;
1498
+ declare local var.clean_path STRING;
1499
+
1500
+ set var.clean_host = normalize_host(host);
1501
+ set var.clean_path = remove_trailing_slash(url);
1502
+ set var.result = "https://" + var.clean_host + var.clean_path;
1503
+
1504
+ return var.result;
1505
+ #enddef
1506
+
1507
+ sub vcl_recv {
1508
+ declare local var.canonical_url STRING;
1509
+ set var.canonical_url = normalize_url(req.url.path, req.http.Host);
1510
+ set req.http.X-Canonical-URL = var.canonical_url;
1511
+ }
1512
+ ```
1513
+
1514
+ ### Example 4: Lookup table generation
1515
+
1516
+ **`lookup-tables.xvcl`:**
1517
+
1518
+ ```vcl
1519
+ #const HEX_DIGITS = "0123456789abcdef"
1520
+
1521
+ // Generate a byte-to-hex lookup table
1522
+ table byte_to_hex STRING {
1523
+ #for i in range(256)
1524
+ "{{i}}": "{{format(i, '02x')}}"{{", " if i < 255 else ""}}
1525
+ #endfor
1526
+ }
1527
+
1528
+ // Generate HTTP status code descriptions
1529
+ #const STATUS_CODES = [200, 201, 301, 302, 400, 401, 403, 404, 500, 502, 503]
1530
+ #const STATUS_MESSAGES = ["OK", "Created", "Moved Permanently", "Found", "Bad Request", "Unauthorized", "Forbidden", "Not Found", "Internal Server Error", "Bad Gateway", "Service Unavailable"]
1531
+
1532
+ table status_messages STRING {
1533
+ #for i in range(len(STATUS_CODES))
1534
+ "{{STATUS_CODES[i]}}": "{{STATUS_MESSAGES[i]}}"{{", " if i < len(STATUS_CODES) - 1 else ""}}
1535
+ #endfor
1536
+ }
1537
+
1538
+ sub vcl_deliver {
1539
+ // Use generated tables
1540
+ set resp.http.X-Status-Message = table.lookup(status_messages, resp.status);
1541
+ }
1542
+ ```
1543
+
1544
+ ---
1545
+
1546
+ ## Summary
1547
+
1548
+ xvcl extends Fastly VCL with powerful programming constructs:
1549
+
1550
+ **Core features:**
1551
+
1552
+ - Constants - Single source of truth for configuration
1553
+ - Template expressions - Dynamic value substitution
1554
+ - For loops - Generate repetitive code
1555
+ - Conditionals - Environment-specific builds
1556
+ - Variables - Cleaner local variable syntax
1557
+ - Includes - Modular code organization
1558
+
1559
+ **Advanced features:**
1560
+
1561
+ - Inline macros - Zero-overhead text substitution
1562
+ - Functions - Reusable logic with return values
1563
+
1564
+ **Benefits:**
1565
+
1566
+ - Less code duplication
1567
+ - Fewer copy-paste errors
1568
+ - Better maintainability
1569
+ - Easier testing
1570
+ - Faster development
1571
+
1572
+ **Integration:**
1573
+
1574
+ - Works with Falco's full toolset
1575
+ - Standard VCL output
1576
+ - No runtime overhead
1577
+ - Easy CI/CD integration
1578
+
1579
+ Start simple, add complexity as needed. xvcl grows with your VCL projects.