@@ -297,41 +297,84 @@ local spaceKind = {
297297 thick = " thick" ,
298298}
299299
300- -- Indexed by left atom
300+ -- Spacing table indexed by left atom, as in TeXbook p. 170.
301+ -- Notes
302+ -- - the "notScript" key is used to prevent spaces in script and scriptscript modes
303+ -- (= parenthesized non-zero value in The TeXbook's table).
304+ -- - Cases commented are as expected, just listed for clarity and completeness.
305+ -- (= no space i.e. 0 in in The TeXbook's table)
306+ -- - Cases marked as impossible are not expected to happen (= stars in the TeXbook):
307+ -- "... such cases never arise, because binary atoms must be preceded and followed
308+ -- by atoms compatible with the nature of binary operations."
309+ -- This must be understood with the context explained onp. 133:
310+ -- "... binary operations are treated as ordinary symbols if they don’t occur
311+ -- between two quantities that they can operate on." (a rule which notably helps
312+ -- addressing binary atoms used as unary operators.)
301313local spacingRules = {
302314 [atomType .ordinary ] = {
315+ -- [atomType.ordinary] = nil
303316 [atomType .bigOperator ] = { spaceKind .thin },
304317 [atomType .binaryOperator ] = { spaceKind .med , notScript = true },
305318 [atomType .relationalOperator ] = { spaceKind .thick , notScript = true },
319+ -- [atomType.openingSymbol] = nil
320+ -- [atomType.closeSymbol] = nil
321+ -- [atomType.punctuationSymbol] = nil
306322 [atomType .inner ] = { spaceKind .thin , notScript = true },
307323 },
308324 [atomType .bigOperator ] = {
309325 [atomType .ordinary ] = { spaceKind .thin },
310326 [atomType .bigOperator ] = { spaceKind .thin },
327+ [atomType .binaryOperator ] = { impossible = true },
311328 [atomType .relationalOperator ] = { spaceKind .thick , notScript = true },
329+ -- [atomType.openingSymbol] = nil
330+ -- [atomType.closeSymbol] = nil
331+ -- [atomType.punctuationSymbol] = nil
312332 [atomType .inner ] = { spaceKind .thin , notScript = true },
313333 },
314334 [atomType .binaryOperator ] = {
315335 [atomType .ordinary ] = { spaceKind .med , notScript = true },
316336 [atomType .bigOperator ] = { spaceKind .med , notScript = true },
337+ [atomType .binaryOperator ] = { impossible = true },
338+ [atomType .relationalOperator ] = { impossible = true },
317339 [atomType .openingSymbol ] = { spaceKind .med , notScript = true },
340+ [atomType .closeSymbol ] = { impossible = true },
341+ [atomType .punctuationSymbol ] = { impossible = true },
318342 [atomType .inner ] = { spaceKind .med , notScript = true },
319343 },
320344 [atomType .relationalOperator ] = {
321345 [atomType .ordinary ] = { spaceKind .thick , notScript = true },
322346 [atomType .bigOperator ] = { spaceKind .thick , notScript = true },
347+ [atomType .binaryOperator ] = { impossible = true },
348+ -- [atomType.relationalOperator] = nil
323349 [atomType .openingSymbol ] = { spaceKind .thick , notScript = true },
350+ -- [atomType.closeSymbol] = nil
351+ -- [atomType.punctuationSymbol] = nil
324352 [atomType .inner ] = { spaceKind .thick , notScript = true },
325353 },
354+ [atomType .openingSymbol ] = {
355+ -- [atomType.ordinary] = nil
356+ -- [atomType.bigOperator] = nil
357+ [atomType .binaryOperator ] = { impossible = true },
358+ -- [atomType.relationalOperator] = nil
359+ -- [atomType.openingSymbol] = nil
360+ -- [atomType.closeSymbol] = nil
361+ -- [atomType.punctuationSymbol] = nil
362+ -- [atomType.inner] = nil
363+ },
326364 [atomType .closeSymbol ] = {
365+ -- [atomType.ordinary] = nil
327366 [atomType .bigOperator ] = { spaceKind .thin },
328367 [atomType .binaryOperator ] = { spaceKind .med , notScript = true },
329368 [atomType .relationalOperator ] = { spaceKind .thick , notScript = true },
369+ -- [atomType.openingSymbol] = nil
370+ -- [atomType.closeSymbol] = nil
371+ -- [atomType.punctuationSymbol] = nil
330372 [atomType .inner ] = { spaceKind .thin , notScript = true },
331373 },
332374 [atomType .punctuationSymbol ] = {
333375 [atomType .ordinary ] = { spaceKind .thin , notScript = true },
334376 [atomType .bigOperator ] = { spaceKind .thin , notScript = true },
377+ [atomType .binaryOperator ] = { impossible = true },
335378 [atomType .relationalOperator ] = { spaceKind .thin , notScript = true },
336379 [atomType .openingSymbol ] = { spaceKind .thin , notScript = true },
337380 [atomType .closeSymbol ] = { spaceKind .thin , notScript = true },
@@ -345,6 +388,7 @@ local spacingRules = {
345388 [atomType .relationalOperator ] = { spaceKind .thick , notScript = true },
346389 [atomType .openingSymbol ] = { spaceKind .thin , notScript = true },
347390 [atomType .punctuationSymbol ] = { spaceKind .thin , notScript = true },
391+ -- [atomType.closeSymbol] = nil
348392 [atomType .inner ] = { spaceKind .thin , notScript = true },
349393 },
350394}
@@ -377,14 +421,51 @@ function elements.stackbox:styleChildren ()
377421 end
378422 if self .direction == " H" then
379423 -- Insert spaces according to the atom type, following Knuth's guidelines
380- -- in the TeXbook
424+ -- in The TeXbook, p. 170 (amended with p. 133 for binary operators)
425+ -- FIXME: This implementation is not using the atom form and the MathML logic (lspace/rspace).
426+ -- (This is notably unsatisfactory for <mphantom> elements)
381427 local spaces = {}
428+ if # self .children >= 1 then
429+ -- An interpretation of the TeXbook p. 133 for binary operator exceptions:
430+ -- A binary operator at the beginning of the expression is treated as an ordinary atom
431+ -- (so as to be considered as a unary operator, without more context).
432+ local v = self .children [1 ]
433+ if v .atom == atomType .binaryOperator then
434+ v .atom = atomType .ordinary
435+ end
436+ end
382437 for i = 1 , # self .children - 1 do
383438 local v = self .children [i ]
384439 local v2 = self .children [i + 1 ]
385440 if spacingRules [v .atom ] and spacingRules [v .atom ][v2 .atom ] then
386441 local rule = spacingRules [v .atom ][v2 .atom ]
387- if not (rule .notScript and (isScriptMode (self .mode ) or isScriptScriptMode (self .mode ))) then
442+ if rule .impossible then
443+ -- Another interpretation of the TeXbook p. 133 for binary operator exceptions:
444+ if v2 .atom == atomType .binaryOperator then
445+ -- If a binary atom follows an atom that is not compatible with it, make it an ordinary.
446+ -- (so as to be conidered as a unary operator).
447+ -- Typical case: "a = -b" (ord rel bin ord), "a + -b" (ord bin bin ord)
448+ v2 .atom = atomType .ordinary
449+ else
450+ -- If a binary atom precedes an atom that is not compatible with it, make it an ordinary.
451+ -- Quite unusual case (bin, rel/close/punct) unlikely to happen in practice.
452+ -- (Not seen in 80+ test formulas)
453+ -- We might address it a bit late here, the preceding atom has already based its spacing
454+ -- on the binary atom... but this might not be a big deal.
455+ -- (i.e. rather than add an extra look-ahead just for this case).
456+ -- Artificial example: "a + = b" (ord bin rel ord)
457+ v .atom = atomType .ordinary
458+ end
459+ rule = spacingRules [v .atom ][v2 .atom ]
460+ if rule and rule .impossible then
461+ -- Should not occur if we did our table based on the TeXbook correctly?
462+ -- We can still handle it by ignoring the rule: no spacing sounds logical.
463+ -- But let's have a warning so it might be investigated further.
464+ SU .warn (" Impossible spacing rule for (" .. v .atom .. " , " .. v2 .atom .. " ), please report this issue" )
465+ rule = nil
466+ end
467+ end
468+ if rule and not (rule .notScript and (isScriptMode (self .mode ) or isScriptScriptMode (self .mode ))) then
388469 spaces [i + 1 ] = rule [1 ]
389470 end
390471 end
0 commit comments