Raven Macro Library
Raven.Macros is the standard compiler-plugin library distributed with Raven.
It contains reusable macros such as quote, compile, timer, json, xml, and the
attached Error macro without making them intrinsic compiler declarations or
members of Raven.Core. These macros demonstrate the broader purpose of the
feature: concise forms can hide repetitive or domain-specific expansion code
and grow into DSLs that integrate naturally with Raven.
The standard library is also a proving ground rather than a permanent home for every useful DSL. A macro family can move into its own package when it needs an independent API, dependency set, compatibility policy, or release cadence; Raven's carrier and application model remains the same.
Applications opt into the short aliases by importing the macro namespace:
import Raven.Macros.*
let syntax = quote! {
left + right
}
The canonical names Raven.Macros.Quote! and Raven.Macros.Compile! remain
available when an alias is shadowed or the wildcard namespace is not imported.
Merely referencing the standard library does not place its aliases in lexical
scope.
#[Error] derives Raven's ordinary System.IError interface for a union. It
adds default Message and Cause properties only when the union does not
already declare them:
import Raven.Macros.*
#[Error]
union ParseError {
#[ErrorMessage("Invalid value: $value")]
case InvalidValue(value: string)
#[ErrorMessage("A value is required")]
case MissingValue
}
ErrorMessage accepts Raven expression syntax. In the common string form,
ordinary Raven interpolation can refer to the payload names of that case; no
macro-specific formatting language is involved. A case without
ErrorMessage falls back to the union's normal case-aware string
representation. Its validation is implemented in Raven: the attached macro
inspects the authored expression and containing union, reports diagnostics for
invalid use, and returns an empty expansion because Error consumes the
annotation when it derives the union implementation.
Conceptually, the macros above expand to ordinary Raven code:
union ParseError: System.IError {
case InvalidValue(value: string)
case MissingValue
val Message: string => self match {
InvalidValue(let value) => "Invalid value: $value"
MissingValue => "A value is required"
_ => self.ToString()
}
val Cause: System.IError? => null
}
The expansion shown here explains the behavior rather than promising an exact
lowered syntax-tree shape. Error adds Message or Cause only when the union
does not already declare that property, so an authored implementation always
takes precedence. ErrorMessage is valid only on a case nested in an
#[Error] union and accepts a string literal or interpolated string.
timer!
timer! removes the usual Stopwatch setup and cleanup around a block of Raven
statements:
import Raven.Macros.*
timer! {
let index = LoadIndex()
Rebuild(index)
Save(index)
}
An expression-header form accepts an optional message template:
let indexName = "products"
timer! "$indexName index rebuilt in {time}" {
RebuildIndex(indexName)
}
$indexName and ${expression} retain Raven's ordinary caller-scope string
interpolation. {time} is deliberately a separate timer placeholder so it
cannot capture a caller variable named time.
Conceptually, it expands to the following boilerplate:
{
let __stopwatch = System.Diagnostics.Stopwatch.StartNew()
try {
{
let index = LoadIndex()
Rebuild(index)
Save(index)
}
}
finally {
__stopwatch.Stop()
System.Console.WriteLine(__stopwatch.Elapsed)
}
}
The macro parses its token body as an ordinary Raven block, preserves that
block's lexical scope, and emits the elapsed duration after the body finishes.
It also publishes the body as a block fragment, so hover and other ordinary
Raven editor features remain available within the braces.
It uses try/finally so timing also stops when control leaves the body early.
The actual stopwatch name is generated to avoid collisions; this expansion is
illustrative rather than an exact syntax-tree contract. Release builds report
TIMER002 to make accidental instrumentation visible.
query!
query! is a small LINQ-style token-tree DSL included in Raven.Macros. It
supports one from clause, an optional where clause, and one select clause:
import Raven.Macros.*
let projected = query! {
from value in [1, 2, 3, 4]
where value > 2
select value * 10
}
The macro expands to ordinary Where and Select calls with Raven lambdas.
The source, predicate, and projection are parsed as ordinary Raven expression
fragments. The range variable is projected into those fragments for completion
and hover, so editor support does not depend on a query-specific syntax tree.
The current query macro is intentionally small. It demonstrates how a reusable DSL can combine custom tokens, Raven expression fragments, introduced locals, diagnostics, and ordinary generated code without adding query syntax to the language grammar.
json! and xml!
The standard data-literal macros embed JSON- and XML-shaped bodies while producing the platform's ordinary in-memory models:
let payload = json! {
"name": "$name",
"age": $age,
"nextAge": ${age + 1}
}
let element = xml! {
<person age="$age">
<name>$name</name>
$statusElement
</person>
}
json! promises JsonObject; its carrier braces are also the object's braces.
xml! promises XElement and accepts one root element. $identifier and
${expression} are ordinary Raven interpolation/splice forms rather than a
second macro-specific expression syntax. JSON value splices use
JsonSerializer; XML content is passed through LINQ to XML so text is escaped
and an inserted XElement remains a node. JSON formatting whitespace is
discarded by parsing. XML indentation-only text is likewise omitted from the
constructed value; meaningful inline text remains content.
Both macros report each embedded expression as a Raven fragment and expose a
position-preserving json or xml projection for editor hosts. Caller-scope
hover, completion, navigation, and diagnostics therefore own splice positions,
while an embedded-language service can own the surrounding data syntax. The
macros validate the masked literal with the platform JSON/XML parser during
compilation, so malformed escapes, entities, delimiters, and nesting are
reported at the authored body instead of becoming runtime string failures. The
initial XML grammar deliberately diagnoses qualified names; namespace support
is a follow-up requiring an explicit XNamespace mapping contract.
Authoring model
The project under src/Raven.Macros is written in Raven. Each public macro has
its own source file, uses macro, and carries Markdown documentation
comments. AssemblyInfo.rvn marks the output as a compiler plugin:
[assembly: RavenCompilerPlugin]
Standard macro implementations move into this Raven project incrementally
instead of depending indefinitely on implementation helpers in the compiler.
Each port is deliberate language and API dogfooding: awkward
syntax construction, missing semantic operations, hidden compiler hooks, or a
required C# escape hatch should be treated as evidence of a Raven compiler or
macro-authoring problem to diagnose and improve. timer is implemented wholly
in Raven.Macros. embedFileContent also runs wholly from Raven and uses the
public dependency-tracked file-reading API. sha256Digest consumes the public
constant information already carried by its MacroArgument. The Error and
ErrorMessage pair also run wholly from Raven: they inspect attached syntax,
report diagnostics, rewrite a base list, and introduce generated properties.
The JSON and XML macros are likewise Raven-authored and exercise typed result
contracts, private DSL parsing, expression fragments, and embedded-language
projections. The remaining older standard macros are migration work.
When a marked Raven library contains macro declarations, emission lowers those declarations into reusable provider types and includes them in the plugin assembly. This is the same general assembly-plugin mechanism available to other Raven macro libraries; the standard macros do not require a built-in macro category.
Dependency and compatibility model
Macro implementations program against Raven.CodeAnalysis, so a macro library
has an ordinary dependency on a particular compiler-contract version. The
compiler loads the macro provider with its dependency closure and the consuming
compilation receives metadata references from that closure. Runtime
dependencies are copied only when the emitted application actually references
them.
Macro authors are responsible for building against a Raven.CodeAnalysis
version compatible with the compiler host that loads the plugin. The loader
must reject incompatible contracts with a diagnostic rather than allowing a
late type-load or invocation failure.
Current implementation boundary
Quote.rvn owns expression parsing, splice recognition, source-mapped
diagnostics, and syntax-factory rendering through public macro and syntax APIs.
Compile.rvn reuses that Raven-authored quote operation before constructing
the runtime-compilation call. Error.rvn, ErrorMessage.rvn, and the other
standard macros are likewise implemented wholly in Raven. The compiler owns
the reusable contracts—position-preserving projected parsing, source-aware
diagnostics, and RavenQuoter—rather than the policy of individual standard
macros.
Documentation publishing
The Raven.Macros project emits both XML documentation and Markdown documentation artifacts. The documentation build publishes the standalone Raven.Macros API reference generated by RavenDoc alongside the Raven.Core API reference. These remain independent RavenDoc sites within the same published Pages artifact as the language documentation and Playground.
Macro authors may cross the boundary into Raven.CodeAnalysis. Use the
macro authoring guide for the supported public
workflow. Compiler API implementation material remains in the repository's
development documentation.