diff --git a/ts/input/tex/ParseMethods.ts b/ts/input/tex/ParseMethods.ts index ec87d6b57..1055e371b 100644 --- a/ts/input/tex/ParseMethods.ts +++ b/ts/input/tex/ParseMethods.ts @@ -40,10 +40,11 @@ namespace ParseMethods { export function variable(parser: TexParser, c: string) { // @test Identifier Font const def = ParseUtil.getFontDef(parser); - if (parser.stack.env.multiLetterIdentifiers && parser.stack.env.font !== '') { - c = parser.string.substr(parser.i - 1).match(/^[a-z]+/i)[0]; + const env = parser.stack.env; + if (env.multiLetterIdentifiers && env.font !== '') { + c = parser.string.substr(parser.i - 1).match(env.multiLetterIdentifiers as RegExp)[0]; parser.i += c.length - 1; - if (def.mathvariant === TexConstant.Variant.NORMAL) { + if (def.mathvariant === TexConstant.Variant.NORMAL && env.noAutoOP && c.length > 1) { def.autoOP = false; } } diff --git a/ts/input/tex/StackItem.ts b/ts/input/tex/StackItem.ts index f6d01417c..2175f9d4b 100644 --- a/ts/input/tex/StackItem.ts +++ b/ts/input/tex/StackItem.ts @@ -28,7 +28,7 @@ import TexError from './TexError.js'; import StackItemFactory from './StackItemFactory.js'; // Union types for abbreviation. -export type EnvProp = string | number | boolean; +export type EnvProp = string | number | boolean | RegExp; export type EnvList = {[key: string]: EnvProp}; diff --git a/ts/input/tex/ams/AmsConfiguration.ts b/ts/input/tex/ams/AmsConfiguration.ts index 0fd4f7ce2..31d2c3d41 100644 --- a/ts/input/tex/ams/AmsConfiguration.ts +++ b/ts/input/tex/ams/AmsConfiguration.ts @@ -51,6 +51,7 @@ let init = function(config: ParserConfiguration) { export const AmsConfiguration = Configuration.create( 'ams', { handler: { + character: ['AMSmath-operatorLetter'], delimiter: ['AMSsymbols-delimiter', 'AMSmath-delimiter'], macro: ['AMSsymbols-mathchar0mi', 'AMSsymbols-mathchar0mo', 'AMSsymbols-delimiter', 'AMSsymbols-macros', diff --git a/ts/input/tex/ams/AmsMappings.ts b/ts/input/tex/ams/AmsMappings.ts index 974929baf..b2e0f15c2 100644 --- a/ts/input/tex/ams/AmsMappings.ts +++ b/ts/input/tex/ams/AmsMappings.ts @@ -39,6 +39,10 @@ new sm.CharacterMap('AMSmath-mathchar0mo', ParseMethods.mathchar0mo, { iiiint: ['\u2A0C', {texClass: TEXCLASS.OP}] }); +/** + * Extra characters that are letters in \operatorname + */ +new sm.RegExpMap('AMSmath-operatorLetter', AmsMethods.operatorLetter, /[-*]/i); /** * Macros from the AMS Math package. diff --git a/ts/input/tex/ams/AmsMethods.ts b/ts/input/tex/ams/AmsMethods.ts index dfaa98e79..fd5c5e8c1 100644 --- a/ts/input/tex/ams/AmsMethods.ts +++ b/ts/input/tex/ams/AmsMethods.ts @@ -26,6 +26,7 @@ import {StackItem} from '../StackItem.js'; import {ParseMethod} from '../Types.js'; import ParseUtil from '../ParseUtil.js'; +import ParseMethods from '../ParseMethods.js'; import NodeUtil from '../NodeUtil.js'; import {TexConstant} from '../TexConstants.js'; import TexParser from '../TexParser.js'; @@ -202,17 +203,14 @@ export const NEW_OPS = 'ams-declare-ops'; * @param {string} name The macro name. */ AmsMethods.HandleDeclareOp = function (parser: TexParser, name: string) { - let limits = (parser.GetStar() ? '' : '\\nolimits\\SkipLimits'); + let star = (parser.GetStar() ? '*' : ''); let cs = ParseUtil.trimSpaces(parser.GetArgument(name)); if (cs.charAt(0) === '\\') { cs = cs.substr(1); } let op = parser.GetArgument(name); - if (!op.match(/\\text/)) { - op = op.replace(/\*/g, '\\text{*}').replace(/-/g, '\\text{-}'); - } (parser.configuration.handlers.retrieve(NEW_OPS) as CommandMap). - add(cs, new Macro(cs, AmsMethods.Macro, ['\\mathop{\\rm ' + op + '}' + limits])); + add(cs, new Macro(cs, AmsMethods.Macro, [`\\operatorname${star}{${op}}`])); }; @@ -223,28 +221,47 @@ AmsMethods.HandleDeclareOp = function (parser: TexParser, name: string) { */ AmsMethods.HandleOperatorName = function(parser: TexParser, name: string) { // @test Operatorname - const limits = (parser.GetStar() ? '' : '\\nolimits\\SkipLimits'); + const star = parser.GetStar(); + // + // Parse the argument using operator letters and grouping multiple letters. + // let op = ParseUtil.trimSpaces(parser.GetArgument(name)); - if (!op.match(/\\text/)) { - op = op.replace(/\*/g, '\\text{*}').replace(/-/g, '\\text{-}'); + let mml = new TexParser(op, { + ...parser.stack.env, + font: TexConstant.Variant.NORMAL, + multiLetterIdentifiers: /^[-*a-z]+/i, + operatorLetters: true + }, parser.configuration).mml(); + // + // If we get something other than a single mi, wrap in a TeXAtom. + // + if (!mml.isKind('mi')) { + mml = parser.create('node', 'TeXAtom', [mml]); } - parser.string = '\\mathop{\\rm ' + op + '}' + limits + ' ' + - parser.string.slice(parser.i); - parser.i = 0; + // + // Mark the limit properties and the TeX class. + // + NodeUtil.setProperties(mml, {movesupsub: star, movablelimits: true, texClass: TEXCLASS.OP}); + // + // Skip a following \limits macro if not a starred operator + // + if (!star) { + const c = parser.GetNext(), i = parser.i; + if (c === '\\' && ++parser.i && parser.GetCS() !== 'limits') { + parser.i = i; + } + } + // + parser.Push(mml); }; - /** - * Handle SkipLimits. + * Handle extra letters in \operatorname (- and *), default to normal otherwise. * @param {TexParser} parser The calling parser. - * @param {string} name The macro name. + * @param {string} c The letter being checked */ -AmsMethods.SkipLimits = function(parser: TexParser, _name: string) { - // @test Operatorname - const c = parser.GetNext(), i = parser.i; - if (c === '\\' && ++parser.i && parser.GetCS() !== 'limits') { - parser.i = i; - } +AmsMethods.operatorLetter = function (parser: TexParser, c: string) { + return parser.stack.env.operatorLetters ? ParseMethods.variable(parser, c) : false; }; diff --git a/ts/input/tex/base/BaseMethods.ts b/ts/input/tex/base/BaseMethods.ts index 77d2e0bc3..62f72a8ec 100644 --- a/ts/input/tex/base/BaseMethods.ts +++ b/ts/input/tex/base/BaseMethods.ts @@ -282,7 +282,8 @@ BaseMethods.MathFont = function(parser: TexParser, name: string, variant: string let mml = new TexParser(text, { ...parser.stack.env, font: variant, - multiLetterIdentifiers: true + multiLetterIdentifiers: /^[a-z]+/i, + noAutoOP: true }, parser.configuration).mml(); parser.Push(parser.create('node', 'TeXAtom', [mml])); };