@@ -49,55 +49,75 @@ export function canonicalInvisibleOperator(
4949 // Is it a function application: symbol with a function
5050 // definition followed by delimiter
5151 //
52- if ( lhs . symbol && rhs . operator === 'Delimiter' ) {
52+ // Note: lhs might be a Subscript (e.g., f_\text{a}) which canonicalizes
53+ // to a symbol (f_a). Canonicalize first to handle this case.
54+ const lhsCanon = lhs . canonical ;
55+ if ( lhsCanon . symbol && rhs . operator === 'Delimiter' ) {
5356 // We have encountered something like `f(a+b)`, where `f` is not
5457 // defined. But it also could be `x(x+1)` where `x` is a number.
5558 // So, start with boxing the arguments and see if it makes sense.
5659
5760 // No arguments, i.e. `f()`? It's definitely a function call.
5861 if ( rhs . nops === 0 ) {
59- const def = ce . lookupDefinition ( lhs . symbol ) ;
62+ const def = ce . lookupDefinition ( lhsCanon . symbol ) ;
6063 if ( def ) {
6164 if ( isOperatorDef ( def ) ) {
6265 // It's a known operator, all good (the canonicalization
6366 // will check the arity)
64- return ce . box ( [ lhs . symbol ] ) ;
67+ return ce . box ( [ lhsCanon . symbol ] ) ;
6568 }
6669
6770 if ( def . value . type . isUnknown ) {
68- lhs . infer ( 'function' ) ;
69- return ce . box ( [ lhs . symbol ] ) ;
71+ lhsCanon . infer ( 'function' ) ;
72+ return ce . box ( [ lhsCanon . symbol ] ) ;
7073 }
7174
72- if ( def . value . type . matches ( 'function' ) ) return ce . box ( [ lhs . symbol ] ) ;
75+ if ( def . value . type . matches ( 'function' ) )
76+ return ce . box ( [ lhsCanon . symbol ] ) ;
7377
7478 // Uh. Oh. It's a symbol with a value that is not a function.
75- return ce . typeError ( 'function' , def . value . type , lhs ) ;
79+ return ce . typeError ( 'function' , def . value . type , lhsCanon ) ;
7680 }
77- ce . declare ( lhs . symbol , 'function' ) ;
78- return ce . box ( [ lhs . symbol ] ) ;
81+ ce . declare ( lhsCanon . symbol , 'function' ) ;
82+ return ce . box ( [ lhsCanon . symbol ] ) ;
7983 }
8084
81- // Parse the arguments first, in case they reference lhs .symbol
85+ // Parse the arguments first, in case they reference lhsCanon .symbol
8286 // i.e. `x(x+1)`.
8387 let args = rhs . op1 . operator === 'Sequence' ? rhs . op1 . ops ! : [ rhs . op1 ] ;
8488 args = flatten ( args ) ;
85- if ( ! ce . lookupDefinition ( lhs . symbol ) ) {
86- // Still not a symbol (i.e. wasn't used as a symbol in the
87- // subexpression), so it's a function call.
88- ce . declare ( lhs . symbol , 'function' ) ;
89- return ce . function ( lhs . symbol , args ) ;
89+
90+ const def = ce . lookupDefinition ( lhsCanon . symbol ) ;
91+ if ( ! def ) {
92+ // Symbol not defined, so it's a function call - declare and return
93+ ce . declare ( lhsCanon . symbol , 'function' ) ;
94+ return ce . function ( lhsCanon . symbol , args ) ;
95+ }
96+
97+ // Symbol is defined - check if it's a function or has unknown type
98+ // (unknown type means it was auto-declared and should be treated as function)
99+ if ( isOperatorDef ( def ) || def . value ?. type ?. matches ( 'function' ) ) {
100+ return ce . function ( lhsCanon . symbol , args ) ;
90101 }
102+
103+ if ( def . value ?. type ?. isUnknown ) {
104+ // Type is unknown - infer as function and return function call
105+ lhsCanon . infer ( 'function' ) ;
106+ return ce . function ( lhsCanon . symbol , args ) ;
107+ }
108+
109+ // Symbol is defined but not as a function - fall through to check
110+ // if it might be multiplication (e.g., x(x+1) where x is a number)
91111 }
92112
93113 // Is is an index operation, i.e. "v[1,2]"?
94114 if (
95- lhs . symbol &&
115+ lhsCanon . symbol &&
96116 rhs . operator === 'Delimiter' &&
97117 ( rhs . op2 . string === '[,]' || rhs . op2 . string === '[;]' )
98118 ) {
99119 const args = rhs . op1 . operator === 'Sequence' ? rhs . op1 . ops ! : [ rhs . op1 ] ;
100- return ce . function ( 'At' , [ lhs , ...args ] ) ;
120+ return ce . function ( 'At' , [ lhsCanon , ...args ] ) ;
101121 }
102122 }
103123
0 commit comments