Skip to content
Topaz6.0 · Before release
Download

Search documentation

Specification 6.0

Use arrow keys to choose a result. Press Enter to open.

Type an API name, diagnostic number, rule, or project topic.

    Topaz 6.0

    One way to say it.

    Topaz is a general-purpose programming language with typesetting built in. Code that does the same job looks the same no matter who writes it.

    TourLanguageLibraryToolsDiagnosticsPlayground

    591rules15chapters340APIs in 19 modules109diagnostics1,332examples

    oneway-en.tpz
    1. import std.io
    2. import std.bigint { BigInt }
    3. let labels = ["12", "cover", "14"]
    4. let pages = labels.map((text) => match (BigInt.parse(text, 10)) {
    5. case Some(number) => number.toInt()
    6. case None() => None()
    7. })
    8. io.writeLine("{pages}")
    Expected output
    [Some(12), None, Some(14)]

    map goes after the list. BigInt.parse reads each string, and toInt converts a parsed number. The entry for “cover” becomes None.

    Who, for what

    Built for these teams

    From manuscript to pages

    Teams building a book typesetting engine

    Build an engine that takes a manuscript and decides where lines and pages fall. Paragraph breaking and the Korean, Japanese and Chinese line-break rules come from the standard library. The same line-breaking code runs in the browser preview and in print composition.

    Per-book decisions in rule files

    Teams whose rules differ by publisher

    Publisher decisions such as where a chapter starts live in Lispex rule files. When each client wants its own style, the line-breaking app code stays the same.

    Data handling inside a web app

    Teams building web tools

    Write a function that handles input in Topaz, build it as a browser package and call it from JavaScript. The browser tools on jeni.kr work this way.

    std.typeset

    Typesetting in the standard library

    std.typeset in Topaz 6.0 is the standard module that breaks paragraphs into lines. The program below sets one Korean sentence at a width of ten Hangul syllables. Looking at the whole paragraph keeps a lone word off the last line.

    typeset-en.tpz
    1. import std.io
    2. import std.typeset {
    3. BreakCluster, BreakPolicy, BreakMode, KinsokuRules, BreakOpportunity, Paragraph, Item,
    4. Box, Penalty, BreakRule, DistanceGrid, CostModel, LineGeometry, JustificationPolicy,
    5. JustificationMode, KnuthPlassPolicy, ParagraphPlan, classifyBreaks,
    6. breakParagraph,
    7. }
    8. let sentence = "문단 전체를 보고 줄을 나누면 마지막 줄이 짧게 남지 않고 줄마다 길이가 고르게 됩니다."
    9. let clusters = sentence.scalars().map(cluster)
    10. let result = match (classifyBreaks(clusters, koreanRules(clusters))) {
    11. case Ok(breaks) => breakParagraph(paragraph(clusters, breaks))
    12. case Err(error) => Err(error)
    13. }
    14. match (result) {
    15. case Ok(plans) => {
    16. io.writeLine("Whole paragraph\n{linesOf(clusters, plans.optimized)}\n")
    17. io.writeLine("Line by line\n{linesOf(clusters, plans.greedy)}")
    18. }
    19. case Err(error) => io.writeLine("{error}")
    20. }
    Expected output
    Whole paragraph
    문단 전체를 보고
    줄을 나누면 마지막
    줄이 짧게 남지
    않고 줄마다 길이가
    고르게 됩니다.
    
    Line by line
    문단 전체를 보고 줄을
    나누면 마지막 줄이
    짧게 남지 않고 줄마다
    길이가 고르게
    됩니다.

    The main part of the program. The full source adds the helpers that set character widths and build the paragraph.

    Paragraph breaking

    Get the breaks chosen for the whole paragraph together with a greedy, line-by-line result. Choose the Knuth-Plass cost model or a fixed-point cost model.

    Unicode line breaking

    Find break opportunities with the Unicode 16.0 line breaking algorithm (UAX #14).

    Rules for each locale

    Add the Korean (KLReq), Japanese (JLReq), or Chinese (CLReq) rules, and an opening bracket never ends a line while a closing bracket or period never starts one. The Japanese rules follow the JLReq character classes.

    East Asian width

    The Unicode East Asian Width property decides breaks around quotation marks and brackets next to CJK text.

    Spacing

    Set how far justification may stretch or shrink the space between Latin words, and how far it may stretch the space between CJK characters.

    Length units

    Write lengths such as 12pt, 3mm, 1cm, 1in, and 1em directly in code and convert between units.

    std.typeset reference

    ATLAS

    A typesetting tool built with Topaz

    ATLAS turns a manuscript into print-ready book pages. Here is a page it set, with two of its line-breaking predicates rewritten for Topaz 6.0. ATLAS names these functions with Korean typesetting terms, shown here in English.

    Page 18 of a Korean book set with ATLAS
    Page 18 of a book set with ATLAS

    atlas-en.tpz
    1. function nextIsNotLatinSpace(next: Option<string>) -> bool {
    2. match (next) {
    3. case Some(text) => !isLatinSpace(text)
    4. case None() => true
    5. }
    6. }
    7. function nextHasNoLineStartBan(next: Option<string>) -> bool {
    8. match (next) {
    9. case Some(text) => !hasLineStartBan(text)
    10. case None() => true
    11. }
    12. }
    Expected output
    false true
    false true

    Two ATLAS line-breaking predicates rewritten for Topaz 6.0, with names translated into English. The full sample adds small policy helpers and inputs.

    View ATLAS

    Why Topaz handles typesetting well

    One way to say it

    Common tasks have one way to write them. Whether a person or an agent writes the code, it takes the same shape, so a review only has to follow what the code does.

    One way to write it

    Converting a number to text and collection operations are methods on the value. Use map on an array and toString on a number. Format the source with topaz fmt.

    Collection operations

    Missing and failed are different values

    A value that may be missing is an Option, an operation that may fail returns a Result, and cleanup is written with defer. At the end of a paragraph there is no next character, so a line-break rule receives None rather than an error.

    Null, Option, Result & Faults

    The outside world shows up in imports

    Printing to the screen and reading files both use a standard module you import. A reviewer finds every point of input and output from the imports.

    Standard Library Overview

    Missing cases stop the build

    Put a fixed set of cases, such as line-break classes, in an enum, and the compiler checks that each match handles every one. When you add a case later, the build reports each match that does not handle it yet.

    Patterns & Control Flow

    Overflow stops on the spot

    An int is a signed 64-bit integer, and overflow stops the program at that point instead of continuing with a wrong value. When you measure line widths and layout costs in integers, an overflow cannot hide in the result.

    Runtime Behavior

    The same rules on every target

    Direct runs, native executables and browser packages follow the same language rules. So the on-screen preview and the print layout break lines at the same places.

    Execution Targets

    Lispex

    Topaz computes, Lispex decides

    Computing such as line breaking happens in Topaz code. Decisions that change with each publisher and book, such as which page a chapter starts on or how many lines may stand alone, go into separate Lispex rules. Topaz 6.0 runs Lispex 2.0 rules through std.lispex with separate limits for preparation, value conversion and evaluation. Change a rule source and prepare it again, and the same manuscript takes another style without any change to the application code.

    • Rules never touch files, the system clock, or the network.
    • A rule stops where it runs past its limit.
    • The same rule, input, and settings give the same decision every time.
    lispex-en.scm
    1. (define rule
    2. (lambda (input)
    3. (let ((min-lines 2))
    4. (if (< input min-lines) "next-page" "keep"))))

    All of lispex-en.scm

    lispex-en.tpz
    1. import std.io
    2. import std.bigint { BigInt }
    3. import std.lispex {
    4. LispexPackage, LispexHostLimits, LispexPreparationLimits, LispexSource,
    5. LispexSourceBundle, PreparedLispexRule, LispexDatumView, LispexValue,
    6. applicationPackage, defaultLimits, prepare, valueFromDatum, valueView, evaluate,
    7. }
    8. export function main(args: Array<string>, stdin: string) -> Result<int, string> {
    9. let application = packageOf()?
    10. let preset = defaultLimits()
    11. let rule = ruleFrom(application, stdin, preset.preparation)?
    12. for linesLeft in ([1, 2]) {
    13. let input = numberOf(application, "{linesLeft}", preset.host)?
    14. let evaluation = match (evaluate(rule, input, preset.evaluation)) {
    15. case Ok(value) => value
    16. case Err(_) => { return Err("The evaluation could not complete.") }
    17. }
    18. match (evaluation.settlement) {
    19. case Complete(result) => io.writeLine("lines left {linesLeft} {textOf(result, preset.host)?}")
    20. case SemanticFailure(_) => { return Err("The rule returned a language failure.") }
    21. case LimitExhaustion(_) => { return Err("The rule reached its evaluation limit.") }
    22. }
    23. }
    24. return Ok(0)
    25. }
    Expected output
    lines left 1 next-page
    lines left 2 keep

    The main part of lispex-en.tpz. The full source includes the preparation and value helpers.

    Prepare a rule with prepare and run it with evaluate.

    View Lispex

    Built with Topaz

    Beyond typesetting

    The core of the Topaz compiler is written in Topaz. Browser tools are written in the same language.

    compiler.tpz
    1. function assignmentRoot(node: SyntaxNode) -> Option<SyntaxNode> {
    2. if (node.kind == "expression/identifier") {
    3. return Some(node)
    4. }
    5. match (child(node, "object")) {
    6. case Some(object) => assignmentRoot(object)
    7. case None() => None()
    8. }
    9. }
    Expected output
    expression/identifier
    None

    The assignment-root walk from compiler/crates/topaz_self_frontend/topaz/checker/expression_core.tpz rewritten for Topaz 6.0

    The compiler

    Code written in Topaz checks names and types and generates the Rust. Use topaz check to check a program and topaz build to create an executable.

    Built with Topaz

    Browser tools

    The JSON formatter on jeni.kr tidies input with a parser written in Topaz, and reports the line and column when a closing bracket is missing.

    Call it from JavaScript

    Explore Topaz

    Choose where to go next

    Introducing Topaz

    See what Topaz is designed for, what its code looks like, and where it fits before writing your first program.

    Meet Topaz

    Learn Topaz

    Start with one small program and continue step by step to a checked, tested, two-module application.

    Start learning

    Typesetting

    Break a paragraph into lines with std.typeset. Set widths such as 12pt or 3mm, the line breaking rules of each language, and the spacing of justified lines.

    Open the typesetting reference

    Reference

    Start from the syntax map, then look up language rules, the standard library, and the tools.

    Open the syntax map

    Menu

    Download