iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
⛄️

[Introductory Article] How to Define Commands in SATySFi

に公開
2020/12/30

This article is for the 10th day of the SATySFi advent calendar 2020.
Yesterday's article was "Things I thought when I made a document with SATySFi" by abenori.
Tomorrow's article will be "About the CI of Satyrographos Repo" by na4zagin3.

I am monaqa.
It's already mid-December before I knew it. December means New Year's Eve, and New Year's Eve means SATySFi. Let's have fun marking up with SATySFi!

To perform markup in SATySFi, mastering commands is indispensable. Conversely, there is a view that if you can master commands perfectly, you are already a fine SATySFician. Therefore, taking advantage of the stage of the Advent Calendar, I would like to summarize "three things that might be useful for mastering SATySFi commands."

  1. 【12/3】 About SATySFi Types
  2. 【12/10】 SATySFi Command Syntax
  3. 【12/17】 How to Define SATySFi Commands (This article!)

The contents explained here are basically described in The SATySFibook. Please refer to it for detailed explanations. However, if you find any errors in this article, it would be helpful if you could contact me via the comments below the article or on Twitter at @mo_naqa.
Also, the following explanation assumes SATySFi v0.0.5. Please note that specifications may change in future versions.

Introduction

In the first part, I explained SATySFi types, and in the second part, I explained command syntax. This time, I will describe "how to define commands yourself."

SATySFi is a language where it is relatively easy to "extend the markup yourself" compared to other markup languages. The foundation for extending that markup is the command definition, which I will explain today.
Once you can define commands yourself, you will be able to:

  • Define shortcuts for content and styles you frequently write to make description easier
  • Add functions not found in existing packages
  • Define new symbols, etc. (especially in mathematical formulas)

This is very powerful.

In this article, I will first describe where to write command definitions, and then explain how to define three types of commands: inline commands, block commands, and math commands.

Where to Write Command Definitions

Command definitions in SATySFi cannot be written anywhere. There are basically two locations where you can define commands.

  1. The preamble of the document file (a file with a .saty extension[1])
  2. Inside a package file (a file with a .satyh extension)

Preamble of a Document File

A document file is "a file where you describe the main body of the document you want to typeset." Let's consider a document file like the following.

test.saty
@require: stdja
@require: code

StdJa.document (|
  ... omitted ...
|) '< 

  +p{
    Hello, \SATySFi;!
    Hello, \emph{world}!
  }
  +eqn(${
    \frac{\alpha}{2}
  });

>

The part before StdJa.document is called the preamble, following the convention of LaTeX (we call it that for convenience). Lines starting with @require: are statements for loading SATySFi package files, and these must be written at the beginning of the file. Command definitions are written in the preamble section after @require:. For example, in the case above, it would be written as follows.

test.saty
@require: stdja
@require: code

% Command definition starts here
let-inline \hoge = {hoge}
let-block +fuga = '<>

in % When defining commands, add `in` at the end of the preamble

StdJa.document (|
  ... omitted ...
|) '< 

  +p{
    Hello, \SATySFi;!
    Hello, \emph{world}!
  }
  +eqn(${
    \frac{\alpha}{2}
  });

  +fuga;  % Commands defined in the preamble can now be used
>

This method is relatively easy, but you cannot reuse the definitions in multiple document files.

Inside a Package File

A package file is for organizing definitions of commands, functions, and so on. Package files are given the extension .satyh[2]. For example, create a file named local.satyh and write the following content in it.

local.satyh
let-inline \hoge = {hoge}
let-block +fuga = '<>

Then, by writing @import: local at the beginning of test.saty placed in the same directory, you will be able to use the commands in the document file.

test.saty
@require: stdja
@require: code
@import: local

StdJa.document (|
  ... omitted ...
|) '<

  +p{
    Hello, \SATySFi;!
    Hello, \emph{world}!
  }
  +eqn(${
    \frac{\alpha}{2}
  });

  +fuga;  % Commands defined in the local file can now be used
>

Although you need to create a new file called a package file, you can manage and use it across multiple document files. Since you can provide a relative path from the document file to @import:, it can be handled even if they are not in the same directory.

Defining Inline Commands

To define an inline command, you must always use the primitive let-inline. For example, to define a command \cmd-name that takes N arguments, you write it as follows:

let-inline ctx \cmd-name arg1 arg2 ... argN = «expr»

