meld
Version:
Meld: A template language for LLM prompts
654 lines (562 loc) • 19 kB
Markdown
<GrammarSpecDocumentation>
IMPORTANT NOTE: We are NOT implementing @api and @call yet.
UPDATE: The syntax below for ${var} and #{var} is outdated. Text and data variables are expressed as {{variable}} and path variables remain $path style.
# Meld Grammar Specification
Meld is a very simple and constrained scripting language for use in the middle of markdown-like docs. We only interpret @directive lines. All other lines are treated as literal text, including lines inside backtick fences.
## Core Tokens
### Directives
Must appear at start of line (no indentation):
```
@embed
@run
@import
@define
@text
@path
@data
@api
@call
```
### Comments
Lines that begin with `>> ` (two greater-than signs followed by a space) are treated as comments:
```meld
>> This is a comment
>> Comments must start at beginning of line (no indentation)
@text message = "Hello" >> Invalid - comments must be on their own line
```
- Must appear at start of line (no indentation)
- Everything after `>> ` on that line is ignored
- Cannot be added to the end of directive lines
- Preserves comments exactly as written (no interpretation of directives, variables, or special characters)
### Delimiters
```
[ ] Command/path boundaries
[[ ]] Multi-line content boundaries
{ } Function embed boundaries
[\n ] Multi-line array
[\n ] Multi-line object
# Section marker
= Assignment (requires spaces on both sides)
. Metadata/field accessor
, List separator
>> Format operator
() Command parameter list
: Schema reference operator (optional)
++ String concatenation operator (requires spaces on both sides)
```
### String Values
- Must be quoted with ', ", or `
- Quotes must match (no mixing)
- Backslashes and quotes within strings are treated as literal characters
- Must be single line (no newlines allowed)
- Can contain any characters except newlines
### Command Definitions
```
@define command = @run [content]
@define command(param1, param2) = @run [content {{param1}} {{param2}}]
@run [$command]
@run [$command({{textvar1}}, {{textvar2}})]
```
- Command names must be valid identifiers
- Parameters are optional
- When parameters are used:
- Must be valid identifiers
- Must be referenced in command body
- The right-hand side of @define must be an @run directive
- Cannot use other directives (@embed, @text, etc.) as the command body
Invalid examples:
```
@define cmd = "hello" # Not an @run directive
@define cmd = @embed [file.md] # Must be @run, not @embed
```
### Variable Types
Meld has three distinct types of variables:
Path Variables:
- Syntax: $identifier (e.g., $path, $HOMEPATH, $~)
- Used for filesystem paths and command arguments
- Can appear anywhere within [] brackets
- No field access or formatting
- Special variables $HOMEPATH/$~ and $PROJECTPATH/$. can be used with path separators
Text and Data Variables:
- Syntax: {{identifier}} (e.g., {{message}}, {{description}}, {{config}}, {{response}})
- Store unstructured text (text variables) or structured data (data variables)
- Support field access for data variables ({{config.name}})
- Can be formatted with >>
- Environment variables ({{ENV_*}}) are a special case of text variables
### Variable Type Conversion
Text and data variables can be used interchangeably in many contexts, with automatic conversion:
Data to Text Conversion:
- Simple values (strings, numbers) convert directly to text
- Objects and arrays convert to JSON string representation
- Useful in template literals and string concatenation
Examples:
```meld
@data config = { name: "test", version: 1 }
@data nested = { user: { name: "Alice" } }
@text simple = `Name: {{config.name}}` # Outputs: Name: test
@text object = `Config: {{config}}` # Outputs: Config: {"name":"test","version":1}
@text deep = `User: {{nested.user}}` # Outputs: User: {"name":"Alice"}
```
Text in Data Contexts:
- Text variables can be used as values in data structures
- Text variables can be used as object keys
- Values are inserted as strings
Examples:
```meld
@text name = "Alice"
@text key = "username"
@data user = {
{{key}}: {{name}}, # Dynamic key from text
id: {{userId}}, # Text value in data structure
settings: {
displayName: {{name}} # Nested text value
}
}
```
### Variables
Variable references in different contexts:
```
{{variable}} Variable reference
{{variable>>(format)}} Formatted variable
{{datavar.field}} Data variable field access
{{datavar.field>>(format)}} Formatted data field access
$command({{param1}}, {{param2}}) Command reference with parameters
$path Path variable reference
$HOMEPATH or $~ Special path variable (equivalent)
$PROJECTPATH or $. Special path variable (equivalent)
```
Variable references are allowed in:
- Inside square brackets [...] for paths and commands
- Inside object literals {{...}} and single-line objects
- Inside template literals (backtick strings) for string interpolation
- Inside directive values after = (including object literals and template literals)
They are NOT allowed in:
- Plain text lines
- Regular string literals (use template literals instead)
- Outside of the contexts listed above
Rules for specific variable types:
- Path variables ($path) only allowed in path contexts
- Variables ({{variable}}) allowed in all interpolation contexts
- Data field access ({{data.field}}) allowed in all interpolation contexts except command parameters
### Code Fences
Triple or more backticks that:
- Must appear at start of line
- Can optionally be followed by a language identifier
- Must be closed with exactly the same number of backticks
- Can contain any content (including directives, variables, etc.) which is treated as literal text
- Support nesting with different numbers of backticks
- Preserve all whitespace and newlines exactly as written
- Note: Code fences (3 or more backticks) are distinct from backtick string literals (single backticks). See "String Values" section for details on string literals.
Examples:
```python
@text x = 1 # treated as literal text
```
````
nested fence with
``` incomplete nest inside
````
````
nested fence with
```
complete nested code fence inside
```
````
```javascript
const x = ${textvar}
# variables not interpolated
```
All content within code fences is preserved exactly as written with no interpretation of:
- Directives
- Variables
- Special characters
- Delimiters
## Directive Patterns
### @embed
```
@embed [path]
@embed [path # section_text]
@embed [path] as ### # ### parsed as count of # chars
@embed [path # section_text] as ###
@embed [path] under header_text
```
where:
- section_text is non-empty text after # until closing bracket
- name is a valid identifier
- path cannot be empty
- whitespace is optional inside {} and around ,
- Value must be quoted with ', ", or `
- Quotes must match (no mixing)
### @run
```
@run [command_text]
@run [command_text] under header_text
@run [$command({{textvar1}}, {{textvar2}})]
```
where:
- command_text cannot be empty
- command_text can contain spaces and quotes (', ", `)
- command_text can contain:
- Variables ({{variable}})
- Path variables ($path)
- Special path variables ($HOMEPATH/$~, $PROJECTPATH/$.)
- command_text can contain nested brackets (treated as text)
- command references must include parameters
### @import
```
@import [path]
```
where:
- path cannot be empty
- path can contain nested brackets (treated as text)
### @define
```
@define identifier = @run [content]
@define command(param1, param2) = @run [content {{param1}} {{param2}}]
```
where:
- content follows @run patterns
- field is limited to ONLY .risk, .risk.high, .risk.med, .risk.low, .about, .meta
- identifier cannot be empty
- field cannot be empty
- command parameters must be valid identifiers
- at least one parameter required for commands
### @text
```meld
@text identifier = "value"
@text identifier = @embed [content]
@text identifier = @run [command]
@text identifier = @call api.method [path]
```
where:
- value must be either:
- A quoted string, or
- String result of @embed directive, or
- String result of @run directive, or
- String result of @call directive
- identifier cannot be empty
### @path
```meld
@path identifier = "$HOMEPATH/path"
@path identifier = "$~/path"
@path identifier = "$PROJECTPATH/path"
@path identifier = "$./path"
```
where:
- Must start with special path variable
- Path segments follow normal path rules
- Cannot start with raw path
- Path segments separated by /
- identifier cannot be empty
- path cannot be empty
- In quotes of any kind as long as they match
### @data
```meld
@data identifier = value
@data identifier : schema = value
```
where:
- value can be:
- Object literal {...}
- Array literal [...]
- String literal
- Result of @embed directive
- Result of @run directive
- Result of @call directive
- schema is optional identifier reference
- Objects can nest
- Arrays can contain any valid value type
### @api
```meld
@api identifier = value
@api identifier.endpoint = value
```
where:
- value must be an API configuration object
- Base API configuration requires:
- baseUrl: string (required)
- All other fields are optional (headers, etc.)
- Endpoint definitions are optional and can include:
- path: string
- methods: array of HTTP methods
- identifier must be valid identifier
- endpoint must be valid identifier
- Can define base APIs and specific endpoints
Examples:
```meld
# Minimal API definition
@api github = {
baseUrl: "https://api.github.com"
}
# Full API definition with optional fields
@api github = {
baseUrl: "https://api.github.com",
headers: {
Authorization: "Bearer {{ENV_TOKEN}}"
}
}
# GET request
@data issues = @call github.issues.get
# POST request with payload
@data newIssue = @call github.issues.post {
title: ${title},
body: ${description}
}
# Direct path usage
@data repo = @call github.get [/repos/${owner}/${repo}]
# Get response as text
@text response = @call github.issues.get
# Using data variables with response
@data responseData = @call github.issues.get
@text summary = `Found #{responseData.total} issues`
```
### @call
```meld
@call identifier.method [path]
@call identifier.method [path] {
key: value,
nested: {
key: value
}
}
@call identifier.endpoint.method
```
where:
- identifier must reference defined @api
- method must be valid HTTP method (GET, POST, PUT, PATCH, DELETE)
- path is optional if endpoint is defined
- payload object is optional
## Syntax Elements
### Identifiers
- Must start with letter or underscore
- Can contain letters, numbers, underscore
- Case-sensitive
- Cannot be empty
### Paths
- Special path variables $~ (aliased $HOMEPATH), $. (aliased $PROJECTPATH) must be followed by / when used for paths
- Can contain any characters used in paths
- Forward slashes as separators when used in paths
- Cannot be empty
Examples:
```meld
@embed [$docs] # Path vars without separators
@run [cpai $docs --stdout] # Path var in command args
@path mypath = "$HOMEPATH/path" # Special path var with separator
```
### Field Access
Two distinct types of field access in meld:
Command Metadata Fields:
- Special case for @define directives only
- Used for documentation and security
- Restricted to specific fields:
- .risk, .risk.high, .risk.med, .risk.low
- .about
- .meta
Data Variable Fields:
- Only available on data variables (#{data.field})
- No restrictions on field names (must be valid identifiers)
- Not available on text or path variables
Valid examples:
```
#{datavar.field}
#{datavar.nested.field}
#{datavar.deeply.nested.field}
@define cmd.risk = "string"
@define cmd.risk.high = "string"
@define cmd.risk.med = "string"
@define cmd.risk.low = "string"
@define cmd.about = "string"
@define cmd.meta = "string"
```
Invalid examples:
```
${textvar.field1} # textvars do not have fields
#{var.}
#{var.field1.field2.field3.field4}
#{.field}
@define cmd.invalid = @run [value]
@define cmd.risk = @run [value]
```
### Command References
Inside [] brackets ONLY:
```
$command({{param1}}, {{param2}}) Command with parameters
```
Rules:
- Must be defined via @define
- Must include parameters
- Parameters must be text variables ({{param}})
- No whitespace in command name
- Spaces allowed after commas
### Variable Interpolation
Inside [...] and {{...}} contexts only:
```
{{textvar}} Text variable reference
{{textvar>>(format)}} Formatted text variable
{{datavar}} Data variable reference
{{datavar.field}} Data field access
{{datavar.field>>(format)}} Formatted data field
$path Path variable reference
$HOMEPATH or $~ Special path variable (equivalent)
$PROJECTPATH or $. Special path variable (equivalent)
```
Rules:
- Path variables ($path) only allowed in path contexts
- Variables ({{variable}}) allowed in all interpolation contexts
- Data field access ({{data.field}}) allowed in all interpolation contexts except command parameters
- @path-defined variables must occur after `[` or ` ` (whitespace) and must be followed by `/`
- No nested interpolation ({{textvar{{inner}}}} or {{datavar{{inner}}}})
- No whitespace around >> operator
- Format must be last operation
- Only one format allowed per variable
- Formatting only allowed inside {{}}
- Path variables cannot use field access or formats
Invalid patterns:
```
"text with {{textvar}}" # No variables in regular strings
Text with {{textvar}} # No variables in plain text
{{textvar{{inner}}}} # No nested text variables
{{data{{inner}}}} # No nested data variables
$path.field # No field access on path vars
$path>>(format) # No format on path vars
```
### Format Specifications
Format operators must be inside the variable braces:
```
{{textvar>>(format)}} Text variable format
{{datavar>>(format)}} Data variable format
{{datavar.field>>(format)}} Data field format
```
Rules:
- Format operator must be inside {{}} braces
- No whitespace around >>
- No format chaining (only one format per variable)
- Format must be the last operation in the variable reference
- Only available for text and data variables (not path variables)
Invalid patterns:
```
$var>>(format) # Must be inside {{}}
{{textvar}}>>(format) # Format must be inside braces
{{textvar>>(format1)>>(format2)}} # No format chaining
{{datavar>>(format).field}} # Format must be last operation
{{textvar >> (format)}} # No whitespace around >>
```
### String Concatenation
- Uses ++ operator with required spaces on both sides
- Can concatenate:
- String literals
- Template literals
- Variables ({{variable}})
- Result of @embed directives
- Cannot concatenate:
- Arrays or objects
- Complex data structures (use template literals instead)
- Must be single line (use template literals for multi-line)
Examples:
```meld
@text greeting = "Hello" ++ " " ++ "World"
@text message = {{intro}} ++ {{body}}
@text doc = @embed [header.md] ++ @embed [content.md]
```
Invalid patterns:
```meld
@text bad = "no"++"spaces" # Missing spaces around ++
@text bad = {{data}} ++ "text" # Cannot concat complex data variables
@text bad = "multi" ++ # Cannot split across lines
"line"
```
### API Examples
```meld
# Define base API
@api github = {
baseUrl: "https://api.github.com",
headers: {
Authorization: "Bearer ${ENV_TOKEN}"
}
}
# Define specific endpoints
@api github.issues = {
path: "/repos/${owner}/${repo}/issues",
methods: ["GET", "POST"]
}
# GET request
@data issues = @call github.issues.get
# POST request with payload
@data newIssue = @call github.issues.post {
title: ${title},
body: ${description}
}
# Direct path usage
@data repo = @call github.get [/repos/${owner}/${repo}]
# Get response as text
@text response = @call github.issues.get
# Using data variables with response
@data responseData = @call github.issues.get
@text summary = `Found #{responseData.total} issues`
```
Template Literals:
- Delimited by backticks (`)
- Can contain ${var} interpolation
- Can be multi-line when wrapped in [[` and `]]
- Can contain both text and data variables
- Can contain any quotes without escaping
Examples:
```meld
# Single-line template literals
`Hello ${name}!` # Text variable
`Config: #{config.name}` # Data variable with field
`${greeting}, your ID is #{user.id}` # Mixed variables
# Multi-line template literal
@text prompt = [[`
System: ${role}
Context:
#{context.data}
User: ${username}
Settings: #{user.preferences}
`]]
```
<Clarifications>
## UX Decisions Augmenting GRAMMAR SPEC
### Imports
- Must appear at top of file
- Support both explicit imports and * import
- Pattern: `import [x,y,z] from [file.md]` or `import [x as y] from [file.md]`
- `import [file.md]` is shorthand for `import [*] from [file.md]`
### Error Handling Philosophy
Meld has three categories of error handling:
#### Fatal Errors (Halt Execution)
- Missing or inaccessible referenced files
- Invalid syntax in meld files
- Invalid file extensions
- Circular imports
- Type mismatches (using wrong variable type)
- Missing required command parameters
- Invalid path references (not using $HOMEPATH/$PROJECTPATH)
#### Warning Errors (Continue with Warning)
- Missing optional fields in data structures (return empty string)
- Missing environment variables (when referenced)
- Command execution that produces stderr but exits with code 0
- Fields accessed on non-existent data paths (return empty string)
#### Silent Operation (No Error/Warning)
- Expected stderr output from successfully running commands
- Empty or partial results from valid operations
- Type coercion in string concatenation
- Normal command output to stderr
### Variables & Environment
- No restrictions on ENV var names
- ENV vars generate errors only when referenced and missing
- Field access on non-existent fields/primitives returns empty string
- Non-string values coerced in string concatenation
### Paths & Files
- All paths must be absolute (via $HOMEPATH/$PROJECTPATH)
- Working directory only affects initial $PROJECTPATH
- Relative paths not allowed for security
- Circular imports detected and errored pre-execution
### Command Parameters
- All parameters required for v1
- Header text supports variable interpolation
### Style Handling
- Common indentation removal handled by grammar
- Delimiter escaping handled by grammar
- Markdown header interpretation handled by grammar
</Clarifications>
</GrammarSpecDocumentation>