Crystal Description Language (CDL)

CDL is a domain-specific language for describing crystal morphology using crystallographic notation. It combines crystal systems, point groups, Miller indices, and optional modifiers to precisely define crystal forms for 3D visualization.

Current version

CDL v1.3

Includes comments (v1.1), features and phenomena (v1.2), grouping, labels, and definitions (v1.3).

Basic Syntax

A CDL expression follows this general structure:

system[point_group]:{hkl}@scale + {hkl}@scale | modifier

Components

Component Required Description Example
system Yes Crystal system cubic, trigonal
[point_group] No Point group symmetry (defaults per system) [m3m], [-3m]
: Yes Separator between header and forms
{hkl} Yes Miller index defining a crystal form {111}, {10-10}
@scale No Distance from origin (default 1.0) @1.0, @0.8
+ No Combine multiple forms

When the point group is omitted, the default for the system is used (e.g., cubic defaults to m3m).

Minimal expression
cubic:{111}
Explicit point group
cubic[m3m]:{111}
Two forms with scales
cubic[m3m]:{111}@1.0 + {100}@1.3

The @scale value controls how far each form sits from the crystal centre. Larger values push the form outward, reducing its face area relative to other forms. Smaller values bring it closer, making faces more prominent.

Crystal Systems and Point Groups

CDL supports all seven crystal systems and all 32 crystallographic point groups:

System Default Point Groups
cubic m3m m3m, 432, -43m, m-3, 23
hexagonal 6/mmm 6/mmm, 622, 6mm, -6m2, 6/m, 6, -6
trigonal -3m -3m, 32, 3m, -3, 3
tetragonal 4/mmm 4/mmm, 422, 4mm, -42m, 4/m, 4, -4
orthorhombic mmm mmm, 222, mm2
monoclinic 2/m 2/m, 2, m
triclinic -1 -1, 1

Miller Indices

Miller indices define crystal face orientations using curly braces. CDL supports several notations:

Format Example Description
Condensed 3-index {111} Single-digit indices concatenated
Separated 3-index {1 1 1} Space-separated (for multi-digit indices)
4-index (Miller-Bravais) {10-10} For hexagonal and trigonal systems
Negative indices {10-11} Minus sign before the digit

In 4-index Miller-Bravais notation {hkil}, the third index i is redundant (i = -(h+k)) but included for clarity in hexagonal and trigonal systems.

Named Forms

Common crystal forms can be referenced by name instead of Miller indices:

Name Miller Index System
cube {100} Cubic
octahedron {111} Cubic
dodecahedron {110} Cubic
trapezohedron {211} Cubic
prism {100} Hexagonal/Trigonal
pinacoid / basal {001} Hexagonal/Trigonal
rhombohedron {101} Trigonal
dipyramid {101} Hexagonal/Trigonal
Named form
cubic[m3m]:octahedron
Named form with scale
cubic[m3m]:octahedron@1.0 + cube@1.3

Combining Forms

Use + to combine multiple crystal forms into a single crystal:

# Diamond: octahedron with minor dodecahedron
cubic[m3m]:{111}@1.0 + {110}@0.2

# Quartz: prism with two rhombohedra
trigonal[32]:{10-10}@0.5 + {10-11}@1.2 + {01-11}@1.0

# Garnet: dodecahedron + trapezohedron
cubic[m3m]:{110}@1.0 + {211}@0.6

Twin Laws

Crystal twinning is specified with the | twin(law) syntax after the form list. The pipe | separates the forms from the twin specification.

# Spinel law twin (diamond macle)
cubic[m3m]:{111}@1.0 + {100}@0.3 | twin(spinel_law)

# Fluorite interpenetrating twin
cubic[m3m]:{100}@1.0 | twin(fluorite)

# Quartz Japan law twin
trigonal[32]:{10-10}@0.5 + {10-11}@1.2 + {01-11}@1.0 + {0001}@2.0 | elongate(c:2.0) | twin(japan)

# Cyclic twin with count
cubic[m3m]:{111} | twin(trilling,3)

# Custom twin axis
cubic[m3m]:{111} | twin([1,1,1],180)

