Instances

Overview

Instances are the objects that represent the data modeled by specs. Instances are always dicts (dictionaries) or JSON objects. Instances may be defined within a lib or outside of a lib.

Id

All instances must define an id tag that is the unique identifier for the data entity. The id tag must always be a Ref value and cannot start with an uppercase letter nor contains the : character; valid characters are ASCI letters, digits, or _ - . ~ characters. Instances which are defined within a lib will have a id formed from the qname:

// instance in lib acme.widgets; id is @acme.widgets::sku-123
@sku-123: Part {...}

Instances defined within a lib must not start with a uppercase letter. This allows us to infer from a qname id whether it is a spec or an instance. Also it invalid to have a lib instance with the same case insensitive name as a spec in the same lib.

Instances outside of a lib will have an identifier that is project or system based. It is illegal for a non-lib id to contain "::" double colons; that format is reserved for lib instances.

The scope of uniqueness for lib instances is global due to the fact that lib names are globally unique. For non-lib instances the id must at least be unique within the containing dataset.

Spec

Instances declare their type spec via the 'spec' tag. The value is a Ref with the spec qname. When the instance is declared via Xeto in a lib, the 'spec' tag is implied:

// Xeto instance - spec is ph::ElecMeter via name resolution
@meter-1: ElecMeter {...}

// Haystack instance
id: @meter-1
spec: @ph::ElecMeter

// JSON object
{
  "id": "meter-1",
  "spec": "ph::ElecMeter"
}

Syntax

Lib instances follow the given syntax:

// separate slot tags with newline
@id: TypeSpec {
  slotA: valA
  slotB: valB
  ...
}

// or separate slot tags with comma
@id: TypeSpec {
  slotA: valA, slotB: valB, slot: valC
  ...
}

// the value can be omitted if it is a marker
@id: TypeSpec {
  markerA, markerB
}

Unlike specs, instances cannot define meta using '<>'.

For example to create an instance of a floor and room:

@floor-2: Floor {
  dis: "Floor 2"
}

@room-204: Room {
  dis: "Room 204"
  spaceRef: @floor-2
}

Note that we can cross reference other instances inside the lib using relative ids (absolute qnames are also permitted). All the non-maybe tags from the spec type are automatically added into the instance. The instances in the example above evaluate to the following Haystack dicts:

id: @acme::floor-2
spec: @ph::Floor
dis: "Floor 2"
space
floor
---
id: @acme::room-204
spec: @ph::Room
spaceRef: @acme::floor-2
space
room

Note that the space and floor/room markers are implied from the instance specs ph::Floor and ph::Room.

The instances above map the following JSON representation:

{
  "id": "acme::floor-2"
  "spec": "ph::Floor",
  "dis": "Floor 2",
  "space": "✓",
  "floor": "✓"
}

{
  "id": "acme::room-204"
  "spec": "ph::Room",
  "dis": "Room 204",
  "spaceRef": "acme::floor-2",
  "space": "✓",
  "room": "✓"
}

Nesting Instances

Xeto allows instances to be nested into a tree of dicts. Nested instances can have a slot name, a top-level id, or both.

Here is an example where the nested dicts have only a slot name:

Toolbar: Dict
Button: Dict

@toolbar: Toolbar {
  save: Button { text:"Save" }
  exit: Button { text:"Exit" }
}

This maps to the following Haystack and JSON representations where 'save' and 'exit' are just named slots that contain a nested dict:

// Trio
id: @acme::toolbar
spec: @acme::Toolbar
exit: {spec:@acme::Button, text:"Exit" }
save: {spec:@acme::Button, text:"Save"}
// JSON
"toolbar": {
    "id": "acme::toolbar"
    "spec": "acme::Toolbar",
    "save": {
      "spec": "acme::Button"
      "text": "Save",
    },
    "exit": {
      "spec": "acme::Button"
      "text": "Exit",
    },
  }

But we can also make those nested dicts first class instances with an id using this syntax by adding an id after the slot name and before the colon:

// Xeto
@toolbar: Toolbar {
  save @save-button: Button { text:"Save" }
  exit @exit-button: Button { text:"Exit" }
}
// Haystack
id: @acme::toolbar
exit: {id:@acme::exit-button spec:@acme::Button text:"Exit"}
save: {id:@acme::save-button spec:@acme::Button text:"Save"}
spec: @acme::Toolbar
// JSON
"toolbar": {
    "id": "acme::toolbar"
    "spec": "acme::Toolbar",
    "save": {
      "id": "acme::save-button",
      "spec": "acme::Button"
      "text": "Save",
    },
    "exit": {
      "id": "acme::exit-button",
      "spec": "acme::Button"
      "text": "Exit",
    },
  }

In many use cases if we don't care about the slot name, only the nested id, in which case we can omit the slot name and one is auto-generated for us:

// Xeto
@toolbar: Toolbar {
  @save-button: Button { text:"Save" }
  @exit-button: Button { text:"Exit" }
}
// Haystack
id: @acme::toolbar
_0: {id:@acme::save-button spec:@acme::Button text:"Save"}
_1: {id:@acme::exit-button spec:@acme::Button text:"Exit"}
spec: @acme::Toolbar
// JSON
"toolbar": {
    "id": "acme::toolbar"
    "spec": "acme::Toolbar",
    "_0": {
      "id": "acme::save-button",
      "spec": "acme::Button"
      "text": "Save",
    },
    "_1": {
      "id": "acme::exit-button",
      "spec": "acme::Button"
      "text": "Exit",
    },
  }

Note that in the reflection APIs the nested instances can be looked up directly. However, when exporting libs that contain nested instances only top-level instances are included (with their nested instances).

Haxall 4.0.5 ∙ 24-Feb-2026 14:33 EST