The following elements appear after let-inline:

  • ctx: The 0-th argument. It has the context type and represents the "text processing context when the command is called." There is no specific variable name required, but it is common practice to use ctx.
  • \cmd-name: The name of the command you want to define.
  • arg1, ..., argN: Command arguments. They are listed in order.
  • «expr»: The expression representing the content of the definition. You put an expression of type inline-boxes here.

There is also the following syntax:

let-inline \cmd-name arg1 arg2 ... argN = «expr»
  • \cmd-name: The name of the command you want to define.
  • arg1, ..., argN: Command arguments. They are listed in order.
  • «expr»: The expression representing the content of the definition. In this syntax, you put an expression of type inline-text.

The two differences from the first syntax are that "the 0-th argument representing the text processing context is omitted" and "the return value is of the inline-text type." This syntax is suitable for defining simple commands that do not require the text processing context.

It may be difficult to understand how to use it just by looking at the general format, so I will introduce some concrete examples.

Example 1: Displaying a Pangram

Let's start with the simplest example. Let's define a command \pangram; that displays the text "The quick brown fox jumps over the lazy dog.". This can be defined as follows:

let-inline ctx \pangram =
  read-inline ctx {The quick brown fox jumps over the lazy dog.}

We are using the read-inline primitive to convert the inline-text {The quick brown fox jumps over the lazy dog.} into an inline-boxes sequence. This can be further simplified as follows:

let-inline \pangram = {The quick brown fox jumps over the lazy dog.}

Very concise! \pangram; has the type [] inline-cmd (the first [] indicates that it takes no arguments), and is used as follows:

+p{This is an English pangram: \pangram;}

Example 2: Doubling the font size of text

Next, let's define a command \double{«text»} that takes an inline-text as an argument and displays it with the font size doubled. This can be defined as follows:

