import { OutputFormat } from '../public/mathfield' ; import { InlineShortcutDefinition , getInlineShortcut } from './shortcuts' ; import { INLINE_SHORTCUTS } from './shortcuts-definitions' ; /**
 * Attempts to parse and interpret a string in an unknown format, possibly
 * ASCIIMath and return a canonical LaTeX string.
 *
 * The format recognized are one of these variations:
 * - ASCIIMath: Only supports a subset
 * (1/2x)
 * 1/2sin x                     -> \frac {1}{2}\sin x
 * 1/2sinx                      -> \frac {1}{2}\sin x
 * (1/2sin x (x^(2+1))          // Unbalanced parentheses
 * (1/2sin(x^(2+1))             -> \left(\frac {1}{2}\sin \left(x^{2+1}\right)\right)
 * alpha + (pi)/(4)             -> \alpha +\frac {\pi }{4}
 * x=(-b +- sqrt(b^2 – 4ac))/(2a)
 * alpha/beta
 * sqrt2 + sqrtx + sqrt(1+a) + sqrt(1/2)
 * f(x) = x^2 "when" x >= 0
 * AA n in QQ
 * AA x in RR "," |x| > 0
 * AA x in RR "," abs(x) > 0
 *
 * - UnicodeMath (generated by Microsoft Word): also only supports a subset
 *      - See https://www.unicode.org/notes/tn28/UTN28-PlainTextMath-v3.1.pdf
 * √(3&x+1)
 * {a+b/c}
 * [a+b/c]
 * _a^b x
 * lim_(n->\infty) n
 * \iint_(a=0)^\infty  a
 *
 * - "JavaScript Latex": a variant that is LaTeX, but with escaped backslashes
 *  \\frac{1}{2} \\sin x
 */ export function parseMathString ( s , options ? ) { let format ; [ format , s ] = inferFormat ( s ) ; if ( format === 'latex' ) return [ 'latex' , s ] ; format = 'ascii-math' ; s = s . replace ( /\u2061/gu , '' ) ; s = s . replace ( /\u3016/gu , '{' ) ; s = s . replace ( /\u3017/gu , '}' ) ; s = s . replace ( /([^\\])sinx/g , '$1\\sin x' ) ; s = s . replace ( /([^\\])cosx/g , '$1\\cos x ' ) ; s = s . replace ( /\u2013/g , '-' ) ; return [ format , parseMathExpression ( s , { inlineShortcuts : options ?. inlineShortcuts } ) , ] ; } function parseMathExpression ( s , options ) { if ( ! s ) return '' ; let done = false ; let m ; const inlineShortcuts = options . inlineShortcuts ?? INLINE_SHORTCUTS ; if ( ! done && ( s . startsWith ( '^' ) || s . startsWith ( '_' ) ) ) { m = parseMathArgument ( s . slice ( 1 ) , { inlineShortcuts , noWrap : true } ) ; s = s [ 0 ] + '{' + m . match + '}' ; s += parseMathExpression ( m . rest , options ) ; done = true ; } if ( ! done ) { m = s . match ( /^(sqrt|\u221A)(.*)/ ) ; if ( m ) { m = parseMathArgument ( m [ 2 ] , { inlineShortcuts , noWrap : true } ) ; const sqrtArgument = m . match ?? '\\placeholder{}' ; s = '\\sqrt{' + sqrtArgument + '}' ; s += parseMathExpression ( m . rest , options ) ; done = true ; } } if ( ! done ) { m = s . match ( /^(\\cbrt|\u221B)(.*)/ ) ; if ( m ) { m = parseMathArgument ( m [ 2 ] , { inlineShortcuts , noWrap : true } ) ; const sqrtArgument = m . match ?? '\\placeholder{}' ; s = '\\sqrt[3]{' + sqrtArgument + '}' ; s += parseMathExpression ( m . rest , options ) ; done = true ; } } if ( ! done ) { m = s . match ( /^abs(.*)/ ) ; if ( m ) { m = parseMathArgument ( m [ 1 ] , { inlineShortcuts , noWrap : true } ) ; s = '\\left|' + m . match + '\\right|' ; s += parseMathExpression ( m . rest , options ) ; done = true ; } } if ( ! done ) { m = s . match ( /^["”“](.*?)["”“](.*)/ ) ; if ( m ) { s = '\\text{' + m [ 1 ] + '}' ; s += parseMathExpression ( m [ 2 ] , options ) ; done = true ; } } if ( ! done ) { m = s . match ( /^([^a-zA-Z({[_^\\\s"]+)(.*)/ ) ; if ( m ) { s = paddedShortcut ( m [ 1 ] , inlineShortcuts ) ; s += parseMathExpression ( m [ 2 ] , options ) ; done = true ; } } if ( ! done && /^([fgh])[^a-zA-Z]/ . test ( s ) ) { m = parseMathArgument ( s . slice ( 1 ) , { inlineShortcuts , noWrap : true } ) ; s = s [ 1 ] === '(' ? s [ 0 ] + '\\mleft(' + m . match + '\\mright)' : s [ 0 ] + m . match ; s += parseMathExpression ( m . rest , options ) ; done = true ; } if ( ! done ) { m = s . match ( /^([a-zA-Z]+)(.*)/ ) ; if ( m ) { s = paddedShortcut ( m [ 1 ] , inlineShortcuts ) ; s += parseMathExpression ( m [ 2 ] , options ) ; done = true ; } } if ( ! done ) { m = parseMathArgument ( s , { inlineShortcuts , noWrap : true } ) ; if ( m . match && m . rest [ 0 ] === '/' ) { const m2 = parseMathArgument ( m . rest . slice ( 1 ) , { inlineShortcuts , noWrap : true , } ) ; if ( m2 . match ) { s = '\\frac{' + m . match + '}{' + m2 . match + '}' + parseMathExpression ( m2 . rest , options ) ; } done = true ; } else if ( m . match ) { s = s . startsWith ( '(' ) ? '\\left(' + m . match + '\\right)' + parseMathExpression ( m . rest , options ) : m . match + parseMathExpression ( m . rest , options ) ; done = true ; } } if ( ! done ) { m = s . match ( /^(\s+)(.*)$/ ) ; if ( m ) { s = ' ' + parseMathExpression ( m [ 2 ] , options ) ; done = true ; } } return s ; } return function parseMathArgument ( s , options ) { let match = '' ; s = s . trim ( ) ; let rest = s ; let lFence = s . charAt ( 0 ) ; let rFence = { '(' : ')' , '{' : '}' , '[' : ']' } [ lFence ] ; if ( rFence ) { let level = 1 ; let i = 1 ; while ( i < s . length && level > 0 ) { if ( s [ i ] === lFence ) level ++ ; if ( s [ i ] === rFence ) level -- ; i ++ ; } if ( level === 0 ) { if ( options . noWrap && lFence === '(' ) { match = parseMathExpression ( s . substring ( 1 , i - 1 ) , options ) ; } else { if ( lFence === '{' && rFence === '}' ) { lFence = '\\{' ; rFence = '\\}' ; } match = '\\left' + lFence + parseMathExpression ( s . substring ( 1 , i - 1 ) , options ) + '\\right' + rFence ; } rest = s . slice ( Math . max ( 0 , i ) ) ; } else { match = s . substring ( 1 , i ) ; rest = '' ; } } else { let m = s . match ( /^([a-zA-Z]+)/ ) ; if ( m ) { let shortcut = getInlineShortcut ( null , s , options . inlineShortcuts ) ; if ( shortcut ) { shortcut = shortcut . replace ( '_{#?}' , '' ) ; shortcut = shortcut . replace ( '^{#?}' , '' ) ; return { match : shortcut , rest : s . slice ( shortcut . length ) } ; } } m = s . match ( /^([a-zA-Z])/ ) ; if ( m ) { return { match : m [ 1 ] , rest : s . slice ( 1 ) } ; } m = s . match ( /^(-)?\d+(\.\d*)?/ ) ; if ( m ) { return { match : m [ 0 ] , rest : s . slice ( m [ 0 ] . length ) } ; } if ( ! /^\\(left|right)/ . test ( s ) ) { m = s . match ( /^(\\[a-zA-Z]+)/ ) ; if ( m ) { rest = s . slice ( m [ 1 ] . length ) ; match = m [ 1 ] ; } } } return { match , rest } ; } function paddedShortcut ( s , shortcuts ? ) { let result = getInlineShortcut ( null , s , shortcuts ) ; if ( result ) { result = result . replace ( '_{#?}' , '' ) ; result = result . replace ( '^{#?}' , '' ) ; result += ' ' ; } else { result = s ; } return result ; } export const MODE_SHIFT_COMMANDS = [ [ '\\[' , '\\]' ] , [ '\\(' , '\\)' ] , [ '$$' , '$$' ] , [ '$' , '$' ] , [ '\\begin{math}' , '\\end{math}' ] , [ '\\begin{displaymath}' , '\\end{displaymath}' ] , [ '\\begin{equation}' , '\\end{equation}' ] , [ '\\begin{equation*}' , '\\end{equation*}' ] , ] ; export function trimModeShiftCommand ( s ) { const trimedString = s . trim ( ) ; for ( const mode of MODE_SHIFT_COMMANDS ) { if ( trimedString . startsWith ( mode [ 0 ] ) && trimedString . endsWith ( mode [ 1 ] ) ) { return [ true , trimedString . substring ( mode [ 0 ] . length , trimedString . length - mode [ 1 ] . length ) , ] ; } } return [ false , s ] ; } function inferFormat ( s ) { s = s . trim ( ) ; if ( ! s ) return [ 'latex' , '' ] ; if ( s . length <= 1 ) return [ 'latex' , s ] ; s = s . replace ( /\\\\([^\s\n])/g , '\\$1' ) ; let hasLatexModeShiftCommand ; [ hasLatexModeShiftCommand , s ] = trimModeShiftCommand ( s ) ; if ( hasLatexModeShiftCommand ) return [ 'latex' , s ] ; if ( s . startsWith ( '`' ) && s . endsWith ( '`' ) ) { s = s . substring ( 1 , s . length - 1 ) ; return [ 'ascii-math' , s ] ; } if ( s . includes ( '\\' ) ) { return [ 'latex' , s ] ; } if ( /\$(.+)\$/ . test ( s ) ) { return [ 'latex' , `\\text{${ s }}` ] ; } return [ undefined , s ] ; }