Named Twin Laws

Law Description
spinel / spinel_law Contact twin on {111} (diamond, spinel)
brazil Optical twin in quartz (left/right hand)
dauphine Penetration twin in quartz (180° about c-axis)
japan Contact twin in quartz (~84.5°)
carlsbad Penetration twin in feldspar
baveno Contact twin in feldspar
manebach Contact twin in feldspar
albite Polysynthetic twin in plagioclase
pericline Polysynthetic twin in plagioclase
fluorite Interpenetrating cube twin
iron_cross Pyrite cross twin
trilling Cyclic triplet twin (chrysoberyl)
staurolite_60 60° cross twin
staurolite_90 90° cross twin
gypsum_swallow Swallowtail twin in gypsum

Modifications

Morphological modifications alter the crystal shape. They follow the form list, separated by a pipe |. Each takes the form type(param:value).

# Elongated quartz prism
trigonal[32]:{10-10}@0.5 + {10-11}@1.2 + {01-11}@1.0 | elongate(c:2.0)

# Flattened tabular crystal
cubic[m3m]:{111} | flatten(a:0.5)

# Multiple modifications separated by commas
cubic[m3m]:{111} | elongate(c:1.5), taper(top:0.3)

Modification Types

Type Syntax Description
elongate elongate(axis:ratio) Stretch along an axis (a, b, or c)
flatten flatten(axis:ratio) Compress along an axis
truncate truncate(form:depth) Cut corners or edges by a form
taper taper(direction:factor) Narrow in one direction
bevel bevel(edges:width) Add beveled edges

Comments (v1.1)

CDL supports three comment styles, stripped before parsing:

# Line comment (to end of line)
cubic[m3m]:{111}  # Inline comment

/* Block comment
   spanning multiple lines */

#! Mineral: Diamond
#! Habit: Octahedral
cubic[m3m]:{111}@1.0
Style Syntax Purpose
Line comment # text Ignored during parsing
Block comment /* text */ Multi-line ignored content
Doc comment #! Key: Value Extracted as structured metadata

