Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf7855ca

Browse files
committed
feat: improved dictionary handling
1 parentac70dee commitf7855ca

File tree

12 files changed

+1545
-1151
lines changed

12 files changed

+1545
-1151
lines changed

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- The`[Length]` function has been renamed to`[Count]`.
66
- The`xsize` property of collections has been renamed to`count`.
77
- The`xcontains()` method of collections has been renamed to`contains()`.
8+
- Handling of dictionaries (`["Dictionary"] expressions and`{dict:...}`
9+
shorthand) has been improved. shorthand) has been improved.
810

911
##0.30.2_2025-07-15_
1012

‎src/api.md‎

Lines changed: 965 additions & 1026 deletions
Large diffs are not rendered by default.

‎src/compute-engine/boxed-expression/abstract-boxed-expression.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export abstract class _BoxedExpression implements BoxedExpression {
259259
):Expression{
260260
constdefaultOptions:JsonSerializationOptions={
261261
exclude:[],
262-
shorthands:['function','symbol','string','number'],
262+
shorthands:['function','symbol','string','number','dictionary'],
263263
metadata:[],
264264
fractionalDigits:'max',
265265
repeatingDecimal:true,
@@ -272,7 +272,13 @@ export abstract class _BoxedExpression implements BoxedExpression {
272272
options.shorthands==='all')||
273273
options.shorthands?.includes('all')
274274
){
275-
defaultOptions.shorthands=['function','symbol','string','number'];
275+
defaultOptions.shorthands=[
276+
'function',
277+
'symbol',
278+
'string',
279+
'number',
280+
'dictionary',
281+
];
276282
}
277283
// if (options.shorthands?.includes('none')) defaultOptions.shorthands = [];
278284
if(Array.isArray(options.shorthands))

‎src/compute-engine/boxed-expression/box.ts‎

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@ import type {
1111

1212
import{Expression,MathJsonSymbol}from'../../math-json/types';
1313
import{
14-
dictionaryFromExpression,
1514
machineValue,
1615
matchesNumber,
1716
matchesString,
1817
matchesSymbol,
1918
missingIfEmpty,
20-
operands,
21-
operator,
2219
stringValue,
2320
symbol,
2421
}from'../../math-json/utils';
@@ -411,11 +408,8 @@ export function box(
411408
if('str'inexpr)returnnewBoxedString(ce,expr.str);
412409
if('sym'inexpr)returnce.symbol(expr.sym,{ canonical});
413410
if('num'inexpr)returnce.number(expr,{ canonical});
414-
if('dict'inexpr){
415-
returnnewBoxedDictionary(ce,dictionaryFromExpression(expr)!.dict,{
416-
canonical,
417-
});
418-
}
411+
if('dict'inexpr)
412+
returnnewBoxedDictionary(ce,expr.dict,{ canonical});
419413

420414
thrownewError(
421415
`Unexpected MathJSON object:${JSON.stringify(expr,undefined,4)}`
@@ -463,39 +457,8 @@ function makeCanonicalFunction(
463457
}
464458

465459
if(name==='Dictionary'){
466-
constentries:[string,BoxedExpression][]=ops.map((x)=>{
467-
if(xinstanceof_BoxedExpression){
468-
constoperator=x.operator;
469-
if(
470-
operator==='KeyValuePair'||
471-
operator==='Tuple'||
472-
operator==='Pair'
473-
){
474-
const[key,value]=x.ops!;
475-
return[
476-
ce.box(key).string!,
477-
dictionaryValueToBoxedExpression(ce,value)!,
478-
];
479-
}else
480-
thrownewError(
481-
`Unexpected operator:${operator}. Expected KeyValuePair, Tuple or Pair`
482-
);
483-
}else{
484-
consth=operator(xasExpression);
485-
if(h==='KeyValuePair'||h==='Tuple'||h==='Pair'){
486-
const[k,v]=operands(xasExpression);
487-
constkey=stringValue(k);
488-
if(!key)
489-
thrownewError(
490-
`Invalid key:${JSON.stringify(k)}. Expected a string.`
491-
);
492-
return[key,ce.box(v??'Nothing')];
493-
}
494-
}
495-
return['undefined',ce.Nothing];
496-
});
497-
498-
returnnewBoxedDictionary(ce,Object.fromEntries(entries),{
460+
constboxedOps=ops.map((x)=>ce.box(x,{canonical:false}));
461+
returnnewBoxedDictionary(ce,ce._fn('Dictionary',boxedOps),{
499462
canonical:true,
500463
});
501464
}
@@ -814,30 +777,3 @@ export function semiCanonical(
814777

815778
returnxs.map((x)=>ce.box(x,{ scope}));
816779
}
817-
818-
functiondictionaryValueToBoxedExpression(
819-
ce:ComputeEngine,
820-
value:SemiBoxedExpression|null|undefined
821-
):BoxedExpression{
822-
if(value===null||value===undefined)returnce.Nothing;
823-
if(valueinstanceof_BoxedExpression)returnvalue;
824-
if(typeofvalue==='string')returnce.string(value);
825-
if(typeofvalue==='number')returnce.number(value);
826-
if(typeofvalue==='boolean')returnvalue ?ce.True :ce.False;
827-
828-
if(Array.isArray(value)){
829-
returnce.function(
830-
'List',
831-
value.map((x)=>dictionaryValueToBoxedExpression(ce,x))
832-
);
833-
}
834-
if(typeofvalue==='object'){
835-
if('num'invalue)returnce.number(value.num);
836-
if('str'invalue)returnce.string(value.str);
837-
if('sym'invalue)returnce.symbol(value.sym);
838-
if('fn'invalue)returnce.box(value);
839-
if('dict'invalue)
840-
returnnewBoxedDictionary(ce,dictionaryFromExpression(value)!.dict);
841-
}
842-
returnce.Nothing;
843-
}

‎src/compute-engine/boxed-expression/boxed-dictionary.ts‎

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import type {
44
BoxedSubstitution,
55
ComputeEngine,
66
Metadata,
7-
SemiBoxedExpression,
87
DictionaryInterface,
8+
JsonSerializationOptions,
99
}from'../global-types';
1010

1111
import{_BoxedExpression}from'./abstract-boxed-expression';
1212
import{hashCode}from'./utils';
1313
import{isWildcard,wildcardName}from'./boxed-patterns';
1414
import{BoxedType}from'../../common/type/boxed-type';
15-
import{Expression}from'../../math-json/types';
15+
import{DictionaryValue,Expression}from'../../math-json/types';
1616
import{widen}from'../../common/type/utils';
1717

1818
/**
@@ -28,29 +28,29 @@ export class BoxedDictionary
2828
privatereadonly_keyValues:Record<string,BoxedExpression>={};
2929
private_type:BoxedType|undefined;
3030

31+
/** The input to the constructor is either a ["Dictionary", ["KeyValuePair", ..., ...], ...] expression or a record of key-value pairs */
3132
constructor(
3233
ce:ComputeEngine,
33-
keyValues:Record<string,SemiBoxedExpression>|BoxedExpression,
34+
keyValues:Record<string,DictionaryValue>|BoxedExpression,
3435
options?:{
3536
metadata?:Metadata;
3637
canonical?:boolean;
3738
}
3839
){
3940
super(ce,options?.metadata);
4041

41-
// Handle different input types for canonical form support
4242
if(keyValuesinstanceof_BoxedExpression){
4343
this._initFromExpression(keyValues,options);
4444
}else{
4545
this._initFromRecord(
46-
keyValuesasRecord<string,SemiBoxedExpression>,
46+
keyValuesasRecord<string,DictionaryValue>,
4747
options
4848
);
4949
}
5050
}
5151

5252
private_initFromRecord(
53-
keyValues:Record<string,SemiBoxedExpression>,
53+
keyValues:Record<string,DictionaryValue>,
5454
options?:{canonical?:boolean}
5555
){
5656
for(constkeyinkeyValues){
@@ -61,11 +61,11 @@ export class BoxedDictionary
6161
}
6262
if(key.length===0)
6363
thrownewError('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

126126
getjson():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+
constresult=this.json;
140+
returnresult;
141+
}
142+
if(this.isEmptyCollection)return{dict:{}};
143+
144+
constresult:Record<string,Expression>={};
145+
for(const[key,value]ofthis.entries)
146+
result[key]=value.toMathJson(options);
147+
148+
return{dict:result};
135149
}
136150

137151
gethash():number{
@@ -252,3 +266,47 @@ export class BoxedDictionary
252266
returnresult;
253267
}
254268
}
269+
270+
functionboxedExpressionToDictionaryValue(
271+
value:BoxedExpression
272+
):DictionaryValue{
273+
if(value.string)returnvalue.string;
274+
if(value.symbol==='True')returntrue;
275+
if(value.symbol==='False')returnfalse;
276+
if(value.symbol)return{sym:value.symbol};
277+
278+
if(value.numericValue!==null&&value.type.matches('real'))
279+
returnvalue.re;
280+
281+
if(value.operator==='List')
282+
returnvalue.ops!.map(boxedExpressionToDictionaryValue);
283+
284+
returnvalue.toMathJson({shorthands:[]});
285+
}
286+
287+
functiondictionaryValueToBoxedExpression(
288+
ce:ComputeEngine,
289+
value:DictionaryValue|null|undefined,
290+
options?:{canonical?:boolean}
291+
):BoxedExpression{
292+
if(value===null||value===undefined)returnce.Nothing;
293+
if(valueinstanceof_BoxedExpression)returnvalue;
294+
if(typeofvalue==='string')returnce.string(value);
295+
if(typeofvalue==='number')returnce.number(value,options);
296+
if(typeofvalue==='boolean')returnvalue ?ce.True :ce.False;
297+
298+
if(Array.isArray(value)){
299+
returnce.function(
300+
'List',
301+
value.map((x)=>dictionaryValueToBoxedExpression(ce,x,options))
302+
);
303+
}
304+
if(typeofvalue==='object'){
305+
if('num'invalue)returnce.number(value.num,options);
306+
if('str'invalue)returnce.string(value.str);
307+
if('sym'invalue)returnce.symbol(value.sym,options);
308+
if('fn'invalue)returnce.box(value,options);
309+
if('dict'invalue)returnnewBoxedDictionary(ce,value.dict,options);
310+
}
311+
returnce.Nothing;
312+
}

‎src/compute-engine/boxed-expression/serialize.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,9 @@ export function serializeJson(
845845
// Is it a tensor?
846846
if(expr.rank>0)returnexpr.json;
847847

848+
// Is it a dictionary?
849+
if(expr.type.matches('dictionary'))returnexpr.toMathJson(options);
850+
848851
// Is it a string?
849852
if(expr.string!==null)returnserializeJsonString(expr.string,options);
850853

‎src/compute-engine/global-types.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1876,7 +1876,14 @@ export type JsonSerializationOptions = {
18761876
*
18771877
* **Default**: `["all"]`
18781878
*/
1879-
shorthands:('all'|'number'|'symbol'|'function'|'string')[];
1879+
shorthands:(
1880+
|'all'
1881+
|'number'
1882+
|'symbol'
1883+
|'function'
1884+
|'string'
1885+
|'dictionary'
1886+
)[];
18801887

18811888
/** A list of space separated keywords indicating which metadata should be
18821889
* included in the MathJSON. If metadata is included, shorthand notation

‎src/math-json/types.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,17 @@ export type MathJsonFunctionObject = {
116116
fn:[MathJsonSymbol, ...Expression[]];
117117
}&MathJsonAttributes;
118118

119+
/**@category MathJSON */
120+
exporttypeDictionaryValue=
121+
|boolean
122+
|number
123+
|string
124+
|ExpressionObject
125+
|ReadonlyArray<DictionaryValue>;
126+
119127
/**@category MathJSON */
120128
exporttypeMathJsonDictionaryObject={
121-
dict:Record<string,Expression>;
129+
dict:Record<string,DictionaryValue>;
122130
}&MathJsonAttributes;
123131

124132
/**

‎src/math-json/utils.ts‎

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
MathJsonStringObject,
77
MathJsonSymbol,
88
MathJsonDictionaryObject,
9+
DictionaryValue,
910
}from'./types';
1011

1112
exportconstMISSING:Expression=['Error',"'missing'"];
@@ -200,7 +201,7 @@ export function dictionaryFromExpression(
200201
for(leti=1;i<nops(expr);i++){
201202
constkv=keyValuePair(ops[i]);
202203
if(kv){
203-
dict[kv[0]]=expressionToJsValue(kv[1])??'Nothing';
204+
dict[kv[0]]=expressionToDictionaryValue(kv[1])??'Nothing';
204205
}
205206
}
206207

@@ -505,25 +506,17 @@ function jsValueToExpression(v: any): Expression | null {
505506
returnnull;
506507
}
507508

508-
functionexpressionToJsValue(expr:Expression|null|undefined):any{
509+
functionexpressionToDictionaryValue(
510+
expr:Expression|null|undefined
511+
):DictionaryValue|null{
509512
if(expr===null||expr===undefined)returnnull;
510513
if(isStringObject(expr))returnexpr.str;
511514
if(isNumberObject(expr))returnparseFloat(expr.num);
512515
if(isSymbolObject(expr))returnexpr.sym;
513516

514517
if(typeofexpr==='string'||typeofexpr==='number')returnexpr;
515518

516-
if(Array.isArray(expr)){
517-
returnexpr.map((x)=>expressionToJsValue(x));
518-
}
519+
if(Array.isArray(expr))return{fn:expr}asMathJsonFunctionObject;
519520

520-
if(isDictionaryObject(expr)){
521-
constresult:Record<string,any>={};
522-
for(constkeyinexpr.dict){
523-
result[key]=expressionToJsValue(expr.dict[key]);
524-
}
525-
returnresult;
526-
}
527-
528-
returnnull;
521+
returnexpr;
529522
}

‎test/compute-engine.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ <h2><code class="label">solve</code></h2>
734734

735735
ce.jsonSerializationOptions={
736736
exclude:[],
737-
shorthands:["function","symbol","string","dictionary","number"],
737+
shorthands:["function","symbol","string","dictionary","number","dictionary"],
738738
metadata:[],
739739
// precision: 'max',
740740
repeatingDecimals:false,

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp