xvcl


Namexvcl JSON
Version 2.5.0 PyPI version JSON
download
home_pageNone
SummaryExtended VCL compiler with metaprogramming features for Fastly VCL
upload_time2025-10-11 09:16:25
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
keywords compiler fastly preprocessor varnish vcl
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # xvcl

Supercharge your Fastly VCL with programming constructs like loops, functions, constants, and more.

**📖 [Quick Reference Guide](xvcl-quick-reference.md)** - One-page syntax reference for all xvcl features

## Table of Contents

- [Introduction](#introduction)
- [Why Use xvcl?](#why-use-xvcl)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Features](#core-features)
  - [Constants](#constants)
  - [Template Expressions](#template-expressions)
  - [For Loops](#for-loops)
  - [Conditionals](#conditionals)
  - [Variables](#variables)
  - [File Includes](#file-includes)
- [Advanced Features](#advanced-features)
  - [Inline Macros](#inline-macros)
  - [Functions](#functions)
- [Command-Line Usage](#command-line-usage)
- [Integration with Falco](#integration-with-falco)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)

## Introduction

xvcl is VCL transpiler that extends Fastly VCL with programming constructs that generate standard VCL code.

Think of it as a build step for your VCL: write enhanced VCL source files, run xvcl, and get clean, valid VCL output.

> **💡 Tip:** For a quick syntax reference, see the [xvcl Quick Reference Guide](xvcl-quick-reference.md).

**What you can do:**

- Define constants once, use them everywhere
- Generate repetitive code with for loops
- Create reusable functions with return values
- Build zero-overhead macros for common patterns
- Conditionally compile code for different environments
- Split large VCL files into modular includes

**What you get:**

- Standard VCL output that works with Fastly
- Compile-time safety and error checking
- Reduced code duplication
- Better maintainability

## Why Use xvcl?

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:

- **Copy-paste errors:** Similar backends? Copy, paste, modify, repeat, make mistakes
- **Magic numbers:** Hardcoded values scattered throughout your code
- **Duplication:** Same logic repeated in multiple subroutines
- **Poor maintainability:** Change one thing, update it in 20 places

xvcl solves these problems by adding programming constructs that compile down to clean VCL.

### Real-World Example

**Without xvcl (manual, error-prone):**

```vcl
backend web1 {
  .host = "web1.example.com";
  .port = "80";
}

backend web2 {
  .host = "web2.example.com";
  .port = "80";
}

backend web3 {
  .host = "web3.example.com";
  .port = "80";
}

sub vcl_recv {
  if (req.http.Host == "web1.example.com") {
    set req.backend = web1;
  }
  if (req.http.Host == "web2.example.com") {
    set req.backend = web2;
  }
  if (req.http.Host == "web3.example.com") {
    set req.backend = web3;
  }
}
```

**With xvcl (clean, maintainable):**

```vcl
#const BACKENDS = ["web1", "web2", "web3"]

#for backend in BACKENDS
backend {{backend}} {
  .host = "{{backend}}.example.com";
  .port = "80";
}
#endfor

sub vcl_recv {
#for backend in BACKENDS
  if (req.http.Host == "{{backend}}.example.com") {
    set req.backend = {{backend}};
  }
#endfor
}
```

Adding a new backend? Just update the list. xvcl generates all the code.

## Installation

xvcl is a Python package. You need Python 3.9 or later.

```bash
# Using pip
pip install xvcl

# Or install from source
pip install .

# Development installation with dev dependencies
pip install -e ".[dev]"

# Using uv (recommended for faster installation)
uv pip install xvcl
```

After installation, the `xvcl` command is available globally.

**No external dependencies** - uses only Python standard library.

## Quick Start

Create an xvcl source file (use `.xvcl` extension by convention):

**`hello.xvcl`:**

```vcl
#const MESSAGE = "Hello from xvcl!"

sub vcl_recv {
  set req.http.X-Message = "{{MESSAGE}}";
}
```

Run xvcl:

```bash
xvcl hello.xvcl -o hello.vcl
```

Output **`hello.vcl`:**

```vcl
sub vcl_recv {
  set req.http.X-Message = "Hello from xvcl!";
}
```

Validate with Falco:

```bash
falco lint hello.vcl
```

## Core Features

### Constants

Define named constants with type checking. Constants are evaluated at preprocessing time and substituted into your code.

**Syntax:**

```vcl
#const NAME TYPE = value
```

**Supported types:**
- `INTEGER` - Whole numbers
- `STRING` - Text strings
- `FLOAT` - Decimal numbers
- `BOOL` - True/False

**Example:**

```vcl
#const MAX_AGE INTEGER = 3600
#const ORIGIN STRING = "origin.example.com"
#const PRODUCTION BOOL = True
#const CACHE_VERSION FLOAT = 1.5
```

**Using constants in templates:**

```vcl
#const TTL = 300
#const BACKEND_HOST = "api.example.com"

backend F_api {
  .host = "{{BACKEND_HOST}}";
  .port = "443";
}

sub vcl_fetch {
  set beresp.ttl = {{TTL}}s;
}
```

**Why use constants?**

- Single source of truth for configuration values
- Easy to update across entire VCL
- Type safety prevents mistakes
- Self-documenting code

### Template Expressions

Embed Python expressions in your VCL using `{{expression}}` syntax. Expressions are evaluated at preprocessing time.

**Example:**

```vcl
#const PORT = 8080

sub vcl_recv {
  set req.http.X-Port = "{{PORT}}";
  set req.http.X-Double = "{{PORT * 2}}";
  set req.http.X-Hex = "{{hex(PORT)}}";
}
```

**Output:**

```vcl
sub vcl_recv {
  set req.http.X-Port = "8080";
  set req.http.X-Double = "16160";
  set req.http.X-Hex = "0x1f90";
}
```

**Available functions:**

- `range(n)` - Generate number sequences
- `len(list)` - Get list length
- `str(x)`, `int(x)` - Type conversions
- `hex(n)` - Hexadecimal conversion
- `format(x, fmt)` - Format values
- `enumerate(iterable)` - Enumerate with indices
- `min(...)`, `max(...)` - Min/max values
- `abs(n)` - Absolute value

**String formatting:**

```vcl
#const REGION = "us-east"
#const INDEX = 1

set req.backend = F_backend_{{REGION}}_{{INDEX}};
```

### For Loops

Generate repetitive VCL code by iterating over ranges or lists.

**Syntax:**

```vcl
#for variable in iterable
  // Code to repeat
#endfor
```

**Example 1: Range-based loop**

```vcl
#for i in range(5)
backend web{{i}} {
  .host = "web{{i}}.example.com";
  .port = "80";
}
#endfor
```

**Output:**

```vcl
backend web0 {
  .host = "web0.example.com";
  .port = "80";
}
backend web1 {
  .host = "web1.example.com";
  .port = "80";
}
// ... continues through web4
```

**Example 2: List iteration**

```vcl
#const REGIONS = ["us-east", "us-west", "eu-west"]

#for region in REGIONS
backend F_{{region}} {
  .host = "{{region}}.example.com";
  .port = "443";
  .ssl = true;
}
#endfor
```

**Example 3: Nested loops**

```vcl
#const REGIONS = ["us", "eu"]
#const ENVS = ["prod", "staging"]

#for region in REGIONS
  #for env in ENVS
backend {{region}}_{{env}} {
  .host = "{{env}}.{{region}}.example.com";
  .port = "443";
}
  #endfor
#endfor
```

**Why use for loops?**

- Generate multiple similar backends
- Create ACL entries from lists
- Build routing logic programmatically
- Reduce copy-paste errors

### Conditionals

Conditionally include or exclude code based on compile-time conditions.

**Syntax:**

```vcl
#if condition
  // Code when true
#else
  // Code when false (optional)
#endif
```

**Example 1: Environment-specific configuration**

```vcl
#const PRODUCTION = True
#const DEBUG = False

sub vcl_recv {
#if PRODUCTION
  set req.http.X-Environment = "production";
  unset req.http.X-Debug-Info;
#else
  set req.http.X-Environment = "development";
  set req.http.X-Debug-Info = "Enabled";
#endif

#if DEBUG
  set req.http.X-Request-ID = randomstr(16, "0123456789abcdef");
#endif
}
```

**Example 2: Feature flags**

```vcl
#const ENABLE_NEW_ROUTING = True
#const ENABLE_RATE_LIMITING = False

sub vcl_recv {
#if ENABLE_NEW_ROUTING
  call new_routing_logic;
#else
  call legacy_routing_logic;
#endif

#if ENABLE_RATE_LIMITING
  if (ratelimit.check_rate("client_" + client.ip, 1, 100, 60s, 1000s)) {
    error 429 "Too Many Requests";
  }
#endif
}
```

**Why use conditionals?**

- Single codebase for multiple environments
- Easy feature flag management
- Dead code elimination (code in false branches isn't generated)
- Compile-time optimization

### Variables

Declare and initialize local variables in one step.

**Syntax:**

```vcl
#let name TYPE = expression;
```

**Example:**

```vcl
sub vcl_recv {
  #let timestamp STRING = std.time(now, now);
  #let cache_key STRING = req.url.path + req.http.Host;

  set req.http.X-Timestamp = var.timestamp;
  set req.hash = var.cache_key;
}
```

**Expands to:**

```vcl
sub vcl_recv {
  declare local var.timestamp STRING;
  set var.timestamp = std.time(now, now);
  declare local var.cache_key STRING;
  set var.cache_key = req.url.path + req.http.Host;

  set req.http.X-Timestamp = var.timestamp;
  set req.hash = var.cache_key;
}
```

**Why use #let?**

- Shorter syntax than separate declare + set
- Clear initialization point
- Reduces boilerplate

### File Includes

Split large VCL files into modular, reusable components.

**Syntax:**

```vcl
#include "path/to/file.xvcl"
```

**Example project structure:**

```
vcl/
├── main.xvcl
├── includes/
│   ├── backends.xvcl
│   ├── security.xvcl
│   └── routing.xvcl
```

**`main.xvcl`:**

```vcl
#include "includes/backends.xvcl"
#include "includes/security.xvcl"
#include "includes/routing.xvcl"

sub vcl_recv {
  call security_checks;
  call routing_logic;
}
```

**`includes/backends.xvcl`:**

```vcl
#const BACKENDS = ["web1", "web2", "web3"]

#for backend in BACKENDS
backend F_{{backend}} {
  .host = "{{backend}}.example.com";
  .port = "443";
}
#endfor
```

**Include path resolution:**

1. Relative to the current file
2. Relative to include paths specified with `-I`

**Run with include paths:**

```bash
xvcl main.xvcl -o main.vcl -I ./vcl/includes
```

**Features:**

- **Include-once semantics:** Files are only included once even if referenced multiple times
- **Cycle detection:** Prevents circular includes
- **Shared constants:** Constants defined in included files are available to the parent

**Why use includes?**

- Organize large VCL projects
- Share common configurations across multiple VCL files
- Team collaboration (different files for different concerns)
- Reusable components library

## Advanced Features

### Inline Macros

Create zero-overhead text substitution macros. Unlike functions, macros are expanded inline at compile time with no runtime cost.

**Syntax:**

```vcl
#inline macro_name(param1, param2, ...)
expression
#endinline
```

**Example 1: String concatenation**

```vcl
#inline add_prefix(s)
"prefix-" + s
#endinline

#inline add_suffix(s)
s + "-suffix"
#endinline

sub vcl_recv {
  set req.http.X-Modified = add_prefix("test");
  set req.http.X-Both = add_prefix(add_suffix("middle"));
}
```

**Output:**

```vcl
sub vcl_recv {
  set req.http.X-Modified = "prefix-" + "test";
  set req.http.X-Both = "prefix-" + "middle" + "-suffix";
}
```

**Example 2: Common patterns**

```vcl
#inline normalize_host(host)
std.tolower(regsub(host, "^www\.", ""))
#endinline

#inline cache_key(url, host)
digest.hash_md5(url + "|" + host)
#endinline

sub vcl_recv {
  set req.http.X-Normalized = normalize_host(req.http.Host);
  set req.hash = cache_key(req.url, req.http.Host);
}
```

**Output:**

```vcl
sub vcl_recv {
  set req.http.X-Normalized = std.tolower(regsub(req.http.Host, "^www\.", ""));
  set req.hash = digest.hash_md5(req.url + "|" + req.http.Host);
}
```

**Example 3: Operator precedence handling**

xvcl automatically handles operator precedence:

```vcl
#inline double(x)
x + x
#endinline

sub vcl_recv {
  declare local var.result INTEGER;
  set var.result = double(5) * 10;  // Correctly expands to (5 + 5) * 10
}
```

**Macros vs Functions:**

| Feature       | Macros              | Functions                     |
| ------------- | ------------------- | ----------------------------- |
| Expansion     | Compile-time inline | Runtime subroutine call       |
| Overhead      | None                | Subroutine call + global vars |
| Return values | Expression only     | Single or tuple               |
| Use case      | Simple expressions  | Complex logic                 |

**When to use macros:**

- String manipulation patterns
- Simple calculations
- Common expressions repeated throughout code
- When you need zero runtime overhead

**When to use functions:**

- Complex logic with multiple statements
- Need to return multiple values
- Conditional logic or loops inside the reusable code

### Functions

Define reusable functions with parameters and return values. Functions are compiled into VCL subroutines.

**Syntax:**

```vcl
#def function_name(param1 TYPE, param2 TYPE, ...) -> RETURN_TYPE
  // Function body
  return value;
#enddef
```

**Example 1: Simple function**

```vcl
#def add(a INTEGER, b INTEGER) -> INTEGER
  declare local var.sum INTEGER;
  set var.sum = a + b;
  return var.sum;
#enddef

sub vcl_recv {
  declare local var.result INTEGER;
  set var.result = add(5, 10);
  set req.http.X-Sum = var.result;
}
```

**Example 2: String processing**

```vcl
#def normalize_path(path STRING) -> STRING
  declare local var.result STRING;
  set var.result = std.tolower(path);
  set var.result = regsub(var.result, "/$", "");
  return var.result;
#enddef

sub vcl_recv {
  declare local var.clean_path STRING;
  set var.clean_path = normalize_path(req.url.path);
  set req.url = var.clean_path;
}
```

**Example 3: Functions with conditionals**

```vcl
#def should_cache(url STRING) -> BOOL
  declare local var.cacheable BOOL;

  if (url ~ "^/api/") {
    set var.cacheable = false;
  } else if (url ~ "\.(jpg|png|css|js)$") {
    set var.cacheable = true;
  } else {
    set var.cacheable = false;
  }

  return var.cacheable;
#enddef

sub vcl_recv {
  declare local var.can_cache BOOL;
  set var.can_cache = should_cache(req.url.path);

  if (var.can_cache) {
    return(lookup);
  } else {
    return(pass);
  }
}
```

**Example 4: Tuple returns (multiple values)**

```vcl
#def parse_user_agent(ua STRING) -> (STRING, STRING)
  declare local var.browser STRING;
  declare local var.os STRING;

  if (ua ~ "Chrome") {
    set var.browser = "chrome";
  } else if (ua ~ "Firefox") {
    set var.browser = "firefox";
  } else {
    set var.browser = "other";
  }

  if (ua ~ "Windows") {
    set var.os = "windows";
  } else if (ua ~ "Mac") {
    set var.os = "macos";
  } else {
    set var.os = "other";
  }

  return var.browser, var.os;
#enddef

sub vcl_recv {
  declare local var.browser STRING;
  declare local var.os STRING;

  set var.browser, var.os = parse_user_agent(req.http.User-Agent);

  set req.http.X-Browser = var.browser;
  set req.http.X-OS = var.os;
}
```

**Behind the scenes:**

Functions are compiled into VCL subroutines using global headers for parameter passing:

```vcl
// Your code:
set var.result = add(5, 10);

// Becomes:
set req.http.X-Func-add-a = std.itoa(5);
set req.http.X-Func-add-b = std.itoa(10);
call add;
set var.result = std.atoi(req.http.X-Func-add-Return);
```

xvcl generates the `sub add { ... }` implementation and handles all type conversions automatically.

**Function features:**

- **Type safety:** Parameters and returns are type-checked
- **Multiple returns:** Use tuple syntax to return multiple values
- **Automatic conversions:** INTEGER/FLOAT/BOOL are converted to/from STRING automatically
- **Scope annotations:** Generated subroutines work in all VCL scopes

**Why use functions?**

- Reusable complex logic
- Reduce code duplication
- Easier testing (test the function once)
- Better code organization

## Command-Line Usage

**Basic usage:**

```bash
xvcl input.xvcl -o output.vcl
```

**Options:**

| Option          | Description                                         |
| --------------- | --------------------------------------------------- |
| `input`         | Input xvcl source file (required)                   |
| `-o, --output`  | Output VCL file (default: replaces .xvcl with .vcl) |
| `-I, --include` | Add include search path (repeatable)                |
| `--debug`       | Enable debug output with expansion traces           |
| `--source-maps` | Add source map comments to output                   |
| `-v, --verbose` | Verbose output (alias for --debug)                  |

**Examples:**

```bash
# Basic compilation
xvcl main.xvcl -o main.vcl

# With include paths
xvcl main.xvcl -o main.vcl \
  -I ./includes \
  -I ./shared

# Debug mode (see expansion traces)
xvcl main.xvcl -o main.vcl --debug

# With source maps (track generated code origin)
xvcl main.xvcl -o main.vcl --source-maps
```

**Automatic output naming:**

If you don't specify `-o`, xvcl replaces `.xvcl` with `.vcl`:

```bash
# These are equivalent:
xvcl main.xvcl
xvcl main.xvcl -o main.vcl
```

**Debug mode output:**

```bash
$ xvcl example.xvcl --debug

[DEBUG] Processing file: example.xvcl
[DEBUG] Pass 1: Extracting constants
[DEBUG]   Defined constant: MAX_AGE = 3600
[DEBUG] Pass 2: Processing includes
[DEBUG] Pass 3: Extracting inline macros
[DEBUG]   Defined macro: add_prefix(s)
[DEBUG] Pass 4: Extracting functions
[DEBUG] Pass 5: Processing directives and generating code
[DEBUG]   Processing #for at line 10
[DEBUG]     Loop iterating 3 times
[DEBUG]       Iteration 0: backend = web1
[DEBUG]       Iteration 1: backend = web2
[DEBUG]       Iteration 2: backend = web3
[DEBUG] Pass 6: Generating function subroutines
✓ Compiled example.xvcl -> example.vcl
  Constants: 1
  Macros: 1 (add_prefix)
  Functions: 0
```

## Integration with Falco

xvcl generates standard VCL that you can use with Falco's full toolset.

**Recommended workflow:**

```bash
# 1. Write your xvcl source
vim main.xvcl

# 2. Compile with xvcl
xvcl main.xvcl -o main.vcl

# 3. Lint with Falco
falco lint main.vcl

# 4. Test with Falco
falco test main.vcl

# 5. Simulate with Falco
falco simulate main.vcl
```

**Makefile integration:**

```makefile
# Makefile
.PHONY: build lint test clean

XVCL = xvcl
SOURCES = $(wildcard *.xvcl)
OUTPUTS = $(SOURCES:.xvcl=.vcl)

build: $(OUTPUTS)

%.vcl: %.xvcl
	$(XVCL) $< -o $@ -I ./includes

lint: build
	falco lint *.vcl

test: build
	falco test *.vcl

clean:
	rm -f $(OUTPUTS)
```

**CI/CD integration:**

```yaml
# .github/workflows/vcl.yml
name: VCL CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install Falco
        run: |
          wget https://github.com/ysugimoto/falco/releases/latest/download/falco_linux_amd64
          chmod +x falco_linux_amd64
          sudo mv falco_linux_amd64 /usr/local/bin/falco

      - name: Compile xvcl
        run: |
          xvcl main.xvcl -o main.vcl

      - name: Lint VCL
        run: falco lint main.vcl

      - name: Test VCL
        run: falco test main.vcl
```

**Testing compiled VCL:**

You can write Falco tests for your generated VCL:

**`main.test.vcl`:**

```vcl
// @suite: Backend routing tests

// @test: Should route to correct backend
sub test_backend_routing {
  set req.http.Host = "web1.example.com";
  call vcl_recv;

  assert.equal(req.backend, "web1");
}
```

Run tests after compilation:

```bash
xvcl main.xvcl -o main.vcl
falco test main.vcl
```

## Best Practices

### 1. Use the `.xvcl` extension

Makes it clear which files are xvcl source files:

```
✓ main.xvcl → main.vcl
✗ main.vcl → main.vcl.processed
```

### 2. Keep constants at the top

```vcl
// Good: Constants first, easy to find
#const MAX_BACKENDS = 10
#const PRODUCTION = True

#for i in range(MAX_BACKENDS)
  // ... use constant
#endfor
```

### 3. Use descriptive constant names

```vcl
// Good
#const CACHE_TTL_SECONDS = 3600
#const API_BACKEND_HOST = "api.example.com"

// Bad
#const X = 3600
#const B = "api.example.com"
```

### 4. Comment your macros and functions

```vcl
// Normalizes a hostname by removing www prefix and converting to lowercase
#inline normalize_host(host)
std.tolower(regsub(host, "^www\.", ""))
#endinline

// Parses User-Agent and returns (browser, os) tuple
#def parse_user_agent(ua STRING) -> (STRING, STRING)
  // ...
#enddef
```

### 5. Prefer macros for simple expressions, functions for complex logic

```vcl
// Good: Simple expression = macro
#inline cache_key(url, host)
digest.hash_md5(url + "|" + host)
#endinline

// Good: Complex logic = function
#def should_cache(url STRING, method STRING) -> BOOL
  declare local var.result BOOL;
  if (method != "GET" && method != "HEAD") {
    set var.result = false;
  } else if (url ~ "^/api/") {
    set var.result = false;
  } else {
    set var.result = true;
  }
  return var.result;
#enddef
```

### 6. Use includes for organization

```
vcl/
├── main.xvcl          # Main entry point
├── config.xvcl        # Constants and configuration
├── includes/
│   ├── backends.xvcl
│   ├── security.xvcl
│   ├── routing.xvcl
│   └── caching.xvcl
```

### 7. Version control both source and output

```gitignore
# Include both in git
*.xvcl
*.vcl

# But gitignore generated files in CI
# (if you regenerate on deploy)
```

**Or** only version control source files and regenerate on deployment:

```gitignore
# Version control xvcl source only
*.xvcl

# Ignore generated VCL
*.vcl
```

Choose based on your deployment process.

### 8. Add source maps in development

```bash
# Development: easier debugging
xvcl main.xvcl -o main.vcl --source-maps

# Production: cleaner output
xvcl main.xvcl -o main.vcl
```

Source maps add comments like:

```vcl
// BEGIN INCLUDE: includes/backends.xvcl
backend F_web1 { ... }
// END INCLUDE: includes/backends.xvcl
```

### 9. Test incrementally

Don't write a massive source file and compile once. Test as you go:

```bash
# Write a bit
vim main.xvcl

# Compile
xvcl main.xvcl

# Check output
cat main.vcl

# Lint
falco lint main.vcl

# Repeat
```

### 10. Use debug mode when things go wrong

```bash
xvcl main.xvcl --debug
```

Shows exactly what xvcl is doing.

## Troubleshooting

### Error: "Name 'X' is not defined"

**Problem:** You're using a variable or constant that doesn't exist.

```vcl
#const PORT = 8080

set req.http.X-Value = "{{PROT}}";  // Typo!
```

**Error:**

```
Error at main.xvcl:3:
  Name 'PROT' is not defined
  Did you mean: PORT?
```

**Solution:** Check spelling. xvcl suggests similar names.

### Error: "Invalid #const syntax"

**Problem:** Malformed constant declaration.

```vcl
#const PORT 8080        // Missing = sign
#const = 8080           // Missing name
#const PORT = STRING    // Missing value
```

**Solution:** Use correct syntax:

```vcl
#const PORT INTEGER = 8080
```

### Error: "No matching #endfor for #for"

**Problem:** Missing closing keyword.

```vcl
#for i in range(10)
  backend web{{i}} { ... }
// Missing #endfor
```

**Solution:** Add the closing keyword:

```vcl
#for i in range(10)
  backend web{{i}} { ... }
#endfor
```

### Error: "Circular include detected"

**Problem:** File A includes file B which includes file A.

```
main.xvcl includes util.xvcl
util.xvcl includes main.xvcl
```

**Solution:** Restructure your includes. Create a shared file:

```
main.xvcl includes shared.xvcl
util.xvcl includes shared.xvcl
```

### Error: "Cannot find included file"

**Problem:** Include path is wrong or file doesn't exist.

```vcl
#include "includes/backends.xvcl"
```

**Solution:** Check path and use `-I` flag:

```bash
xvcl main.xvcl -o main.vcl -I ./includes
```

### Generated VCL has syntax errors

**Problem:** xvcl generated invalid VCL.

**Solution:**

1. Check the generated output:
   ```bash
   cat main.vcl
   ```

2. Find the problematic section

3. Trace back to source with `--source-maps`:

   ```bash
   xvcl main.xvcl -o main.vcl --source-maps
   ```

4. Fix the source file

### Macro expansion issues

**Problem:** Macro expands incorrectly.

```vcl
#inline double(x)
x + x
#endinline

set var.result = double(1 + 2);
// Expands to: (1 + 2) + (1 + 2)  ✓ Correct
```

xvcl automatically adds parentheses when needed.

**If you see issues:** Check operator precedence in your macro definition.

### Function calls not working

**Problem:** Function call doesn't get replaced.

```vcl
#def add(a INTEGER, b INTEGER) -> INTEGER
  return a + b;
#enddef

set var.result = add(5, 10);
```

**Common causes:**

1. **Missing semicolon:** Function calls must end with `;`
   ```vcl
   set var.result = add(5, 10);  // ✓ Correct
   set var.result = add(5, 10)   // ✗ Won't match
   ```

2. **Wrong number of arguments:**
   ```vcl
   set var.result = add(5);      // ✗ Expects 2 args
   ```

3. **Typo in function name:**
   ```vcl
   set var.result = addr(5, 10); // ✗ Function 'addr' not defined
   ```

### Performance issues

**Problem:** Compilation is slow.

**Common causes:**

1. **Large loops:** `#for i in range(10000)` generates 10,000 copies
2. **Deep nesting:** Multiple nested loops or includes
3. **Complex macros:** Heavily nested macro expansions

**Solutions:**

1. Reduce loop iterations if possible
2. Use functions instead of generating everything inline
3. Split into multiple source files
4. Profile with `--debug` to see what's slow

### Getting help

**Check the error context:**

Errors show surrounding lines:

```
Error at main.xvcl:15:
  Invalid #for syntax: #for in range(10)

  Context:
    13: sub vcl_recv {
    14:   // Generate backends
  → 15:   #for in range(10)
    16:     backend web{{i}} { ... }
    17:   #endfor
    18: }
```

**Enable debug mode:**

```bash
xvcl main.xvcl --debug
```

**Validate generated VCL:**

```bash
falco lint main.vcl -vv
```

The `-vv` flag shows detailed Falco errors.

---

## Examples Gallery

Here are complete, working examples you can use as starting points.

### Example 1: Multi-region backends

**`multi-region.xvcl`:**

```vcl
#const REGIONS = ["us-east", "us-west", "eu-west", "ap-south"]
#const DEFAULT_REGION = "us-east"

#for region in REGIONS
backend F_origin_{{region}} {
  .host = "origin-{{region}}.example.com";
  .port = "443";
  .ssl = true;
  .connect_timeout = 5s;
  .first_byte_timeout = 30s;
  .between_bytes_timeout = 10s;
}
#endfor

sub vcl_recv {
  declare local var.region STRING;

  // Detect region from client IP or header
  if (req.http.X-Region) {
    set var.region = req.http.X-Region;
  } else {
    set var.region = "{{DEFAULT_REGION}}";
  }

  // Route to appropriate backend
#for region in REGIONS
  if (var.region == "{{region}}") {
    set req.backend = F_origin_{{region}};
  }
#endfor
}
```

### Example 2: Feature flag system

**`feature-flags.xvcl`:**

```vcl
#const ENABLE_NEW_CACHE_POLICY = True
#const ENABLE_WEBP_CONVERSION = True
#const ENABLE_ANALYTICS = False
#const ENABLE_DEBUG_HEADERS = False

sub vcl_recv {
#if ENABLE_NEW_CACHE_POLICY
  // New cache policy with fine-grained control
  if (req.url.path ~ "\.(jpg|png|gif|css|js)$") {
    set req.http.X-Cache-Policy = "static";
  } else {
    set req.http.X-Cache-Policy = "dynamic";
  }
#else
  // Legacy cache policy
  set req.http.X-Cache-Policy = "default";
#endif

#if ENABLE_WEBP_CONVERSION
  if (req.http.Accept ~ "image/webp") {
    set req.http.X-Image-Format = "webp";
  }
#endif

#if ENABLE_ANALYTICS
  set req.http.X-Analytics-ID = uuid.generate();
#endif
}

sub vcl_deliver {
#if ENABLE_DEBUG_HEADERS
  set resp.http.X-Cache-Status = resp.http.X-Cache;
  set resp.http.X-Backend = req.backend;
  set resp.http.X-Region = req.http.X-Region;
#endif
}
```

### Example 3: URL normalization library

**`url-utils.xvcl`:**

```vcl
// Inline macros for common URL operations
#inline strip_www(host)
regsub(host, "^www\.", "")
#endinline

#inline lowercase_host(host)
std.tolower(host)
#endinline

#inline normalize_host(host)
lowercase_host(strip_www(host))
#endinline

#inline remove_trailing_slash(path)
regsub(path, "/$", "")
#endinline

#inline remove_query_string(url)
regsub(url, "\?.*$", "")
#endinline

// Function for complex normalization
#def normalize_url(url STRING, host STRING) -> STRING
  declare local var.result STRING;
  declare local var.clean_host STRING;
  declare local var.clean_path STRING;

  set var.clean_host = normalize_host(host);
  set var.clean_path = remove_trailing_slash(url);
  set var.result = "https://" + var.clean_host + var.clean_path;

  return var.result;
#enddef

sub vcl_recv {
  declare local var.canonical_url STRING;
  set var.canonical_url = normalize_url(req.url.path, req.http.Host);
  set req.http.X-Canonical-URL = var.canonical_url;
}
```

### Example 4: A/B testing framework

**`ab-testing.xvcl`:**

```vcl
#const EXPERIMENTS = [
  ("homepage_hero", 50),
  ("checkout_flow", 30),
  ("pricing_page", 25)
]

#def assign_experiment(exp_id STRING, percentage INTEGER) -> BOOL
  declare local var.hash STRING;
  declare local var.value INTEGER;
  declare local var.assigned BOOL;

  set var.hash = digest.hash_md5(client.ip + exp_id);
  set var.value = std.atoi(substr(var.hash, 0, 2)) % 100;
  set var.assigned = (var.value < percentage);

  return var.assigned;
#enddef

sub vcl_recv {
  declare local var.in_experiment BOOL;

#for exp_id, percentage in EXPERIMENTS
  set var.in_experiment = assign_experiment("{{exp_id}}", {{percentage}});
  if (var.in_experiment) {
    set req.http.X-Experiment-{{exp_id}} = "variant";
  } else {
    set req.http.X-Experiment-{{exp_id}} = "control";
  }
#endfor
}
```

---

## Summary

xvcl extends Fastly VCL with powerful programming constructs:

**Core features:**

- Constants - Single source of truth for configuration
- Template expressions - Dynamic value substitution
- For loops - Generate repetitive code
- Conditionals - Environment-specific builds
- Variables - Cleaner local variable syntax
- Includes - Modular code organization

**Advanced features:**

- Inline macros - Zero-overhead text substitution
- Functions - Reusable logic with return values

**Benefits:**

- Less code duplication
- Fewer copy-paste errors
- Better maintainability
- Easier testing
- Faster development

**Integration:**

- Works with Falco's full toolset
- Standard VCL output
- No runtime overhead
- Easy CI/CD integration

Start simple, add complexity as needed. xvcl grows with your VCL projects.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "xvcl",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "compiler, fastly, preprocessor, varnish, vcl",
    "author": null,
    "author_email": "Frank Denis <github@pureftpd.org>",
    "download_url": "https://files.pythonhosted.org/packages/46/15/599be23003f62ffbf09a37488ca781c8a816529a1f81ce393a90b51bc58a/xvcl-2.5.0.tar.gz",
    "platform": null,
    "description": "# xvcl\n\nSupercharge your Fastly VCL with programming constructs like loops, functions, constants, and more.\n\n**\ud83d\udcd6 [Quick Reference Guide](xvcl-quick-reference.md)** - One-page syntax reference for all xvcl features\n\n## Table of Contents\n\n- [Introduction](#introduction)\n- [Why Use xvcl?](#why-use-xvcl)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Features](#core-features)\n  - [Constants](#constants)\n  - [Template Expressions](#template-expressions)\n  - [For Loops](#for-loops)\n  - [Conditionals](#conditionals)\n  - [Variables](#variables)\n  - [File Includes](#file-includes)\n- [Advanced Features](#advanced-features)\n  - [Inline Macros](#inline-macros)\n  - [Functions](#functions)\n- [Command-Line Usage](#command-line-usage)\n- [Integration with Falco](#integration-with-falco)\n- [Best Practices](#best-practices)\n- [Troubleshooting](#troubleshooting)\n\n## Introduction\n\nxvcl is VCL transpiler that extends Fastly VCL with programming constructs that generate standard VCL code.\n\nThink of it as a build step for your VCL: write enhanced VCL source files, run xvcl, and get clean, valid VCL output.\n\n> **\ud83d\udca1 Tip:** For a quick syntax reference, see the [xvcl Quick Reference Guide](xvcl-quick-reference.md).\n\n**What you can do:**\n\n- Define constants once, use them everywhere\n- Generate repetitive code with for loops\n- Create reusable functions with return values\n- Build zero-overhead macros for common patterns\n- Conditionally compile code for different environments\n- Split large VCL files into modular includes\n\n**What you get:**\n\n- Standard VCL output that works with Fastly\n- Compile-time safety and error checking\n- Reduced code duplication\n- Better maintainability\n\n## Why Use xvcl?\n\nVCL 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:\n\n- **Copy-paste errors:** Similar backends? Copy, paste, modify, repeat, make mistakes\n- **Magic numbers:** Hardcoded values scattered throughout your code\n- **Duplication:** Same logic repeated in multiple subroutines\n- **Poor maintainability:** Change one thing, update it in 20 places\n\nxvcl solves these problems by adding programming constructs that compile down to clean VCL.\n\n### Real-World Example\n\n**Without xvcl (manual, error-prone):**\n\n```vcl\nbackend web1 {\n  .host = \"web1.example.com\";\n  .port = \"80\";\n}\n\nbackend web2 {\n  .host = \"web2.example.com\";\n  .port = \"80\";\n}\n\nbackend web3 {\n  .host = \"web3.example.com\";\n  .port = \"80\";\n}\n\nsub vcl_recv {\n  if (req.http.Host == \"web1.example.com\") {\n    set req.backend = web1;\n  }\n  if (req.http.Host == \"web2.example.com\") {\n    set req.backend = web2;\n  }\n  if (req.http.Host == \"web3.example.com\") {\n    set req.backend = web3;\n  }\n}\n```\n\n**With xvcl (clean, maintainable):**\n\n```vcl\n#const BACKENDS = [\"web1\", \"web2\", \"web3\"]\n\n#for backend in BACKENDS\nbackend {{backend}} {\n  .host = \"{{backend}}.example.com\";\n  .port = \"80\";\n}\n#endfor\n\nsub vcl_recv {\n#for backend in BACKENDS\n  if (req.http.Host == \"{{backend}}.example.com\") {\n    set req.backend = {{backend}};\n  }\n#endfor\n}\n```\n\nAdding a new backend? Just update the list. xvcl generates all the code.\n\n## Installation\n\nxvcl is a Python package. You need Python 3.9 or later.\n\n```bash\n# Using pip\npip install xvcl\n\n# Or install from source\npip install .\n\n# Development installation with dev dependencies\npip install -e \".[dev]\"\n\n# Using uv (recommended for faster installation)\nuv pip install xvcl\n```\n\nAfter installation, the `xvcl` command is available globally.\n\n**No external dependencies** - uses only Python standard library.\n\n## Quick Start\n\nCreate an xvcl source file (use `.xvcl` extension by convention):\n\n**`hello.xvcl`:**\n\n```vcl\n#const MESSAGE = \"Hello from xvcl!\"\n\nsub vcl_recv {\n  set req.http.X-Message = \"{{MESSAGE}}\";\n}\n```\n\nRun xvcl:\n\n```bash\nxvcl hello.xvcl -o hello.vcl\n```\n\nOutput **`hello.vcl`:**\n\n```vcl\nsub vcl_recv {\n  set req.http.X-Message = \"Hello from xvcl!\";\n}\n```\n\nValidate with Falco:\n\n```bash\nfalco lint hello.vcl\n```\n\n## Core Features\n\n### Constants\n\nDefine named constants with type checking. Constants are evaluated at preprocessing time and substituted into your code.\n\n**Syntax:**\n\n```vcl\n#const NAME TYPE = value\n```\n\n**Supported types:**\n- `INTEGER` - Whole numbers\n- `STRING` - Text strings\n- `FLOAT` - Decimal numbers\n- `BOOL` - True/False\n\n**Example:**\n\n```vcl\n#const MAX_AGE INTEGER = 3600\n#const ORIGIN STRING = \"origin.example.com\"\n#const PRODUCTION BOOL = True\n#const CACHE_VERSION FLOAT = 1.5\n```\n\n**Using constants in templates:**\n\n```vcl\n#const TTL = 300\n#const BACKEND_HOST = \"api.example.com\"\n\nbackend F_api {\n  .host = \"{{BACKEND_HOST}}\";\n  .port = \"443\";\n}\n\nsub vcl_fetch {\n  set beresp.ttl = {{TTL}}s;\n}\n```\n\n**Why use constants?**\n\n- Single source of truth for configuration values\n- Easy to update across entire VCL\n- Type safety prevents mistakes\n- Self-documenting code\n\n### Template Expressions\n\nEmbed Python expressions in your VCL using `{{expression}}` syntax. Expressions are evaluated at preprocessing time.\n\n**Example:**\n\n```vcl\n#const PORT = 8080\n\nsub vcl_recv {\n  set req.http.X-Port = \"{{PORT}}\";\n  set req.http.X-Double = \"{{PORT * 2}}\";\n  set req.http.X-Hex = \"{{hex(PORT)}}\";\n}\n```\n\n**Output:**\n\n```vcl\nsub vcl_recv {\n  set req.http.X-Port = \"8080\";\n  set req.http.X-Double = \"16160\";\n  set req.http.X-Hex = \"0x1f90\";\n}\n```\n\n**Available functions:**\n\n- `range(n)` - Generate number sequences\n- `len(list)` - Get list length\n- `str(x)`, `int(x)` - Type conversions\n- `hex(n)` - Hexadecimal conversion\n- `format(x, fmt)` - Format values\n- `enumerate(iterable)` - Enumerate with indices\n- `min(...)`, `max(...)` - Min/max values\n- `abs(n)` - Absolute value\n\n**String formatting:**\n\n```vcl\n#const REGION = \"us-east\"\n#const INDEX = 1\n\nset req.backend = F_backend_{{REGION}}_{{INDEX}};\n```\n\n### For Loops\n\nGenerate repetitive VCL code by iterating over ranges or lists.\n\n**Syntax:**\n\n```vcl\n#for variable in iterable\n  // Code to repeat\n#endfor\n```\n\n**Example 1: Range-based loop**\n\n```vcl\n#for i in range(5)\nbackend web{{i}} {\n  .host = \"web{{i}}.example.com\";\n  .port = \"80\";\n}\n#endfor\n```\n\n**Output:**\n\n```vcl\nbackend web0 {\n  .host = \"web0.example.com\";\n  .port = \"80\";\n}\nbackend web1 {\n  .host = \"web1.example.com\";\n  .port = \"80\";\n}\n// ... continues through web4\n```\n\n**Example 2: List iteration**\n\n```vcl\n#const REGIONS = [\"us-east\", \"us-west\", \"eu-west\"]\n\n#for region in REGIONS\nbackend F_{{region}} {\n  .host = \"{{region}}.example.com\";\n  .port = \"443\";\n  .ssl = true;\n}\n#endfor\n```\n\n**Example 3: Nested loops**\n\n```vcl\n#const REGIONS = [\"us\", \"eu\"]\n#const ENVS = [\"prod\", \"staging\"]\n\n#for region in REGIONS\n  #for env in ENVS\nbackend {{region}}_{{env}} {\n  .host = \"{{env}}.{{region}}.example.com\";\n  .port = \"443\";\n}\n  #endfor\n#endfor\n```\n\n**Why use for loops?**\n\n- Generate multiple similar backends\n- Create ACL entries from lists\n- Build routing logic programmatically\n- Reduce copy-paste errors\n\n### Conditionals\n\nConditionally include or exclude code based on compile-time conditions.\n\n**Syntax:**\n\n```vcl\n#if condition\n  // Code when true\n#else\n  // Code when false (optional)\n#endif\n```\n\n**Example 1: Environment-specific configuration**\n\n```vcl\n#const PRODUCTION = True\n#const DEBUG = False\n\nsub vcl_recv {\n#if PRODUCTION\n  set req.http.X-Environment = \"production\";\n  unset req.http.X-Debug-Info;\n#else\n  set req.http.X-Environment = \"development\";\n  set req.http.X-Debug-Info = \"Enabled\";\n#endif\n\n#if DEBUG\n  set req.http.X-Request-ID = randomstr(16, \"0123456789abcdef\");\n#endif\n}\n```\n\n**Example 2: Feature flags**\n\n```vcl\n#const ENABLE_NEW_ROUTING = True\n#const ENABLE_RATE_LIMITING = False\n\nsub vcl_recv {\n#if ENABLE_NEW_ROUTING\n  call new_routing_logic;\n#else\n  call legacy_routing_logic;\n#endif\n\n#if ENABLE_RATE_LIMITING\n  if (ratelimit.check_rate(\"client_\" + client.ip, 1, 100, 60s, 1000s)) {\n    error 429 \"Too Many Requests\";\n  }\n#endif\n}\n```\n\n**Why use conditionals?**\n\n- Single codebase for multiple environments\n- Easy feature flag management\n- Dead code elimination (code in false branches isn't generated)\n- Compile-time optimization\n\n### Variables\n\nDeclare and initialize local variables in one step.\n\n**Syntax:**\n\n```vcl\n#let name TYPE = expression;\n```\n\n**Example:**\n\n```vcl\nsub vcl_recv {\n  #let timestamp STRING = std.time(now, now);\n  #let cache_key STRING = req.url.path + req.http.Host;\n\n  set req.http.X-Timestamp = var.timestamp;\n  set req.hash = var.cache_key;\n}\n```\n\n**Expands to:**\n\n```vcl\nsub vcl_recv {\n  declare local var.timestamp STRING;\n  set var.timestamp = std.time(now, now);\n  declare local var.cache_key STRING;\n  set var.cache_key = req.url.path + req.http.Host;\n\n  set req.http.X-Timestamp = var.timestamp;\n  set req.hash = var.cache_key;\n}\n```\n\n**Why use #let?**\n\n- Shorter syntax than separate declare + set\n- Clear initialization point\n- Reduces boilerplate\n\n### File Includes\n\nSplit large VCL files into modular, reusable components.\n\n**Syntax:**\n\n```vcl\n#include \"path/to/file.xvcl\"\n```\n\n**Example project structure:**\n\n```\nvcl/\n\u251c\u2500\u2500 main.xvcl\n\u251c\u2500\u2500 includes/\n\u2502   \u251c\u2500\u2500 backends.xvcl\n\u2502   \u251c\u2500\u2500 security.xvcl\n\u2502   \u2514\u2500\u2500 routing.xvcl\n```\n\n**`main.xvcl`:**\n\n```vcl\n#include \"includes/backends.xvcl\"\n#include \"includes/security.xvcl\"\n#include \"includes/routing.xvcl\"\n\nsub vcl_recv {\n  call security_checks;\n  call routing_logic;\n}\n```\n\n**`includes/backends.xvcl`:**\n\n```vcl\n#const BACKENDS = [\"web1\", \"web2\", \"web3\"]\n\n#for backend in BACKENDS\nbackend F_{{backend}} {\n  .host = \"{{backend}}.example.com\";\n  .port = \"443\";\n}\n#endfor\n```\n\n**Include path resolution:**\n\n1. Relative to the current file\n2. Relative to include paths specified with `-I`\n\n**Run with include paths:**\n\n```bash\nxvcl main.xvcl -o main.vcl -I ./vcl/includes\n```\n\n**Features:**\n\n- **Include-once semantics:** Files are only included once even if referenced multiple times\n- **Cycle detection:** Prevents circular includes\n- **Shared constants:** Constants defined in included files are available to the parent\n\n**Why use includes?**\n\n- Organize large VCL projects\n- Share common configurations across multiple VCL files\n- Team collaboration (different files for different concerns)\n- Reusable components library\n\n## Advanced Features\n\n### Inline Macros\n\nCreate zero-overhead text substitution macros. Unlike functions, macros are expanded inline at compile time with no runtime cost.\n\n**Syntax:**\n\n```vcl\n#inline macro_name(param1, param2, ...)\nexpression\n#endinline\n```\n\n**Example 1: String concatenation**\n\n```vcl\n#inline add_prefix(s)\n\"prefix-\" + s\n#endinline\n\n#inline add_suffix(s)\ns + \"-suffix\"\n#endinline\n\nsub vcl_recv {\n  set req.http.X-Modified = add_prefix(\"test\");\n  set req.http.X-Both = add_prefix(add_suffix(\"middle\"));\n}\n```\n\n**Output:**\n\n```vcl\nsub vcl_recv {\n  set req.http.X-Modified = \"prefix-\" + \"test\";\n  set req.http.X-Both = \"prefix-\" + \"middle\" + \"-suffix\";\n}\n```\n\n**Example 2: Common patterns**\n\n```vcl\n#inline normalize_host(host)\nstd.tolower(regsub(host, \"^www\\.\", \"\"))\n#endinline\n\n#inline cache_key(url, host)\ndigest.hash_md5(url + \"|\" + host)\n#endinline\n\nsub vcl_recv {\n  set req.http.X-Normalized = normalize_host(req.http.Host);\n  set req.hash = cache_key(req.url, req.http.Host);\n}\n```\n\n**Output:**\n\n```vcl\nsub vcl_recv {\n  set req.http.X-Normalized = std.tolower(regsub(req.http.Host, \"^www\\.\", \"\"));\n  set req.hash = digest.hash_md5(req.url + \"|\" + req.http.Host);\n}\n```\n\n**Example 3: Operator precedence handling**\n\nxvcl automatically handles operator precedence:\n\n```vcl\n#inline double(x)\nx + x\n#endinline\n\nsub vcl_recv {\n  declare local var.result INTEGER;\n  set var.result = double(5) * 10;  // Correctly expands to (5 + 5) * 10\n}\n```\n\n**Macros vs Functions:**\n\n| Feature       | Macros              | Functions                     |\n| ------------- | ------------------- | ----------------------------- |\n| Expansion     | Compile-time inline | Runtime subroutine call       |\n| Overhead      | None                | Subroutine call + global vars |\n| Return values | Expression only     | Single or tuple               |\n| Use case      | Simple expressions  | Complex logic                 |\n\n**When to use macros:**\n\n- String manipulation patterns\n- Simple calculations\n- Common expressions repeated throughout code\n- When you need zero runtime overhead\n\n**When to use functions:**\n\n- Complex logic with multiple statements\n- Need to return multiple values\n- Conditional logic or loops inside the reusable code\n\n### Functions\n\nDefine reusable functions with parameters and return values. Functions are compiled into VCL subroutines.\n\n**Syntax:**\n\n```vcl\n#def function_name(param1 TYPE, param2 TYPE, ...) -> RETURN_TYPE\n  // Function body\n  return value;\n#enddef\n```\n\n**Example 1: Simple function**\n\n```vcl\n#def add(a INTEGER, b INTEGER) -> INTEGER\n  declare local var.sum INTEGER;\n  set var.sum = a + b;\n  return var.sum;\n#enddef\n\nsub vcl_recv {\n  declare local var.result INTEGER;\n  set var.result = add(5, 10);\n  set req.http.X-Sum = var.result;\n}\n```\n\n**Example 2: String processing**\n\n```vcl\n#def normalize_path(path STRING) -> STRING\n  declare local var.result STRING;\n  set var.result = std.tolower(path);\n  set var.result = regsub(var.result, \"/$\", \"\");\n  return var.result;\n#enddef\n\nsub vcl_recv {\n  declare local var.clean_path STRING;\n  set var.clean_path = normalize_path(req.url.path);\n  set req.url = var.clean_path;\n}\n```\n\n**Example 3: Functions with conditionals**\n\n```vcl\n#def should_cache(url STRING) -> BOOL\n  declare local var.cacheable BOOL;\n\n  if (url ~ \"^/api/\") {\n    set var.cacheable = false;\n  } else if (url ~ \"\\.(jpg|png|css|js)$\") {\n    set var.cacheable = true;\n  } else {\n    set var.cacheable = false;\n  }\n\n  return var.cacheable;\n#enddef\n\nsub vcl_recv {\n  declare local var.can_cache BOOL;\n  set var.can_cache = should_cache(req.url.path);\n\n  if (var.can_cache) {\n    return(lookup);\n  } else {\n    return(pass);\n  }\n}\n```\n\n**Example 4: Tuple returns (multiple values)**\n\n```vcl\n#def parse_user_agent(ua STRING) -> (STRING, STRING)\n  declare local var.browser STRING;\n  declare local var.os STRING;\n\n  if (ua ~ \"Chrome\") {\n    set var.browser = \"chrome\";\n  } else if (ua ~ \"Firefox\") {\n    set var.browser = \"firefox\";\n  } else {\n    set var.browser = \"other\";\n  }\n\n  if (ua ~ \"Windows\") {\n    set var.os = \"windows\";\n  } else if (ua ~ \"Mac\") {\n    set var.os = \"macos\";\n  } else {\n    set var.os = \"other\";\n  }\n\n  return var.browser, var.os;\n#enddef\n\nsub vcl_recv {\n  declare local var.browser STRING;\n  declare local var.os STRING;\n\n  set var.browser, var.os = parse_user_agent(req.http.User-Agent);\n\n  set req.http.X-Browser = var.browser;\n  set req.http.X-OS = var.os;\n}\n```\n\n**Behind the scenes:**\n\nFunctions are compiled into VCL subroutines using global headers for parameter passing:\n\n```vcl\n// Your code:\nset var.result = add(5, 10);\n\n// Becomes:\nset req.http.X-Func-add-a = std.itoa(5);\nset req.http.X-Func-add-b = std.itoa(10);\ncall add;\nset var.result = std.atoi(req.http.X-Func-add-Return);\n```\n\nxvcl generates the `sub add { ... }` implementation and handles all type conversions automatically.\n\n**Function features:**\n\n- **Type safety:** Parameters and returns are type-checked\n- **Multiple returns:** Use tuple syntax to return multiple values\n- **Automatic conversions:** INTEGER/FLOAT/BOOL are converted to/from STRING automatically\n- **Scope annotations:** Generated subroutines work in all VCL scopes\n\n**Why use functions?**\n\n- Reusable complex logic\n- Reduce code duplication\n- Easier testing (test the function once)\n- Better code organization\n\n## Command-Line Usage\n\n**Basic usage:**\n\n```bash\nxvcl input.xvcl -o output.vcl\n```\n\n**Options:**\n\n| Option          | Description                                         |\n| --------------- | --------------------------------------------------- |\n| `input`         | Input xvcl source file (required)                   |\n| `-o, --output`  | Output VCL file (default: replaces .xvcl with .vcl) |\n| `-I, --include` | Add include search path (repeatable)                |\n| `--debug`       | Enable debug output with expansion traces           |\n| `--source-maps` | Add source map comments to output                   |\n| `-v, --verbose` | Verbose output (alias for --debug)                  |\n\n**Examples:**\n\n```bash\n# Basic compilation\nxvcl main.xvcl -o main.vcl\n\n# With include paths\nxvcl main.xvcl -o main.vcl \\\n  -I ./includes \\\n  -I ./shared\n\n# Debug mode (see expansion traces)\nxvcl main.xvcl -o main.vcl --debug\n\n# With source maps (track generated code origin)\nxvcl main.xvcl -o main.vcl --source-maps\n```\n\n**Automatic output naming:**\n\nIf you don't specify `-o`, xvcl replaces `.xvcl` with `.vcl`:\n\n```bash\n# These are equivalent:\nxvcl main.xvcl\nxvcl main.xvcl -o main.vcl\n```\n\n**Debug mode output:**\n\n```bash\n$ xvcl example.xvcl --debug\n\n[DEBUG] Processing file: example.xvcl\n[DEBUG] Pass 1: Extracting constants\n[DEBUG]   Defined constant: MAX_AGE = 3600\n[DEBUG] Pass 2: Processing includes\n[DEBUG] Pass 3: Extracting inline macros\n[DEBUG]   Defined macro: add_prefix(s)\n[DEBUG] Pass 4: Extracting functions\n[DEBUG] Pass 5: Processing directives and generating code\n[DEBUG]   Processing #for at line 10\n[DEBUG]     Loop iterating 3 times\n[DEBUG]       Iteration 0: backend = web1\n[DEBUG]       Iteration 1: backend = web2\n[DEBUG]       Iteration 2: backend = web3\n[DEBUG] Pass 6: Generating function subroutines\n\u2713 Compiled example.xvcl -> example.vcl\n  Constants: 1\n  Macros: 1 (add_prefix)\n  Functions: 0\n```\n\n## Integration with Falco\n\nxvcl generates standard VCL that you can use with Falco's full toolset.\n\n**Recommended workflow:**\n\n```bash\n# 1. Write your xvcl source\nvim main.xvcl\n\n# 2. Compile with xvcl\nxvcl main.xvcl -o main.vcl\n\n# 3. Lint with Falco\nfalco lint main.vcl\n\n# 4. Test with Falco\nfalco test main.vcl\n\n# 5. Simulate with Falco\nfalco simulate main.vcl\n```\n\n**Makefile integration:**\n\n```makefile\n# Makefile\n.PHONY: build lint test clean\n\nXVCL = xvcl\nSOURCES = $(wildcard *.xvcl)\nOUTPUTS = $(SOURCES:.xvcl=.vcl)\n\nbuild: $(OUTPUTS)\n\n%.vcl: %.xvcl\n\t$(XVCL) $< -o $@ -I ./includes\n\nlint: build\n\tfalco lint *.vcl\n\ntest: build\n\tfalco test *.vcl\n\nclean:\n\trm -f $(OUTPUTS)\n```\n\n**CI/CD integration:**\n\n```yaml\n# .github/workflows/vcl.yml\nname: VCL CI\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up Python\n        uses: actions/setup-python@v4\n        with:\n          python-version: '3.10'\n\n      - name: Install Falco\n        run: |\n          wget https://github.com/ysugimoto/falco/releases/latest/download/falco_linux_amd64\n          chmod +x falco_linux_amd64\n          sudo mv falco_linux_amd64 /usr/local/bin/falco\n\n      - name: Compile xvcl\n        run: |\n          xvcl main.xvcl -o main.vcl\n\n      - name: Lint VCL\n        run: falco lint main.vcl\n\n      - name: Test VCL\n        run: falco test main.vcl\n```\n\n**Testing compiled VCL:**\n\nYou can write Falco tests for your generated VCL:\n\n**`main.test.vcl`:**\n\n```vcl\n// @suite: Backend routing tests\n\n// @test: Should route to correct backend\nsub test_backend_routing {\n  set req.http.Host = \"web1.example.com\";\n  call vcl_recv;\n\n  assert.equal(req.backend, \"web1\");\n}\n```\n\nRun tests after compilation:\n\n```bash\nxvcl main.xvcl -o main.vcl\nfalco test main.vcl\n```\n\n## Best Practices\n\n### 1. Use the `.xvcl` extension\n\nMakes it clear which files are xvcl source files:\n\n```\n\u2713 main.xvcl \u2192 main.vcl\n\u2717 main.vcl \u2192 main.vcl.processed\n```\n\n### 2. Keep constants at the top\n\n```vcl\n// Good: Constants first, easy to find\n#const MAX_BACKENDS = 10\n#const PRODUCTION = True\n\n#for i in range(MAX_BACKENDS)\n  // ... use constant\n#endfor\n```\n\n### 3. Use descriptive constant names\n\n```vcl\n// Good\n#const CACHE_TTL_SECONDS = 3600\n#const API_BACKEND_HOST = \"api.example.com\"\n\n// Bad\n#const X = 3600\n#const B = \"api.example.com\"\n```\n\n### 4. Comment your macros and functions\n\n```vcl\n// Normalizes a hostname by removing www prefix and converting to lowercase\n#inline normalize_host(host)\nstd.tolower(regsub(host, \"^www\\.\", \"\"))\n#endinline\n\n// Parses User-Agent and returns (browser, os) tuple\n#def parse_user_agent(ua STRING) -> (STRING, STRING)\n  // ...\n#enddef\n```\n\n### 5. Prefer macros for simple expressions, functions for complex logic\n\n```vcl\n// Good: Simple expression = macro\n#inline cache_key(url, host)\ndigest.hash_md5(url + \"|\" + host)\n#endinline\n\n// Good: Complex logic = function\n#def should_cache(url STRING, method STRING) -> BOOL\n  declare local var.result BOOL;\n  if (method != \"GET\" && method != \"HEAD\") {\n    set var.result = false;\n  } else if (url ~ \"^/api/\") {\n    set var.result = false;\n  } else {\n    set var.result = true;\n  }\n  return var.result;\n#enddef\n```\n\n### 6. Use includes for organization\n\n```\nvcl/\n\u251c\u2500\u2500 main.xvcl          # Main entry point\n\u251c\u2500\u2500 config.xvcl        # Constants and configuration\n\u251c\u2500\u2500 includes/\n\u2502   \u251c\u2500\u2500 backends.xvcl\n\u2502   \u251c\u2500\u2500 security.xvcl\n\u2502   \u251c\u2500\u2500 routing.xvcl\n\u2502   \u2514\u2500\u2500 caching.xvcl\n```\n\n### 7. Version control both source and output\n\n```gitignore\n# Include both in git\n*.xvcl\n*.vcl\n\n# But gitignore generated files in CI\n# (if you regenerate on deploy)\n```\n\n**Or** only version control source files and regenerate on deployment:\n\n```gitignore\n# Version control xvcl source only\n*.xvcl\n\n# Ignore generated VCL\n*.vcl\n```\n\nChoose based on your deployment process.\n\n### 8. Add source maps in development\n\n```bash\n# Development: easier debugging\nxvcl main.xvcl -o main.vcl --source-maps\n\n# Production: cleaner output\nxvcl main.xvcl -o main.vcl\n```\n\nSource maps add comments like:\n\n```vcl\n// BEGIN INCLUDE: includes/backends.xvcl\nbackend F_web1 { ... }\n// END INCLUDE: includes/backends.xvcl\n```\n\n### 9. Test incrementally\n\nDon't write a massive source file and compile once. Test as you go:\n\n```bash\n# Write a bit\nvim main.xvcl\n\n# Compile\nxvcl main.xvcl\n\n# Check output\ncat main.vcl\n\n# Lint\nfalco lint main.vcl\n\n# Repeat\n```\n\n### 10. Use debug mode when things go wrong\n\n```bash\nxvcl main.xvcl --debug\n```\n\nShows exactly what xvcl is doing.\n\n## Troubleshooting\n\n### Error: \"Name 'X' is not defined\"\n\n**Problem:** You're using a variable or constant that doesn't exist.\n\n```vcl\n#const PORT = 8080\n\nset req.http.X-Value = \"{{PROT}}\";  // Typo!\n```\n\n**Error:**\n\n```\nError at main.xvcl:3:\n  Name 'PROT' is not defined\n  Did you mean: PORT?\n```\n\n**Solution:** Check spelling. xvcl suggests similar names.\n\n### Error: \"Invalid #const syntax\"\n\n**Problem:** Malformed constant declaration.\n\n```vcl\n#const PORT 8080        // Missing = sign\n#const = 8080           // Missing name\n#const PORT = STRING    // Missing value\n```\n\n**Solution:** Use correct syntax:\n\n```vcl\n#const PORT INTEGER = 8080\n```\n\n### Error: \"No matching #endfor for #for\"\n\n**Problem:** Missing closing keyword.\n\n```vcl\n#for i in range(10)\n  backend web{{i}} { ... }\n// Missing #endfor\n```\n\n**Solution:** Add the closing keyword:\n\n```vcl\n#for i in range(10)\n  backend web{{i}} { ... }\n#endfor\n```\n\n### Error: \"Circular include detected\"\n\n**Problem:** File A includes file B which includes file A.\n\n```\nmain.xvcl includes util.xvcl\nutil.xvcl includes main.xvcl\n```\n\n**Solution:** Restructure your includes. Create a shared file:\n\n```\nmain.xvcl includes shared.xvcl\nutil.xvcl includes shared.xvcl\n```\n\n### Error: \"Cannot find included file\"\n\n**Problem:** Include path is wrong or file doesn't exist.\n\n```vcl\n#include \"includes/backends.xvcl\"\n```\n\n**Solution:** Check path and use `-I` flag:\n\n```bash\nxvcl main.xvcl -o main.vcl -I ./includes\n```\n\n### Generated VCL has syntax errors\n\n**Problem:** xvcl generated invalid VCL.\n\n**Solution:**\n\n1. Check the generated output:\n   ```bash\n   cat main.vcl\n   ```\n\n2. Find the problematic section\n\n3. Trace back to source with `--source-maps`:\n\n   ```bash\n   xvcl main.xvcl -o main.vcl --source-maps\n   ```\n\n4. Fix the source file\n\n### Macro expansion issues\n\n**Problem:** Macro expands incorrectly.\n\n```vcl\n#inline double(x)\nx + x\n#endinline\n\nset var.result = double(1 + 2);\n// Expands to: (1 + 2) + (1 + 2)  \u2713 Correct\n```\n\nxvcl automatically adds parentheses when needed.\n\n**If you see issues:** Check operator precedence in your macro definition.\n\n### Function calls not working\n\n**Problem:** Function call doesn't get replaced.\n\n```vcl\n#def add(a INTEGER, b INTEGER) -> INTEGER\n  return a + b;\n#enddef\n\nset var.result = add(5, 10);\n```\n\n**Common causes:**\n\n1. **Missing semicolon:** Function calls must end with `;`\n   ```vcl\n   set var.result = add(5, 10);  // \u2713 Correct\n   set var.result = add(5, 10)   // \u2717 Won't match\n   ```\n\n2. **Wrong number of arguments:**\n   ```vcl\n   set var.result = add(5);      // \u2717 Expects 2 args\n   ```\n\n3. **Typo in function name:**\n   ```vcl\n   set var.result = addr(5, 10); // \u2717 Function 'addr' not defined\n   ```\n\n### Performance issues\n\n**Problem:** Compilation is slow.\n\n**Common causes:**\n\n1. **Large loops:** `#for i in range(10000)` generates 10,000 copies\n2. **Deep nesting:** Multiple nested loops or includes\n3. **Complex macros:** Heavily nested macro expansions\n\n**Solutions:**\n\n1. Reduce loop iterations if possible\n2. Use functions instead of generating everything inline\n3. Split into multiple source files\n4. Profile with `--debug` to see what's slow\n\n### Getting help\n\n**Check the error context:**\n\nErrors show surrounding lines:\n\n```\nError at main.xvcl:15:\n  Invalid #for syntax: #for in range(10)\n\n  Context:\n    13: sub vcl_recv {\n    14:   // Generate backends\n  \u2192 15:   #for in range(10)\n    16:     backend web{{i}} { ... }\n    17:   #endfor\n    18: }\n```\n\n**Enable debug mode:**\n\n```bash\nxvcl main.xvcl --debug\n```\n\n**Validate generated VCL:**\n\n```bash\nfalco lint main.vcl -vv\n```\n\nThe `-vv` flag shows detailed Falco errors.\n\n---\n\n## Examples Gallery\n\nHere are complete, working examples you can use as starting points.\n\n### Example 1: Multi-region backends\n\n**`multi-region.xvcl`:**\n\n```vcl\n#const REGIONS = [\"us-east\", \"us-west\", \"eu-west\", \"ap-south\"]\n#const DEFAULT_REGION = \"us-east\"\n\n#for region in REGIONS\nbackend F_origin_{{region}} {\n  .host = \"origin-{{region}}.example.com\";\n  .port = \"443\";\n  .ssl = true;\n  .connect_timeout = 5s;\n  .first_byte_timeout = 30s;\n  .between_bytes_timeout = 10s;\n}\n#endfor\n\nsub vcl_recv {\n  declare local var.region STRING;\n\n  // Detect region from client IP or header\n  if (req.http.X-Region) {\n    set var.region = req.http.X-Region;\n  } else {\n    set var.region = \"{{DEFAULT_REGION}}\";\n  }\n\n  // Route to appropriate backend\n#for region in REGIONS\n  if (var.region == \"{{region}}\") {\n    set req.backend = F_origin_{{region}};\n  }\n#endfor\n}\n```\n\n### Example 2: Feature flag system\n\n**`feature-flags.xvcl`:**\n\n```vcl\n#const ENABLE_NEW_CACHE_POLICY = True\n#const ENABLE_WEBP_CONVERSION = True\n#const ENABLE_ANALYTICS = False\n#const ENABLE_DEBUG_HEADERS = False\n\nsub vcl_recv {\n#if ENABLE_NEW_CACHE_POLICY\n  // New cache policy with fine-grained control\n  if (req.url.path ~ \"\\.(jpg|png|gif|css|js)$\") {\n    set req.http.X-Cache-Policy = \"static\";\n  } else {\n    set req.http.X-Cache-Policy = \"dynamic\";\n  }\n#else\n  // Legacy cache policy\n  set req.http.X-Cache-Policy = \"default\";\n#endif\n\n#if ENABLE_WEBP_CONVERSION\n  if (req.http.Accept ~ \"image/webp\") {\n    set req.http.X-Image-Format = \"webp\";\n  }\n#endif\n\n#if ENABLE_ANALYTICS\n  set req.http.X-Analytics-ID = uuid.generate();\n#endif\n}\n\nsub vcl_deliver {\n#if ENABLE_DEBUG_HEADERS\n  set resp.http.X-Cache-Status = resp.http.X-Cache;\n  set resp.http.X-Backend = req.backend;\n  set resp.http.X-Region = req.http.X-Region;\n#endif\n}\n```\n\n### Example 3: URL normalization library\n\n**`url-utils.xvcl`:**\n\n```vcl\n// Inline macros for common URL operations\n#inline strip_www(host)\nregsub(host, \"^www\\.\", \"\")\n#endinline\n\n#inline lowercase_host(host)\nstd.tolower(host)\n#endinline\n\n#inline normalize_host(host)\nlowercase_host(strip_www(host))\n#endinline\n\n#inline remove_trailing_slash(path)\nregsub(path, \"/$\", \"\")\n#endinline\n\n#inline remove_query_string(url)\nregsub(url, \"\\?.*$\", \"\")\n#endinline\n\n// Function for complex normalization\n#def normalize_url(url STRING, host STRING) -> STRING\n  declare local var.result STRING;\n  declare local var.clean_host STRING;\n  declare local var.clean_path STRING;\n\n  set var.clean_host = normalize_host(host);\n  set var.clean_path = remove_trailing_slash(url);\n  set var.result = \"https://\" + var.clean_host + var.clean_path;\n\n  return var.result;\n#enddef\n\nsub vcl_recv {\n  declare local var.canonical_url STRING;\n  set var.canonical_url = normalize_url(req.url.path, req.http.Host);\n  set req.http.X-Canonical-URL = var.canonical_url;\n}\n```\n\n### Example 4: A/B testing framework\n\n**`ab-testing.xvcl`:**\n\n```vcl\n#const EXPERIMENTS = [\n  (\"homepage_hero\", 50),\n  (\"checkout_flow\", 30),\n  (\"pricing_page\", 25)\n]\n\n#def assign_experiment(exp_id STRING, percentage INTEGER) -> BOOL\n  declare local var.hash STRING;\n  declare local var.value INTEGER;\n  declare local var.assigned BOOL;\n\n  set var.hash = digest.hash_md5(client.ip + exp_id);\n  set var.value = std.atoi(substr(var.hash, 0, 2)) % 100;\n  set var.assigned = (var.value < percentage);\n\n  return var.assigned;\n#enddef\n\nsub vcl_recv {\n  declare local var.in_experiment BOOL;\n\n#for exp_id, percentage in EXPERIMENTS\n  set var.in_experiment = assign_experiment(\"{{exp_id}}\", {{percentage}});\n  if (var.in_experiment) {\n    set req.http.X-Experiment-{{exp_id}} = \"variant\";\n  } else {\n    set req.http.X-Experiment-{{exp_id}} = \"control\";\n  }\n#endfor\n}\n```\n\n---\n\n## Summary\n\nxvcl extends Fastly VCL with powerful programming constructs:\n\n**Core features:**\n\n- Constants - Single source of truth for configuration\n- Template expressions - Dynamic value substitution\n- For loops - Generate repetitive code\n- Conditionals - Environment-specific builds\n- Variables - Cleaner local variable syntax\n- Includes - Modular code organization\n\n**Advanced features:**\n\n- Inline macros - Zero-overhead text substitution\n- Functions - Reusable logic with return values\n\n**Benefits:**\n\n- Less code duplication\n- Fewer copy-paste errors\n- Better maintainability\n- Easier testing\n- Faster development\n\n**Integration:**\n\n- Works with Falco's full toolset\n- Standard VCL output\n- No runtime overhead\n- Easy CI/CD integration\n\nStart simple, add complexity as needed. xvcl grows with your VCL projects.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Extended VCL compiler with metaprogramming features for Fastly VCL",
    "version": "2.5.0",
    "project_urls": null,
    "split_keywords": [
        "compiler",
        " fastly",
        " preprocessor",
        " varnish",
        " vcl"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d8ffcc8473b6131648a1a7a1b8efd85d6273a6e510d76ec3e7f0842fea353a7f",
                "md5": "4e8f8d176e21180bb6c35021b0d70c45",
                "sha256": "fcc72f42eff445c450bac2de7e880a0ee6414a29b83bba290754427f24f2cc74"
            },
            "downloads": -1,
            "filename": "xvcl-2.5.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4e8f8d176e21180bb6c35021b0d70c45",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 24000,
            "upload_time": "2025-10-11T09:16:24",
            "upload_time_iso_8601": "2025-10-11T09:16:24.316292Z",
            "url": "https://files.pythonhosted.org/packages/d8/ff/cc8473b6131648a1a7a1b8efd85d6273a6e510d76ec3e7f0842fea353a7f/xvcl-2.5.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4615599be23003f62ffbf09a37488ca781c8a816529a1f81ce393a90b51bc58a",
                "md5": "02a42d53085e6790be5b382986f4210d",
                "sha256": "7d2192a11c2ed37ec805cd6f3504ce08bb057f2bc9bdf56bab77d737c1fb2aa3"
            },
            "downloads": -1,
            "filename": "xvcl-2.5.0.tar.gz",
            "has_sig": false,
            "md5_digest": "02a42d53085e6790be5b382986f4210d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 56245,
            "upload_time": "2025-10-11T09:16:25",
            "upload_time_iso_8601": "2025-10-11T09:16:25.500066Z",
            "url": "https://files.pythonhosted.org/packages/46/15/599be23003f62ffbf09a37488ca781c8a816529a1f81ce393a90b51bc58a/xvcl-2.5.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-11 09:16:25",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "xvcl"
}
        
Elapsed time: 1.50997s