www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

environments.js (7753B)


      1 /* eslint no-constant-condition:0 */
      2 const parseData = require("./parseData");
      3 const ParseError = require("./ParseError");
      4 const Style = require("./Style");
      5 
      6 const ParseNode = parseData.ParseNode;
      7 
      8 /**
      9  * Parse the body of the environment, with rows delimited by \\ and
     10  * columns delimited by &, and create a nested list in row-major order
     11  * with one group per cell.  If given an optional argument style
     12  * ("text", "display", etc.), then each cell is cast into that style.
     13  */
     14 function parseArray(parser, result, style) {
     15     let row = [];
     16     const body = [row];
     17     const rowGaps = [];
     18     while (true) {
     19         let cell = parser.parseExpression(false, null);
     20         cell = new ParseNode("ordgroup", cell, parser.mode);
     21         if (style) {
     22             cell = new ParseNode("styling", {
     23                 style: style,
     24                 value: [cell],
     25             }, parser.mode);
     26         }
     27         row.push(cell);
     28         const next = parser.nextToken.text;
     29         if (next === "&") {
     30             parser.consume();
     31         } else if (next === "\\end") {
     32             break;
     33         } else if (next === "\\\\" || next === "\\cr") {
     34             const cr = parser.parseFunction();
     35             rowGaps.push(cr.value.size);
     36             row = [];
     37             body.push(row);
     38         } else {
     39             throw new ParseError("Expected & or \\\\ or \\end",
     40                                  parser.nextToken);
     41         }
     42     }
     43     result.body = body;
     44     result.rowGaps = rowGaps;
     45     return new ParseNode(result.type, result, parser.mode);
     46 }
     47 
     48 /*
     49  * An environment definition is very similar to a function definition:
     50  * it is declared with a name or a list of names, a set of properties
     51  * and a handler containing the actual implementation.
     52  *
     53  * The properties include:
     54  *  - numArgs: The number of arguments after the \begin{name} function.
     55  *  - argTypes: (optional) Just like for a function
     56  *  - allowedInText: (optional) Whether or not the environment is allowed inside
     57  *                   text mode (default false) (not enforced yet)
     58  *  - numOptionalArgs: (optional) Just like for a function
     59  * A bare number instead of that object indicates the numArgs value.
     60  *
     61  * The handler function will receive two arguments
     62  *  - context: information and references provided by the parser
     63  *  - args: an array of arguments passed to \begin{name}
     64  * The context contains the following properties:
     65  *  - envName: the name of the environment, one of the listed names.
     66  *  - parser: the parser object
     67  *  - lexer: the lexer object
     68  *  - positions: the positions associated with these arguments from args.
     69  * The handler must return a ParseResult.
     70  */
     71 
     72 function defineEnvironment(names, props, handler) {
     73     if (typeof names === "string") {
     74         names = [names];
     75     }
     76     if (typeof props === "number") {
     77         props = { numArgs: props };
     78     }
     79     // Set default values of environments
     80     const data = {
     81         numArgs: props.numArgs || 0,
     82         argTypes: props.argTypes,
     83         greediness: 1,
     84         allowedInText: !!props.allowedInText,
     85         numOptionalArgs: props.numOptionalArgs || 0,
     86         handler: handler,
     87     };
     88     for (let i = 0; i < names.length; ++i) {
     89         module.exports[names[i]] = data;
     90     }
     91 }
     92 
     93 // Decides on a style for cells in an array according to whether the given
     94 // environment name starts with the letter 'd'.
     95 function dCellStyle(envName) {
     96     if (envName.substr(0, 1) === "d") {
     97         return "display";
     98     } else {
     99         return "text";
    100     }
    101 }
    102 
    103 // Arrays are part of LaTeX, defined in lttab.dtx so its documentation
    104 // is part of the source2e.pdf file of LaTeX2e source documentation.
    105 // {darray} is an {array} environment where cells are set in \displaystyle,
    106 // as defined in nccmath.sty.
    107 defineEnvironment(["array", "darray"], {
    108     numArgs: 1,
    109 }, function(context, args) {
    110     let colalign = args[0];
    111     colalign = colalign.value.map ? colalign.value : [colalign];
    112     const cols = colalign.map(function(node) {
    113         const ca = node.value;
    114         if ("lcr".indexOf(ca) !== -1) {
    115             return {
    116                 type: "align",
    117                 align: ca,
    118             };
    119         } else if (ca === "|") {
    120             return {
    121                 type: "separator",
    122                 separator: "|",
    123             };
    124         }
    125         throw new ParseError(
    126             "Unknown column alignment: " + node.value,
    127             node);
    128     });
    129     let res = {
    130         type: "array",
    131         cols: cols,
    132         hskipBeforeAndAfter: true, // \@preamble in lttab.dtx
    133     };
    134     res = parseArray(context.parser, res, dCellStyle(context.envName));
    135     return res;
    136 });
    137 
    138 // The matrix environments of amsmath builds on the array environment
    139 // of LaTeX, which is discussed above.
    140 defineEnvironment([
    141     "matrix",
    142     "pmatrix",
    143     "bmatrix",
    144     "Bmatrix",
    145     "vmatrix",
    146     "Vmatrix",
    147 ], {
    148 }, function(context) {
    149     const delimiters = {
    150         "matrix": null,
    151         "pmatrix": ["(", ")"],
    152         "bmatrix": ["[", "]"],
    153         "Bmatrix": ["\\{", "\\}"],
    154         "vmatrix": ["|", "|"],
    155         "Vmatrix": ["\\Vert", "\\Vert"],
    156     }[context.envName];
    157     let res = {
    158         type: "array",
    159         hskipBeforeAndAfter: false, // \hskip -\arraycolsep in amsmath
    160     };
    161     res = parseArray(context.parser, res, dCellStyle(context.envName));
    162     if (delimiters) {
    163         res = new ParseNode("leftright", {
    164             body: [res],
    165             left: delimiters[0],
    166             right: delimiters[1],
    167         }, context.mode);
    168     }
    169     return res;
    170 });
    171 
    172 // A cases environment (in amsmath.sty) is almost equivalent to
    173 // \def\arraystretch{1.2}%
    174 // \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right.
    175 // {dcases} is a {cases} environment where cells are set in \displaystyle,
    176 // as defined in mathtools.sty.
    177 defineEnvironment([
    178     "cases",
    179     "dcases",
    180 ], {
    181 }, function(context) {
    182     let res = {
    183         type: "array",
    184         arraystretch: 1.2,
    185         cols: [{
    186             type: "align",
    187             align: "l",
    188             pregap: 0,
    189             // TODO(kevinb) get the current style.
    190             // For now we use the metrics for TEXT style which is what we were
    191             // doing before.  Before attempting to get the current style we
    192             // should look at TeX's behavior especially for \over and matrices.
    193             postgap: Style.TEXT.metrics.quad,
    194         }, {
    195             type: "align",
    196             align: "l",
    197             pregap: 0,
    198             postgap: 0,
    199         }],
    200     };
    201     res = parseArray(context.parser, res, dCellStyle(context.envName));
    202     res = new ParseNode("leftright", {
    203         body: [res],
    204         left: "\\{",
    205         right: ".",
    206     }, context.mode);
    207     return res;
    208 });
    209 
    210 // An aligned environment is like the align* environment
    211 // except it operates within math mode.
    212 // Note that we assume \nomallineskiplimit to be zero,
    213 // so that \strut@ is the same as \strut.
    214 defineEnvironment("aligned", {
    215 }, function(context) {
    216     let res = {
    217         type: "array",
    218         cols: [],
    219     };
    220     res = parseArray(context.parser, res);
    221     const emptyGroup = new ParseNode("ordgroup", [], context.mode);
    222     let numCols = 0;
    223     res.value.body.forEach(function(row) {
    224         for (let i = 1; i < row.length; i += 2) {
    225             row[i].value.unshift(emptyGroup);
    226         }
    227         if (numCols < row.length) {
    228             numCols = row.length;
    229         }
    230     });
    231     for (let i = 0; i < numCols; ++i) {
    232         let align = "r";
    233         let pregap = 0;
    234         if (i % 2 === 1) {
    235             align = "l";
    236         } else if (i > 0) {
    237             pregap = 2; // one \qquad between columns
    238         }
    239         res.value.cols[i] = {
    240             type: "align",
    241             align: align,
    242             pregap: pregap,
    243             postgap: 0,
    244         };
    245     }
    246     return res;
    247 });