Hamen Markup syntax, compiler 1.0.0
This is the executable syntax reference. Examples in the earlier design documents describe the broader design space and may use proposed notation.
For concise note-taking syntax, layered marks, annotations, keyboard math, derivations, and the local editor, see NOTES.md. .hmn or @Notes() opts into that syntax. The document syntax below is unchanged.
Blocks and text
@Document:
@Main:
@Headline(level: 1): The title
@P:
Ordinary prose, with @Strong[important text] and @Emphasis[emphasis].
A blank line separates prose runs in flow containers.
Indentation uses spaces. A colon opens content on the same line, an indented block, or both. Siblings must use consistent indentation. Component names are case-sensitive. P is a built-in alias for Paragraph. Parentheses may be omitted when a component has no arguments.
In an explicit Paragraph, use @LineBreak() for intentional line breaks. In containers such as Main, consecutive plain-text lines form a paragraph and a blank line starts another.
Inline components use @Name(arguments)[content]. Escape a literal at-sign as \@. Email addresses such as name@example.com remain literal. HTML in prose is escaped, never executed.
Lines beginning with # are comments outside raw blocks. Trailing comments are supported after component arguments and after a block colon without content. A # in ordinary prose is literal; hexadecimal colours are values inside arguments.
CodeBlock, Code, Preformatted, Math, Equation, Chemistry, and Comment preserve indented raw content. Raw content can contain @, #, backslashes, and blank lines without being parsed as components.
Arguments and values
Named arguments can be written directly or inside a map:
@Columns(count: 3, gap: 12px)
@Columns({count: 3, gap: 12px})
Argument lists can span lines. Lists use brackets, maps use braces, and tuples of coordinates can use parentheses. Supported values include quoted strings, unquoted symbols, numbers, Boolean values, null, colours, lists, maps, and dimensions.
@Grid({
columns: [1fr, 2fr],
rows: [Auto, 1fr],
areas: ["title title", "aside article"],
gap: 4mm
})
Unquoted names are symbols unless they resolve to a declared value. Quote prose, file paths, URLs, and compound CSS values. Strings support escaped quotes, backslashes, newline, tab, and carriage return.
Lengths: px, pt, pc, in, cm, mm, em, rem, vw, vh, %. Grid fractions: fr. Angles: deg, rad. Durations: s, ms. Component contracts determine which dimensions are valid for each property.
Expressions support +, -, *, /, %, comparisons, and, and or. Multiplication precedes addition. Parentheses group expressions. Physical lengths convert during arithmetic: 1in + 72pt is 2in. Incompatible dimensions report an error. Relative and physical lengths cannot be mixed arithmetically before layout.
Values, data, and repetition
@Let(Gutter, 12px)
@Let(WideGutter, 2 * Gutter)
@DataSource(People, {data: [{name: "Ada"}, {name: "Grace"}]})
@Repeat(People, as: person):
@P: @Value(person.name)
@Repeat(Range(1, 4), as: number):
@P: Item @Value(number)
DataSource also reads local JSON or CSV using source: "data.json". Dotted names access fields of a declared map. Repeat accepts finite lists and supplies a zero-based index. Range supports one to three integer arguments and at most 10,000 items.
Use @Value(name) in text and name directly in argument expressions. No Python evaluation is performed.
Fonts, styles, themes, and aliases
@Font(Body, {
regular: Asset("fonts/body-regular.woff2"),
bold: Asset("fonts/body-bold.woff2")
})
@Def(Paragraph, {family: Body, size: 11pt, lineHeight: 1.5})
@TextStyle(Kicker, {family: "HMK Sans", tracking: 2px, color: #9a432c})
@Alias(Headline, H)
@P(style: Kicker): LABORATORY NOTES
Def without children sets defaults for a component. TextStyle, ParagraphStyle, and CharacterStyle name reusable property maps. Direct properties override a named style; the named style overrides component defaults. Theme groups per-component defaults, for example @Theme(Clean, {Paragraph: {color: #26332c}}).
The bundled default fonts are Libre Baskerville (HMK Serif) and Inter (HMK Sans), with Latin glyph coverage. Import suitable fonts for other scripts or to control every weight and style explicitly.
Custom components
@Def(Callout, {title: String, inset: Length = 12px, body: Content}):
@Aside(padding: inset, background: #eef2ec):
@Headline(level: 3): @Value(title)
@Content(body)
@Callout(title: "Remember"):
@P: This paragraph fills the body slot.
Supported parameter types: String, Number, Boolean, Length, List, Content, and Enum(A, B, C). Defaults use = inside the definition map. Custom components accept named arguments. Missing arguments, wrong types, unknown arguments, and runaway expansion are errors.
Imports and source bundles
@Use(Standard.Layout, {Grid, Columns, Canvas})
@Use("components.hmk")
@Include("chapters/introduction.hmk")
Standard components are available without imports; explicit standard imports validate the requested names. Local source modules and includes expand in source order, so shared declarations must precede their use. Local module imports currently have whole-module scope; selective local symbol isolation is not implemented.
Resources resolve relative to the source file containing their declaration. Every resource must remain inside the project root after symlink resolution. The CLI's default root is the entry file's directory; use --root . for examples that reference sibling asset folders.
hmk pack builds a deterministic .hmkp archive of source and referenced local resources. hmk unpack verifies hashes and paths before extracting into an empty directory. Standard runtime libraries come from the compiler installation, not the source bundle.
Page composition
@DocumentType(Paged)
@Page({size: A4, margin: [20mm, 18mm]})
@Document:
@Header:
@P: A running heading
@Main:
@P: The article.
@PageBreak()
@P: The next page.
@Footer:
@Row:
@P: Publication name
@Spacer
@P: @PageNumber() / @PageCount()
Page sizes: A4, A3, Letter, Legal, or [width, height]. Width, Height, and Margin remain supported from the original outline. PageMaster declares repeated decoration and page geometry; PageSequence(master: Name) selects it. Running headers and footers are declared under Document and apply across the document.
PageNumber(startAt: 0) or Page({startAt: 0}) changes the document counter origin. Page references resolve after pagination. Use Reference(target, format: Number) for numbered references and PageReference(target) for page references.
Complex layouts
Columns pours content across equal columns. Stack(X, 3) preserves the original three-column shorthand; ordinary Stack(axis: X) arranges children in a row.
Grid supports track lists, named areas, explicit row/column placement, and spans. Canvas establishes a coordinate space, and Layer provides overlapping planes. Elements with x or y become positioned boxes. SVG primitives include rectangles, ellipses, lines, polygons, paths, and text on paths.
@Flow(Story):
@P: An article whose text continues through multiple frames.
@Canvas(width: 8in, height: 5in):
@Frame(first, x: 0in, y: 0in, width: 3in, height: 3in, flow: Story)
@Frame(second, x: 4in, y: 1in, width: 2in, height: 3in, flow: Story)
Frame order in the source determines flow order. Frames need explicit positive heights. Text splits on word boundaries and retains inline markup. Tables, images, figures, code blocks, and models are kept whole. Insufficient frame capacity is a render error.
@Attach(target: caption, to: figure, axis: X, edge: Left, toEdge: Left)
@Attach(target: caption, to: figure, axis: Y, edge: Top, toEdge: Bottom, offset: 12px)
Align uses with and withEdge; Attach uses to and toEdge. Edges: Left, Right, Top, Bottom, Center, Baseline. Constraints resolve in dependency order. One constraint per target axis is allowed. Cycles and missing targets are errors. These are anchor relationships, not a general algebraic equation solver.
Responsive and target-specific content
@DocumentType(Pageless)
@When(Screen.Width < 700px):
@Override(ArticleGrid, {columns: [1fr]})
@When(Target == PDF):
@P: Print-only explanatory text.
@Profile(Print):
@Def(Paragraph, {color: Black})
Select a profile with --profile Print. Screen width/height queries are runtime media queries; Target and Profile conditions resolve during compilation. Screen layout should be used for adaptive documents, while page geometry governs fixed compositions.
Math, chemistry, and interactive media
@Equation(id: energy):
E = mc^2
@Chemistry:
2 H2 + O2 -> 2 H2O
@ChemicalStructure(smiles: "CCO")
@P: Speed: @Quantity(36, "km/hour", to: "m/s")
@Model3D(source: Asset("model.glb"), poster: Asset("poster.svg"), cameraControls: true)
Math and chemistry use bundled MathJax and mhchem. ChemicalStructure uses RDKit's SMILES renderer and requires the science extra. Models must be self-contained GLB or embedded-resource glTF; externally fetched compression decoders are not enabled.
PDF media requires a poster, transcript, or explicit Fallback. Interactive(kind: Details) supplies an accessible disclosure on the web. Forms are local; supported button actions are Print, Reset, Toggle, and None. Arbitrary scripts and remote form submission are not supported.
Diagnostics and inspection
hmk check performs compilation checks. It does not execute browser layout; hmk build ... -o result.pdf also verifies pagination, flow capacity, math loading, and image readiness. hmk check --json emits structured diagnostics. hmk inspect emits a source-located syntax tree. hmk components Name lists the accepted properties.
The formatter currently normalizes line endings and the final newline while preserving comments and raw text. It does not reflow or rewrite source syntax.