Crate boxworks_lang

source ·
Expand description

§Boxworks language

This crate defines a domain-specific language (DSL) for Boxworks. This language is used to describe Boxworks elements in Knuth’s box-and-glue model. The initial motivation for the language is to make it easy to create Boxworks primitives, like horizontal and vertical lists, for use in unit testing.

In the long run, the language will probably support running the Boxworks engine and actually performing typesetting. If this happens, this language will be a sort of “intermediate representation” for the Texcraft project.

This is a basic example of converting some of the language into a horizontal list:

use core;
use boxworks::ds;
use boxworks_lang as bwl;

let source = r#"
    # The text() function typesets some text.
    # The language automatically converts spaces into glue.
    text("Box Lang")
    # Or glue can be added manually.
    glue(1pt, 5fil, 0.075in)
    # The following elements illustrate the prototypical example of a kern.
    text("A")
    kern(-0.1pt)
    text("V")
"#;
let got = bwl::parse_horizontal_list(&source);
let want: Vec<ds::Horizontal> = vec![
    ds::Char{char: 'B', font: 0}.into(),
    ds::Char{char: 'o', font: 0}.into(),
    ds::Char{char: 'x', font: 0}.into(),
    ds::Glue{
        kind: ds::GlueKind::Normal,
        // Right now spaces are converted to the glue 10pt plus 4pt minus 4pt.
        // We will eventually support customizing this.
        value: core::Glue{
            width: core::Scaled::ONE * 10,
            stretch: core::Scaled::ONE * 4,
            stretch_order: core::GlueOrder::Normal,
            shrink: core::Scaled::ONE * 4,
            shrink_order: core::GlueOrder::Normal,
        }
    }.into(),
    ds::Char{char: 'L', font: 0}.into(),
    ds::Char{char: 'a', font: 0}.into(),
    ds::Char{char: 'n', font: 0}.into(),
    ds::Char{char: 'g', font: 0}.into(),
    ds::Glue{
        kind: ds::GlueKind::Normal,
        value: core::Glue{
            width: core::Scaled::new(
                1,  // integer part
                core::Scaled::ZERO,  // fractional part
                core::ScaledUnit::Point,  // units
            ).unwrap(),
            stretch: core::Scaled::new(
                5,  // integer part
                core::Scaled::ZERO,  // fractional part
                core::ScaledUnit::Point,  // units
            ).unwrap(),
            stretch_order: core::GlueOrder::Fil,
            shrink: core::Scaled::new(
                0,  // integer part
                core::Scaled::from_decimal_digits(&[0, 7, 5]),  // fractional part
                core::ScaledUnit::Inch,  // units
            ).unwrap(),
            shrink_order: core::GlueOrder::Normal,
        }
    }.into(),
    ds::Char{char: 'A', font: 0}.into(),
    ds::Kern{
        kind: ds::KernKind::Normal,
        width: -core::Scaled::new(
                0,  // integer part
                core::Scaled::from_decimal_digits(&[1]),  // fractional part
                core::ScaledUnit::Point,  // units
            ).unwrap(),
    }.into(),
    ds::Char{char: 'V', font: 0}.into(),
];
assert_eq![got, Ok(want)];

The main takeaway from this example is that you start with a very terse description of the horizontal list, and the library outputs the long and tedious Rust struct definitions.

§Language specification

A Boxworks language program is a sequence of a function calls like text("ABC") or glue(10pt, 3pt, 2pt). Most function calls add an item or items to the current box-and-glue list.

§Function arguments

Each function accepts a number of arguments. For simplicity, every argument to every function is optional.

Arguments can be provided positionally:

let source = r#"
    text("A", 1)
"#;
assert_eq![
    bwl::parse_horizontal_list(&source),
    Ok(vec![ds::Char{char: 'A', font: 1}.into()])
];

Or by keyword, potentially out of order:

let source = r#"
    text(font=2, content="B")
"#;
assert_eq![
    bwl::parse_horizontal_list(&source),
    Ok(vec![ds::Char{char: 'B', font: 2}.into()])
];

Or by a combination of positional and by keyword:

let source = r#"
    text("C", font=3)
"#;
assert_eq![
    bwl::parse_horizontal_list(&source),
    Ok(vec![ds::Char{char: 'C', font: 3}.into()])
];

However, all positional arguments must be provided before keyword arguments:

let source = r#"
    text(content="C", 3)
"#;
let errs = bwl::parse_horizontal_list(&source).unwrap_err();
assert![matches![
    errs[0],
    bwl::Error::PositionalArgAfterKeywordArg{..}
]];

§Function argument types

Every function argument expects a specific concrete type. These are the types:

NameDescriptionExamples
StringArbitrary UTF-8 characters between double quotes. Currently the string can’t contain a double quote character."a string"
IntegerDecimal integer in the range (-2^31,2^31).123, -456
DimensionDecimal number with a unit attached. The format and the allowable units are the same as in TeX.1pt, 2.04in, -10sp
Glue stretch or shrinkA dimension where the unit can alternatively be an infinite stretch/shrink unit.1fil, -2fill, 3filll

§Available functions

More functions will be added over time. These are the currently supported functions.

§glue: add a glue node to the current list

Adds a value of the Rust type [boxworks::ds::Glue] to the current list.

Parameters:

NumberNameTypeDefault
1widthdimension0pt
2stretchglue stretch or shrink0pt
3shrinkglue stretch or shrink0pt
§kern: add a kern node to the current list

Adds a value of the Rust type [boxworks::ds::Kern] to the current list.

Parameters:

NumberNameTypeDefault
1widthdimension0pt
§text: typeset some text

The goal of this function is to add character and glue nodes like TeX does when it is processing normal text. In TeX this is actually a complicated process that includes:

  • Adding kerns between characters.
  • Applying ligature rules.
  • Adjusting the space factor that determines the size of inter-word glue.

Right now the text function is much simpler. It iterates over all characters in the string and:

  • For space characters, adds a glue node corresponding to the glue 10pt plus 4pt minus 4pt.

  • For all other characters, adds a value of the Rust type [boxworks::ds::Char].

Parameters:

NumberNameTypeDefault
1contentstring""
2fontinteger0

Modules§

  • Box language abstract syntax tree
  • Conversions between Box language and Boxworks data structures
  • Box language concrete syntax tree (CST).
  • Lexer and tokens for Box language.

Structs§

Enums§

  • Error encountered when parsing Box language.

Functions§