@@ -4,15 +4,15 @@ import type {
44BoxedSubstitution ,
55ComputeEngine ,
66Metadata ,
7- SemiBoxedExpression ,
87DictionaryInterface ,
8+ JsonSerializationOptions ,
99} from '../global-types' ;
1010
1111import { _BoxedExpression } from './abstract-boxed-expression' ;
1212import { hashCode } from './utils' ;
1313import { isWildcard , wildcardName } from './boxed-patterns' ;
1414import { BoxedType } from '../../common/type/boxed-type' ;
15- import { Expression } from '../../math-json/types' ;
15+ import { DictionaryValue , Expression } from '../../math-json/types' ;
1616import { widen } from '../../common/type/utils' ;
1717
1818/**
@@ -28,29 +28,29 @@ export class BoxedDictionary
2828private readonly _keyValues :Record < string , BoxedExpression > = { } ;
2929private _type :BoxedType | undefined ;
3030
31+ /** The input to the constructor is either a ["Dictionary", ["KeyValuePair", ..., ...], ...] expression or a record of key-value pairs */
3132constructor (
3233ce :ComputeEngine ,
33- keyValues :Record < string , SemiBoxedExpression > | BoxedExpression ,
34+ keyValues :Record < string , DictionaryValue > | BoxedExpression ,
3435options ?:{
3536metadata ?:Metadata ;
3637canonical ?:boolean ;
3738}
3839) {
3940super ( ce , options ?. metadata ) ;
4041
41- // Handle different input types for canonical form support
4242if ( keyValues instanceof _BoxedExpression ) {
4343this . _initFromExpression ( keyValues , options ) ;
4444} else {
4545this . _initFromRecord (
46- keyValues as Record < string , SemiBoxedExpression > ,
46+ keyValues as Record < string , DictionaryValue > ,
4747options
4848) ;
4949}
5050}
5151
5252private _initFromRecord (
53- keyValues :Record < string , SemiBoxedExpression > ,
53+ keyValues :Record < string , DictionaryValue > ,
5454options ?:{ canonical ?:boolean }
5555) {
5656for ( const key in keyValues ) {
@@ -61,11 +61,11 @@ export class BoxedDictionary
6161}
6262if ( key . length === 0 )
6363throw new Error ( 'Dictionary keys must not be empty strings' ) ;
64- if ( keyValues [ key ] instanceof _BoxedExpression ) {
65- this . _keyValues [ key ] = keyValues [ key ] ;
66- } else {
67- this . _keyValues [ key ] = this . engine . box ( keyValues [ key ] , options ) ;
68- }
64+ this . _keyValues [ key ] = dictionaryValueToBoxedExpression (
65+ this . engine ,
66+ keyValues [ key ] ,
67+ options
68+ ) ;
6969}
7070}
7171
@@ -124,14 +124,28 @@ export class BoxedDictionary
124124}
125125
126126get json ( ) :Expression {
127- return [
128- 'Dictionary' ,
129- {
130- dict :Object . fromEntries (
131- Object . entries ( this . _keyValues ) . map ( ( [ k , v ] ) => [ k , v . json ] )
132- ) ,
133- } ,
134- ] ;
127+ return {
128+ dict :Object . fromEntries (
129+ Object . entries ( this . _keyValues ) . map ( ( [ k , v ] ) => [
130+ k ,
131+ boxedExpressionToDictionaryValue ( v ) ,
132+ ] )
133+ ) ,
134+ } ;
135+ }
136+
137+ toMathJson ( options :Readonly < JsonSerializationOptions > ) :Expression {
138+ if ( options . shorthands . includes ( 'dictionary' ) ) {
139+ const result = this . json ;
140+ return result ;
141+ }
142+ if ( this . isEmptyCollection ) return { dict :{ } } ;
143+
144+ const result :Record < string , Expression > = { } ;
145+ for ( const [ key , value ] of this . entries )
146+ result [ key ] = value . toMathJson ( options ) ;
147+
148+ return { dict :result } ;
135149}
136150
137151get hash ( ) :number {
@@ -252,3 +266,47 @@ export class BoxedDictionary
252266return result ;
253267}
254268}
269+
270+ function boxedExpressionToDictionaryValue (
271+ value :BoxedExpression
272+ ) :DictionaryValue {
273+ if ( value . string ) return value . string ;
274+ if ( value . symbol === 'True' ) return true ;
275+ if ( value . symbol === 'False' ) return false ;
276+ if ( value . symbol ) return { sym :value . symbol } ;
277+
278+ if ( value . numericValue !== null && value . type . matches ( 'real' ) )
279+ return value . re ;
280+
281+ if ( value . operator === 'List' )
282+ return value . ops ! . map ( boxedExpressionToDictionaryValue ) ;
283+
284+ return value . toMathJson ( { shorthands :[ ] } ) ;
285+ }
286+
287+ function dictionaryValueToBoxedExpression (
288+ ce :ComputeEngine ,
289+ value :DictionaryValue | null | undefined ,
290+ options ?:{ canonical ?:boolean }
291+ ) :BoxedExpression {
292+ if ( value === null || value === undefined ) return ce . Nothing ;
293+ if ( value instanceof _BoxedExpression ) return value ;
294+ if ( typeof value === 'string' ) return ce . string ( value ) ;
295+ if ( typeof value === 'number' ) return ce . number ( value , options ) ;
296+ if ( typeof value === 'boolean' ) return value ?ce . True :ce . False ;
297+
298+ if ( Array . isArray ( value ) ) {
299+ return ce . function (
300+ 'List' ,
301+ value . map ( ( x ) => dictionaryValueToBoxedExpression ( ce , x , options ) )
302+ ) ;
303+ }
304+ if ( typeof value === 'object' ) {
305+ if ( 'num' in value ) return ce . number ( value . num , options ) ;
306+ if ( 'str' in value ) return ce . string ( value . str ) ;
307+ if ( 'sym' in value ) return ce . symbol ( value . sym , options ) ;
308+ if ( 'fn' in value ) return ce . box ( value , options ) ;
309+ if ( 'dict' in value ) return new BoxedDictionary ( ce , value . dict , options ) ;
310+ }
311+ return ce . Nothing ;
312+ }