Doc comments (#!) are preserved in the parsed output as structured metadata and can carry mineral names, habits, or other descriptive information.

Features (v1.2)

Features annotate individual crystal forms with surface markings, growth patterns, inclusions, or colour properties. Features are placed in square brackets [...] immediately after the form's Miller index and optional scale.

# Diamond octahedron with trigons
cubic[m3m]:{111}@1.0[trigon:dense]

# Sapphire with silk inclusions
trigonal[-3m]:{10-10}@1.0[striation:horizontal] + {10-11}@0.7

# Multiple features on one form
cubic[m3m]:{111}[trigon:dense, phantom:3]

# Feature with multiple values
cubic[m3m]:{111}[phantom:3, white]

Feature Types

Growth

phantom, sector, zoning, skeletal, dendritic

Surface

striation, trigon, etch_pit, growth_hillock

Inclusions

inclusion, needle, silk, fluid, bubble

Colour

colour, colour_zone, pleochroism, lamellar, banding

Phenomena (v1.2)

Optical phenomena are crystal-level annotations (not per-form). They follow the form list (and any modifications or twin), separated by |, using the syntax phenomenon[type:value].

# Star sapphire (6-rayed asterism)
trigonal[-3m]:{10-11}@1.0 | phenomenon[asterism:6]

# Cat's eye chatoyancy
orthorhombic[mmm]:{110}@1.0 | phenomenon[chatoyancy:sharp]

# Phenomenon with additional parameters
trigonal[-3m]:{10-11} | phenomenon[asterism:6, intensity:strong]

# Phenomenon after twin
cubic[m3m]:{111} | twin(spinel) | phenomenon[asterism:6]

# Phenomenon after modification
cubic[m3m]:{111} | elongate(c:1.5) | phenomenon[asterism:6]

# Features on forms AND phenomenon on crystal
trigonal[-3m]:{10-11}@1.0[silk:dense] | phenomenon[asterism:6]

Phenomenon Types

Type Description Example Gems
asterism Star effect (4, 6, or 12 rays) Star sapphire, star ruby
chatoyancy Cat's eye band Chrysoberyl cat's eye
adularescence Billowy internal glow Moonstone
labradorescence Spectral colour play on cleavage planes Labradorite
play_of_color Spectral flashes from diffraction Opal
colour_change Hue shift under different lighting Alexandrite
aventurescence Spangled glitter from inclusions Sunstone, aventurine
iridescence Rainbow colours from thin-film interference Rainbow quartz

Grouping (v1.3)

Parenthesized groups allow shared features to be applied to multiple forms at once. Features placed after the closing parenthesis apply to all forms in the group.

# Group with shared feature
cubic[m3m]:({111}@1.0 + {100}@1.3)[phantom:3]

# Group alongside a standalone form
cubic[m3m]:({111} + {100})[phantom:3] + {110}@0.8

# Nested groups
cubic[m3m]:(({111} + {100}) + {110})

# Individual features within a group merge with group features
cubic[m3m]:({111}[trigon:dense] + {100})[phantom:3]

When flat_forms() is called on the parsed result, group features are merged into each child form. In the last example above, the {111} form would have both trigon:dense (its own) and phantom:3 (from the group).

Labels (v1.3)

Labels assign descriptive names to forms or groups using a label: prefix. This is useful for documenting which form plays what role in the crystal.

# Label individual forms
cubic[m3m]:core:{111}@1.0 + rim:{100}@1.3

# Label a group
cubic[m3m]:core:({111} + {100})[phantom:3]

Named forms (like octahedron, prism) are not treated as labels — they are resolved to their Miller indices directly. Labels must be identifiers that are not known named forms.

Definitions (v1.3)

Named definitions let you assign a form expression to a name with @name = expr, then reference it with $name. Definitions are resolved by text substitution before parsing. They must appear on their own lines before the main CDL expression.

# Simple definition and reference
@oct = {111}@1.0
cubic[m3m]:$oct + {100}@1.3

# Multiple definitions
@prism = {10-10}@1.0
@rhomb = {10-11}@0.8
trigonal[-3m]:$prism + $rhomb

# Definitions can reference earlier definitions
@a = {111}@1.0
@b = {100}@1.3
@combo = $a + $b
cubic[m3m]:$combo

# Definitions work with comments and doc comments
#! Mineral: Diamond
@oct = {111}@1.0
# Main crystal form
cubic[m3m]:$oct

Referencing an undefined name raises a parse error. Definitions are stored on the parsed result for inspection by tools.

Full Grammar

The complete CDL grammar, expressed as a production rule summary:

cdl         = definitions? system ['[' point_group ']'] ':' form_list modifiers?
definitions = ('@' name '=' form_list NEWLINE)*

form_list   = form_or_group ('+' form_or_group)*
form_or_group = label? '(' form_list ')' features?   # group
              | label? form                            # single form

form        = (named_form | '{' miller '}') ['@' scale] features?
features    = '[' feature (',' feature)* ']'
feature     = name ':' value (',' value)*

modifiers   = ('|' modification_list)? ('|' twin)? ('|' phenomenon)?
modification_list = modification (',' modification)*
modification      = type '(' param ':' value ')'
twin              = 'twin' '(' (law [',' count] | '[' axis ']' ',' angle) ')'
phenomenon        = 'phenomenon' '[' type [':' value] (',' param ':' value)* ']'

label       = IDENTIFIER ':'
miller      = INTEGER{3..4}
scale       = NUMBER
name, type  = IDENTIFIER
value       = NUMBER | IDENTIFIER

Processing Order

CDL text is processed through these stages:

  1. Doc comment extraction#! lines are captured as metadata
  2. Comment stripping# and /* */ are removed
  3. Definition pre-processing@name = ... lines are extracted and $name references are resolved by text substitution
  4. Lexing — remaining text is tokenized
  5. Parsing — tokens are parsed into the AST: system, point group, form tree, modifications, twin, phenomenon
  6. Validation — point group checked against system

Try it yourself

Open the CDL Playground to experiment with these expressions.

Open Playground