Skip to content
Beskid The Beskid Book

Beskid

Jump to a Beskid service

Beskid

Jump to a Beskid service

03.6 Bsol — structured config without YAML trauma

The meta-language behind .bproj manifests, schema profiles, and why it feels like HCL until the compiler gets honest.

BSOL — structured config without YAML trauma

Every toolchain eventually invents a config dialect. Beskid’s is BSOL (Beskid Structured Object Language). If you have touched Terraform, you already know the shape: blocks, labels, key = value, bracket lists. If you have touched YAML, you already know why we did not use it for manifests.

This page is informative. Exact grammar and profile rules live in the BSOL Standard.

BSOL and Beskid source use separate parsers. The BSOL parser reads .bproj, .bws, and .bsol documents. A .bd file uses the Beskid source parser, then semantic facts, TypedProgram, CodegenInput, and code generation. See the analysis stack.

BSOL is the meta-language for files the compiler reads before it touches your source tree:

FileProfileBecomes
MyApp.bprojproject.v1ProjectManifest + resolver graph
Workspace.bwsworkspace.v1WorkspaceManifest
runtime_manifest.bsolruntime.v1ABI tables and dispatch registries

Same surface grammar. Different schema profile. Same pipeline shape.

HashiCorp Configuration Language (HCL) popularized a useful pattern:

resource "aws_instance" "web" {
ami = "ami-123"
instance_type = "t3.micro"
}

BSOL uses the same mental model—block kind, optional label, body of assignments—without pretending to be Terraform:

MyApp {
name = "MyApp"
version = "0.1.0"
}
target "App" {
kind = App
entry = "Main.bd"
}

The similarity is intentional. The divergence is where validation lives.

StageHCL-ish tools (typical)Beskid BSOL stack
Lex / parseCustom parser per productOne bsol.pest grammar → BsolDocument
StructureSchema baked into parserSchema profiles (project.v1, …)
SemanticsProvider / app logicbeskid_analysis::projects lowering + E18xx contract band
IDEOften string heuristicsLSP walks span-backed AST from parse_bsol_document

HCL separates syntax from provider semantics across many tools. Beskid separates syntax from manifest semantics across one crate (beskid_bsol) and downstream lowering—the same split you see between parse and semantic analysis for .bd, just on a smaller document.

accTitle: BSOL and Beskid validation spines
accDescr: BSOL validates manifests before project lowering, while Beskid source passes through syntax, semantic facts, and code generation.
flowchart LR
  subgraph bsol [beskid_bsol]
    pest[bsol.pest]
    ast[BsolDocument]
    prof[Schema profile]
    val[validate]
    pest --> ast --> val
    prof --> val
  end
  subgraph analysis [beskid_analysis projects stack]
    lower[parser.rs lowering]
    model[ProjectManifest]
    graph[Resolver / lockfile]
    val --> lower --> model --> graph
  end
  subgraph bd [Beskid .bd stack — same spine idea]
    parse[Parse Program]
    sem[Semantic rules]
    ir[TypedProgram / CodegenInput / codegen]
    parse --> sem --> ir
  end

Text equivalent: BSOL source uses the BSOL parser to become a BsolDocument, then a schema profile validates it before project lowering and graph resolution. Beskid source uses a separate source parser before semantic facts and code generation. The two paths have analogous parse-and-validate spines; they do not share one parser.

Both paths share the platform rule: one spine, no shadow parsers. Manifest files are not special-cased with ad hoc regex in the LSP; they go through BSOL first, then contract validation—mirroring how .bd goes through parse, then semantic rules.

Older manifest parsers hard-coded block kinds in the grammar (target, dependency, …). BSOL widened the grammar to any ident { ... } block and moved legality into profiles:

  • Grammar answers: is this text structurally a BSOL document?
  • Profile answers: which blocks and fields are legal for this file type?
  • Lowering answers: what does it mean for builds and locks?

That three-layer split matches how the rest of the compiler treats syntax vs semantics—BSOL is the config-side version of “parse succeeds, semantic pass may still fail.”

@schemaless — when you need a raw { ... } pocket

Section titled “@schemaless — when you need a raw { ... } pocket”

Most blocks are structured. Occasionally a profile needs an opaque body—content the parser does not interpret as assignments (embedded snippets, foreign syntax, migration shims).

Mark the block with @schemaless before {:

patch @schemaless {
# anything between the braces is captured verbatim
keep = "this { nested } text"
not parsed as bsol
}

The profile rule must opt in with schemaless = true. Parser stores the inner text in schemaless_body; validation skips field rules; downstream code decides what to do with the raw string.

Structured blocks and schemaless blocks can coexist in one document—same as mixing strict types and extern escape hatches in the main language, but for config.

TopicLink
Writing your first manifestProject manifest
Workspace filesWorkspace manifest
Normative grammar + ASTBSOL design model
Runtime ABI manifestRuntime manifest profile
Full analysis pipelineFrom source to something that runs

Tree and resolution

04. Where does this file even go?