.NET port of Anvil Engine β the zero-copy, typeless, blazing-fast data language relegating JSON, YAML, and TOML to the dustbin of embarrassing legacy mistakes. Never settle for good enough.
"JSON is what happens when a programmer hates humans. YAML is what happens when a human tries to write JSON while drunk. TOML is what happens when a config file tries to cosplay as INI. Anvil is what happens when a software engineer decides good enough sucks."
~ Badkraft, 2025
Anvil.Net is a zero-copy, typeless, allocation-minimal parser for the Anvil language family β written in pure C# 13 / .NET 9.
No dependencies. No apologies.
#!aml
@[mod="anvil_test", version="1.0.0", debug=true]
// Single inheritance
base_block @[tier=1] := { hardness := 2.0 }
derived_block : base_block @[tier=2] := { hardness := 5.0 }
server @[core] := {
name := "Anvil Survival"
port := 25565
motd := "Forged in fire."
}
world @[seed=1337] := {
spawn @[respawn] := (0, 64, 0)
rules @[hardcore] := [
"pvp", "keepInventory=false", "naturalRegeneration=true"
]
}
readme := @md`# Anvil Server β Forged in fire. Built to last.`
player := ("Notch", 100, true)
Core features:
- Rich literals: strings, numbers, booleans,
null, hex (#FF5733,0xDEADBEEF), bare identifiers - Objects
{ }, Arrays[ ], Tuples( ), Blobs@tag`...` - Attributes
@[key, key=value, β¦]β attach to any statement, object, array, or tuple - Single inheritance:
derived : base := { β¦ } - Three dialects:
#!aml(modelling),#!asl(scripting),#!amp(messaging, scalars/blobs only) - Shebang detection, multi-line block comments, line comments
All major feature phases are shipped. v1.6.0 delivers the ANVL Schema Phase 2 enforcement
layer: @[schema] context, the full schema.* attribute vocabulary, 47xx error codes,
a 4-step schema path resolver, and 16 schema test cases including security boundary tests.
465 tests. Zero warnings.
- ANVL Schema Phase 2 β
.aschschema files use@[schema]module attribute; RHS bare-word tokens become type names (string,int,float,bool,date,any, arrays[ T ], tuples( T, β¦ ), nested blocks).AnvilSchemaValidatortraverses data documents and enforces declared types.Anvil.LoadWithSchema()provides explicit schema binding for CI and runtime registry use cases. Type confusion, array element injection, and structurally invalid path values are rejected at the schema boundary (TC-16 SQL injection analogue, error codes 4701β4703). - Schema field attributes β
@[schema.optional],@[schema.default="v"],@[schema.path](structural path validation, no TOCTOU). Advisory vs Strict enforcement modes; same.aschfile works for both. - Schema path resolver β 4-step resolution: absolute β relative-to-file β
schema.pathsearch entries β implicitpolaris/schemas/fallback. - Schema validation β
.aschschema documents declare Object, Enum, and Flags types.AnvilSchemaloads, resolves, and validates data documents; all field violations in a document are collected before returning (SchemaRuleset,SchemaType,FieldRule,TryGetOwn(),460xerror codes). - POCO code generation β
AnvilCodeGenconverts a loaded schema directly into a C# source file (sealed classes, records, enums,[Flags]enums). PureStringBuilder, no Roslyn dependency. All identifiers PascalCased. Controlled viaCodeGenOptions. - AMP dialect β scalar arrays
[v,β¦]and scalar tuples(v,β¦)fully parsed and round-tripped byAnvilWriter. - Multi-error validation β
AnvilValidationResultcollects every error across a document or a directory;ThrowIfInvalid()integrates with exception pipelines. - ASL scripting β C-style function bodies,
for/break/continue, List/Map values,$key.fielddeep access, built-in modules (sys_math/sys_str/sys_io), recursion guard at 64 levels, host module registration viaAnvilModuleRegistry.- Seam hardening β bare identifiers resolve from local scope only; the
$sigil is required to cross into the ANVL document layer. - Map literal syntax β
{ key: value, β¦ }produces a runtimeAslValueKind.Map; field access viam.keyand chainedm.inner.key. - String escape sequences β
\n \t \r \" \\processed at parse time in all string literals. - Interpolated strings β
$"Hello, {name}!\n"β identifier holes resolved through the normal scope chain; result type is alwaysString.
- Seam hardening β bare identifiers resolve from local scope only; the
Anvil.LoadDirectory(path)β load and proxy every.aml/.asl/.anvlfile in a directory as a single synthetic root; statements accessible asroot["stem.Key"].- Multi-file import graphs β split large AML datasets across files with configurable aliases, cross-source lookup, VarRef resolution, and inheritance.
Anvil.Net.dll β the parser, a direct port. Never diverges from the Java/C cousins.
| File | Purpose |
|---|---|
src/Source.cs |
Scanner cursor β shebang, dialect, whitespace/comment skipping |
src/Context.cs |
Parse context β statement/attribute/field pools, Builder |
src/Parser.cs |
Full AML/ASL/AMP parser β all value types |
src/Types.cs |
Enums and metadata structs (AnvilValueMeta, AnvilStatement, β¦) |
src/Symbols.cs |
Symbol table |
src/Operators.cs |
Operator table |
src/Errors.cs |
Error codes and thread-local error state |
src/Constants.cs |
Dialect enum, versioning, encoding helpers |
src/AslValue.cs |
Runtime value type (AslValueKind: Null/Int/Float/String/Bool/List/Map) |
src/AslScope.cs |
Scope chain for ASL function execution |
src/AslNode.cs |
AST node types for the ASL evaluator |
src/AslOp.cs |
ASL opcode table (includes For/Break/Continue/ListLiteral/MapLiteral) |
src/AslParser.cs |
ASL function-body parser |
src/AslEvaluator.cs |
Tree-walking evaluator |
src/AslFunctionMeta.cs |
Parsed function metadata + Execute() entry |
src/AslExecutionContext.cs |
Per-call execution context |
src/IAnvilModule.cs |
Module identity interface (Name property) |
src/IAnvilCallableModule.cs |
Module callable interface (Invoke(method, args)) |
src/AnvilModuleRegistry.cs |
Global thread-safe module registry (two-tier: built-ins + user) |
src/modules/SysMathModule.cs |
Built-in sys_math module (abs/min/max/pow/sqrt/β¦) |
src/modules/SysStrModule.cs |
Built-in sys_str module (upper/lower/split/join/β¦) |
src/modules/SysIoModule.cs |
Built-in sys_io module (exists/readFile/writeFile/lines/β¦) |
Anvil.Net.Api.dll β the fluent .NET API layer. References Anvil.Net and gets access
to parser internals via InternalsVisibleTo. Ships separately so the parser never has to
change to satisfy API concerns.
| File | Purpose |
|---|---|
src/Anvil.cs |
Static entry point β Anvil.Load(), Anvil.Parse(), Anvil.LoadDirectory(), LoadResolved() |
src/AnvilNode.cs |
Single consumer type β indexers, TryGet, TryGetOwn, AsSpan/AsMemory, HasBase, Is(), Resolve() |
src/AnvilNodeExtensions.cs |
AsInt(), AsFloat(), AsValues<Tβ¦>(), GetX(key, default) |
src/AnvilNodeState.cs |
Per-root lazy merge cache for inheritance resolution |
src/AnvilMemberAttribute.cs |
[AnvilMember("key")] β maps POCO properties to AML keys |
src/AnvilSerializer.cs |
Reflection-based POCO deserializer β Load<T>(), Parse<T>(), Deserialize<T>() |
src/AnvilScriptEngine.cs |
Public script host β Execute(name, argsβ¦), HasFunction, FunctionNames |
src/AnvilSchema.cs |
Schema entry β Load, Parse, Types, Validate, ValidateAll |
src/schema/ |
SchemaRuleset, SchemaType, FieldRule, SchemaTypeKind, SchemaResolver, SchemaValidator |
src/AnvilCodeGen.cs |
Code-gen entry β GenerateSource(AnvilSchema), GenerateSource(path) |
src/codegen/ |
CSharpEmitter (internal renderer), CodeGenOptions |
src/resolver/AnvilResolver.cs |
Kahn's sort + cycle detection; builds AnvilNodeState |
Deliberately minimal β one public type, full LINQ, zero friction.
// Load from file or parse a raw string
AnvilNode? root = Anvil.Load("server.aml");
AnvilNode? root = Anvil.Parse(sourceString);
// Always check for parse errors
if (root is null)
{
var err = AnvilError.Current;
Console.Error.WriteLine($"{err.Line}:{err.Column} β {err.Message}");
return;
}
// Navigate with indexers β throws AnvilKeyNotFoundException on miss
string name = root["server"]["name"].AsString();
int port = root["server"]["port"].AsInt();
// Arrays and tuples are IEnumerable<AnvilNode> β LINQ works natively
var rules = root["world"]["rules"]
.Select(r => r.AsString())
.ToList();
// Tuple elements by index
int x = root["world"]["spawn"][0].AsInt();
int y = root["world"]["spawn"][1].AsInt();
int z = root["world"]["spawn"][2].AsInt();
// Tuple deconstruction via extension method
var (rx, ry, rz) = root["world"]["spawn"].AsValues<int, int, int>();
// Attributes β HasAttribute / Attribute(key) returns AnvilNode?
bool experimental = root.HasAttribute("experimental");
long seed = root["world"].Attribute("seed")!.AsLong();
// Enumerate all attributes
foreach (var attr in root.Attributes)
Console.WriteLine($"@{attr.Identifier} = {attr.AsString()}");
// Blobs β AsString() returns the raw backtick content
string content = root["readme"].AsString();
string? tag = root["readme"].BlobTag; // "md", "sql", etc.
// Optional key β Attribute() returns null on miss; indexer throws
AnvilNode? opt = root["server"].Attribute("optional");
string val = opt?.AsString() ?? "default";// Load a schema document (.asch or @[schema] AML)
var schema = AnvilSchema.Load("mods.asch");
if (schema is null) { Console.Error.WriteLine(Anvil.LastError); return; }
// Validate a data document
var result = schema.Validate("data/stone.aml");
result.ThrowIfInvalid();
// Validate many files at once
var all = schema.ValidateAll(Directory.GetFiles("data", "*.aml"));
foreach (var err in all.Errors)
Console.Error.WriteLine($"{err.FilePath}:{err.Line} {err.Message}");// Generate C# from a schema β default options (nullable, sealed class, no namespace)
var source = AnvilCodeGen.GenerateSource(schema);
File.WriteAllText("Config.g.cs", source);
// With options
var source = AnvilCodeGen.GenerateSource(schema, new CodeGenOptions {
Namespace = "MyMod.Config",
UseRecords = true,
NullableEnabled = true,
});
// One-shot from file path
var source = AnvilCodeGen.GenerateSource("mods.asch");For tooling or consumers that don't want the API layer:
var ctx = new AnvilContext.Builder()
.LoadFile("server.aml")
.Build();
if (ctx?.Parse() != true)
{
Console.Error.WriteLine(AnvilError.Current);
return;
}
string src = File.ReadAllText("server.aml");
foreach (var stmt in ctx.Statements)
Console.WriteLine($"[{stmt.ValueMeta?.Type}] {stmt.GetIdentifier(src)}");Execute ASL functions from C# using AnvilScriptEngine:
// 1. Parse ASL script
var script = """
#!asl
add := (a, b) => { return a + b; }
greet := (name) => { return "Hello " + name + "!"; }
""";
var root = Anvil.Parse(script, AnvilDialect.Asl)!;
// 2. Get script engine
var engine = root.GetScriptEngine()!;
// 3. Execute functions
var sum = engine.Execute("add",
AslValue.FromInt(5),
AslValue.FromInt(3));
Console.WriteLine(sum!.Value.LongValue); // 8
var greeting = engine.Execute("greet",
AslValue.FromString("Alice"));
Console.WriteLine(greeting!.Value.StringValue); // "Hello Alice!"Register callable modules for ASL scripts:
// 1. Implement IAnvilCallableModule
public sealed class MathModule : IAnvilCallableModule {
public string Name => "Math";
public AslValue Invoke(string method, AslValue[] args) {
return method switch {
"sqrt" => AslValue.FromFloat(Math.Sqrt(args[0].ToDouble())),
"pow" => AslValue.FromFloat(Math.Pow(args[0].ToDouble(), args[1].ToDouble())),
_ => AslValue.Null
};
}
}
// 2. Register BEFORE loading scripts
AnvilModuleRegistry.Register(new MathModule());
// 3. ASL scripts can now call Math.sqrt(x)
var script = """
#!asl
using Math
calculate := (x) => {
sqrt_x := Math.sqrt(x);
return Math.pow(sqrt_x, 2);
}
""";
var result = engine.Execute("calculate", AslValue.FromFloat(16.0));
// result: 16.0Event hook integration:
public class GameEventBus {
private readonly AnvilScriptEngine _engine;
public GameEventBus(VoxelWorld world) {
// Register World module
AnvilModuleRegistry.Register(new WorldModule(world));
// Load hook scripts
var hooks = Anvil.Load("hooks.asl", AnvilDialect.Asl)!;
_engine = hooks.GetScriptEngine()!;
}
public void OnPlayerJoined(string username, uint netId) {
_engine.Execute("on_player_joined",
AslValue.FromString(username),
AslValue.FromInt(netId));
}
}π Complete Documentation: See docs/ASL-Execution-API.md for:
- Full API reference
- Module system details
- Thread safety considerations
- Current limitations & workarounds
- Planned features (per-invocation module context)
| Version | Focus | Status |
|---|---|---|
| 0.1.0 | Zero-copy parser core β full AML/ASL/AMP (Anvil.Net.dll) |
β Released (alpha) |
| 0.1.1 | Fluent runtime API β AnvilNode, Anvil.Load(), LINQ (Anvil.Net.Api.dll) |
β Released (alpha) |
| 0.1.3 | Safe navigation, AsSpan/AsMemory, transparent lazy inheritance resolver, POCO deserialization |
β Released (alpha) |
| 0.2.0 | vars block, .ref VarRefs, $"\u2026" interpolation; ReadOnlyMemory<char> backing |
β Released (alpha) |
| 0.2.1 | AnvilWriter β round-trip, pretty-print, minification, dialect support |
β Released (alpha) |
| 0.2.2 | [AnvilArray/Map/Tuple/Quoted], POCO write side, blob parser fix, string quoting |
β Released (alpha) |
| 0.2.3 | O(1) statement hash index (540Γ TryGet miss), NodeEnumerator struct, benchmarks suite |
β Released (alpha) |
| 0.3.0 | Multi-file import graphs, cross-source lookup/VarRef/inheritance, MergeBenchmarks |
β Released (alpha) |
| 0.4.0 | ASL scripting layer β functions, modules, using, AnvilScriptEngine, Anvil.LoadDirectory() |
β Released (alpha) |
| 0.4.1 | ASL completeness β C-style body syntax (=, ;, ++/--, +=/-=/*=//=), for/else if/break/continue, flag-based control flow (no exception on return), List/Map values, $key.field deep access, built-in modules (sys_math/sys_str/sys_io), error context FunctionName |
β Released (alpha) |
| 0.4.2 | @[attr] on ASL function declarations, AnvilScriptEngine.FunctionNode() |
β Released (alpha) |
| 0.4.3 | AMP scalar arrays [v,β¦] and scalar tuples (v,β¦), AmpArrayElementNotScalar=4401, AnvilWriter AMP array/tuple support |
β Released (alpha) |
| 0.4.4 | Multi-error validation β AnvilValidationError, AnvilValidationResult, Anvil.Validate*(), AnvilValidationException, ValidateAll() aggregate result |
β Released (alpha) |
| 0.4.5 | Schema validation β AnvilSchema, SchemaRuleset, .asch files, @[schema] marker, TryGetOwn(), SC01βSC20 |
β Released (alpha) |
| 0.4.6 | POCO code-gen β AnvilCodeGen, CSharpEmitter (pure StringBuilder), CodeGenOptions |
β Released (alpha) |
| 1.0.0-rc1 | Schema + codegen integration, full test suite, documentation pass | β Released |
| 1.0.0-rc2 | No-throw parsing policy enforced, 11 new ASL parse error codes (450x range) | β Released |
| 1.0.0-rc3 | SchemaValidator zero-alloc happy path, AslScope inline 8-slot linear table, SE09 fix | β Released |
| 1.0.0-rc4 | Interpolated string slot syntax fix ({ref} bare name), dotnet pack NuGet toolchain | β Released |
| 1.1.0 | Anonymous block syntax (FR-001), custom merge strategy API (FR-002) β IMergeStrategy, MergeStrategies.ConcatArrays/FilterBase/Compose, AnvilResolveOptions, public AnvilField/AnvilValue |
β Released |
| 1.2.0 | Lazy array concatenation (FR-003) β IAnvilArrayView, ConcatenatedArrayView, VirtualArrayView, full ConcatArrays() implementation with zero-copy virtual views |
β Released |
| 1.3.0 | Immutable function declarations, first-class function values (AslValueKind.Function), closure semantics, error 5504 on reassign |
β Released |
| 1.4.0 | Lambda expressions, AnvilScriptEngine recursion guard, AslCallDepthExceeded, FunctionName on error context, SE09-SE12 test coverage |
β Released |
| 1.5.0 | while loop, AstTests coverage, ImmutableFunctionTests, VarsTests, pass-by-value semantics hardening |
β Released |
| 1.5.1 | @[attr] on ASL function declarations refinements, AnvilScriptEngine.FunctionNode(), test suite consolidation |
β Released |
| 1.5.2 | Seam hardening β bare-ident ExternalLookup fallthrough removed (BL-040-asl); $ sigil now required to cross ANVL/ASL boundary |
β Released |
| 1.6.0 | ANVL Schema Phase 2 β @[schema] context switch, schema.* vocabulary, validator, AnvilSchemaValidator, Anvil.LoadWithSchema(), 4-step path resolver, 47xx error codes, security boundary enforcement (TC-16), 16 schema test cases, .asch authoring guide |
β Released |
cd anvil.net
dotnet buildRequires .NET 9 SDK.
Proprietary Β© 2025β2026 Quantum Override. All rights reserved.
This software may not be used, copied, modified, or distributed without explicit written permission. See LICENSE for full terms.
Anvil Messaging Protocol (AMP) is a separately licensable dialect. Contact licensing@quantumoverride.dev to obtain a commercial AMP license.
"Anvil β because your data deserves better than 1998."