let-inline ctx \double it =
  let fsize = get-font-size ctx in
  let new-ctx = ctx |> set-font-size (fsize *' 2.) in
  read-inline new-ctx it

It's starting to feel a bit more like programming. While it's not very complex yet, let's briefly go over what's happening:

  • First, we use the get-font-size primitive to get the font size currently set in the text processing context (ctx). fsize will be a value of type length.
  • Next, we use the set-font-size primitive to obtain a new text processing context where "the font size is twice fsize". new-ctx has the type context.
  • Finally, we typeset the inline-text it using the new new-ctx.

The resulting \double command has the type [inline-text] inline-cmd.

Example 3: Resizing the font size by r times

Let's add another argument to the previous example. When changing the font size, let's allow the user to specify the enlargement ratio. We will define a command \enlarge(«ratio»){«text»} that enlarges (or reduces) the text by a factor of ratio. Adding just one argument isn't very difficult.

let-inline ctx \enlarge ratio it =
  let fsize = get-font-size ctx in
  let new-ctx = ctx |> set-font-size (fsize *' ratio) in
  read-inline new-ctx it

The resulting \enlarge command has the type [float; inline-text] inline-cmd.

Example 4: Displaying a pangram with font size enlarged by r times

Next, let's define a command to display a pangram with the font size enlarged by a factor of r. We can achieve this by combining our implementations so far.

let-inline ctx \show-enlarged-pangram ratio =
  let fsize = get-font-size ctx in
  let new-ctx = ctx |> set-font-size (fsize *' ratio) in
  read-inline new-ctx {The quick brown fox jumps over the lazy dog.}

The resulting \show-enlarged-pangram command has the type [float] inline-cmd.

By the way, this command can also be realized by combining the \enlarge and \pangram commands we've already created. Can't we leverage the commands already defined? Of course, we can.

let-inline \show-enlarged-pangram ratio = {\enlarge(ratio){\pangram;}}

With this approach, you can omit the 0-th argument ctx to write it concisely, and it works even if you don't know the internal implementation of \enlarge or \pangram.

Example 5: Resizing font size by r times (defaulting to 2 times)

Consider a use case where "the font size is often doubled, but sometimes set to 1.5 or 3 times." In this case, making the enlargement ratio an optional argument is convenient. Let's change the first argument of the previous \enlarge command into an optional argument.

let-inline ctx \enlarge ?:ratioopt it =
  let ratio = match ratioopt with
    | Some(r) -> r
    | None    -> 2.0
  in
  let fsize = get-font-size ctx in
  let new-ctx = ctx |> set-font-size (fsize *' ratio) in
  read-inline new-ctx it

Optional arguments are indicated by prefixing the argument name with ?:. In this case, ratioopt takes the type float option, and it can be unwrapped into a float type using a match expression.

Defining Block Commands

To define a block command, you must always use the primitive let-block. For example, to define a command +cmd-name that takes N arguments, you write it as follows:

let-block ctx +cmd-name arg1 arg2 ... argN = «expr»

The following elements appear after let-block:

  • ctx: The 0-th argument. It has the context type and represents the "text processing context when the command is called." There is no specific variable name required, but it is common practice to use ctx.
  • +cmd-name: The name of the command you want to define.
  • arg1, ..., argN: Command arguments. They are listed in order.
  • «expr»: The expression representing the content of the definition. You put an expression of type block-boxes here.

...Yes, most of this is a copy-paste from the let-inline explanation. In fact, the usage of the let-block primitive is not much different from let-inline, except that the types change from inline-text or inline-boxes to block-text or block-boxes.

Similar to let-inline, there is also the following syntax:

let-block +cmd-name arg1 arg2 ... argN = «expr»
  • +cmd-name: The name of the command you want to define.
  • arg1, ..., argN: Command arguments. They are listed in order.
  • «expr»: The expression representing the content of the definition. In this syntax, you put an expression of type block-text.

Here is one concrete example.

Example 6: Creating a paragraph (+p)

As seen several times in previous explanations, +p is a command for creating a paragraph. If you simply want to provide the functionality of creating a paragraph, you can define it as follows:

let-block ctx +p inner =
  let ib-inner = read-inline ctx inner in
  let quad-indent = inline-skip (get-font-size ctx) in
  let br-parag = quad-indent ++ ib-inner ++ inline-fil in
  line-break true true ctx br-parag
  • First, convert the first argument inner of type inline-text into inline-boxes.
    The 0-th argument ctx is used for this.
  • Create an inline-box sequence quad-indent corresponding to an indent with a length of a quad space.
  • Insert quad-indent at the beginning of ib-inner and an "infinitely stretching glue in the inline direction" (inline-fil) at the end.
  • Perform line breaking based on the created inline-box sequence.

The resulting +p has the type [inline-text] block-cmd.

Note that in class files such as stdja, the specification is that "paragraphs at the beginning of chapters or sections do not have indents," so it is a bit more complex.

Defining Math Commands

To define a math command, you must always use the primitive let-math.

let-math \cmd-name arg1 arg2 ... argN = «expr»

The following elements appear after let-math:

  • \cmd-name: The name of the command you want to define.
  • arg1, ..., argN: Command arguments. They are listed in order.
  • «expr»: The expression representing the content of the definition. You put an expression of type math here.

For math commands, unlike let-inline or let-block, you cannot specify the text processing context as the 0-th argument (personally, I think it would be useful since there are many situations where you'd want the text processing context in math commands too).

Example 7: Writing Maclaurin expansion formulas easily

I couldn't think of a very good concrete example, so let's try defining a command \exp-coef{n} that provides the n-th term when \exp(x) is expanded by Maclaurin series.
It would look like this:

let-math \exp-coef n = ${\frac{x^{#n}}{#n\!}}

And that's it. Now you've defined a command \exp-coef with the type [math] math-cmd. It's simple. n is a variable of type math, and it can be referenced as #n inside a math literal ${ ... }.

The defined \exp-coef can be used like this:

+math(${
  e^x = \sum_{n=0}^\infty \exp-coef{n}
      = 1 + x + \exp-coef{2} + \exp-coef{3} + \exp-coef{4} + \cdots
});

Easy, right!

Conclusion

Thank you for reading to the end. I've organized things around SATySFi commands over three days: 12/3, 12/10, and today.

Since SATySFi is still a new typesetting language, it would be an overstatement to say it is feature-rich compared to long-standing languages like TeX/LaTeX. While I am developing packages that could be used generally, such as AZmath, the current situation is that each user still has to customize it to fit their own needs when creating full-scale documents. I wrote this article as an entry point for such customization.

I hope that even people who haven't touched SATySFi yet might feel, even if just a little, that "I might be able to write in SATySFi too."

脚注
  1. Regarding document files, the position seems to be that having a .saty extension is not a MUST but a SHOULD. In other words, it will compile even if it's not .saty, but using .saty is strongly recommended. ↩︎

  2. Sometimes the extension .satyg is used, but if you are creating a package file for the purpose of PDF output, for now, it is best to use the .satyh extension. ↩︎

Discussion