-
-
Notifications
You must be signed in to change notification settings - Fork 59
/
tree2.ts
294 lines (212 loc) · 6.55 KB
/
tree2.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
namespace $ {
/** Path by types in tree. */
export type $mol_tree2_path =
Array< string | number | null >
/** Hask tool for processing node. */
export type $mol_tree2_hack< Context > =
(
input : $mol_tree2 ,
belt : $mol_tree2_belt< Context > ,
context : Context ,
)=> readonly $mol_tree2[]
/** Collection of hask tools for processing tree. */
export type $mol_tree2_belt< Context > =
Record< string , $mol_tree2_hack< Context > >
/**
* Abstract Syntax Tree with human readable serialization.
* Avoid direct instantiation. Use static factories instead.
* @see https://github.com/nin-jin/tree.d
*/
export class $mol_tree2 extends Object {
constructor(
/** Type of structural node, `value` should be empty */
readonly type : string,
/** Content of data node, `type` should be empty */
readonly value : string,
/** Child nodes */
readonly kids : readonly $mol_tree2[],
/** Position in most far source resource */
readonly span : $mol_span,
) {
super()
;(this as any)[ Symbol.toStringTag ] = type || '\\' + value
}
/** Makes collection node. */
static list(
kids : readonly $mol_tree2[] ,
span = $mol_span.unknown ,
) {
return new $mol_tree2( '' , '' , kids , span )
}
/** Makes new derived collection node. */
list(
kids : readonly $mol_tree2[] ,
) {
return $mol_tree2.list( kids , this.span )
}
/** Makes data node for any string. */
static data(
value : string ,
kids = [] as readonly $mol_tree2[] ,
span = $mol_span.unknown ,
) {
const chunks = value.split( '\n' )
if( chunks.length > 1 ) {
let kid_span = span.span( span.row , span.col , 0 )
const data = chunks.map( chunk => {
kid_span = kid_span.after( chunk.length )
return new $mol_tree2( '' , chunk , [] , kid_span )
} )
kids = [ ... data , ... kids ]
value = ''
}
return new $mol_tree2( '' , value , kids , span )
}
/** Makes new derived data node. */
data(
value : string ,
kids = [] as readonly $mol_tree2[] ,
) {
return $mol_tree2.data( value , kids , this.span )
}
/** Makes struct node. */
static struct(
type : string ,
kids = [] as readonly $mol_tree2[] ,
span = $mol_span.unknown ,
) {
if( /[ \n\t\\]/.test( type ) ) {
$$.$mol_fail( span.error( `Wrong type ${ JSON.stringify( type ) }` ) )
}
return new $mol_tree2( type , '' , kids , span )
}
/** Makes new derived structural node. */
struct(
type : string ,
kids = [] as readonly $mol_tree2[] ,
) {
return $mol_tree2.struct( type , kids , this.span )
}
/** Makes new derived node with different kids id defined. */
clone( kids : readonly $mol_tree2[], span = this.span ) {
return new $mol_tree2( this.type , this.value , kids , span )
}
/** Returns multiline text content. */
text() {
var values : string[] = []
for( var kid of this.kids ) {
if( kid.type ) continue
values.push( kid.value )
}
return this.value + values.join( '\n' )
}
/** Parses tree format. */
/** @deprecated Use $mol_tree2_from_string */
static fromString( str : string , uri = 'unknown' ) {
return $$.$mol_tree2_from_string( str, uri )
}
/** Serializes to tree format. */
toString() : string {
return $$.$mol_tree2_to_string( this )
}
/** Makes new tree with node overrided by path. */
insert( value : $mol_tree2 | null , ...path : $mol_tree2_path ) : $mol_tree2 {
if( path.length === 0 ) return value!
const type = path[0]
if( typeof type === 'string' ) {
let replaced = false
const sub = this.kids.map( ( item , index )=> {
if( item.type !== type ) return item
replaced = true
return item.insert( value , ... path.slice( 1 ) )
} ).filter( Boolean )
if( !replaced && value ) {
sub.push( this.struct( type , [] ).insert( value , ... path.slice( 1 ) ) )
}
return this.clone( sub )
} else if( typeof type === 'number' ) {
const sub = this.kids.slice()
sub[ type ] = ( sub[ type ] || this.list([]) )
.insert( value , ... path.slice( 1 ) )
return this.clone( sub.filter( Boolean ) )
} else {
const kids = ( ( this.kids.length === 0 ) ? [ this.list([]) ] : this.kids )
.map( item => item.insert( value , ... path.slice( 1 ) ) )
.filter( Boolean )
return this.clone( kids )
}
}
/** Query nodes by path. */
select( ...path : $mol_tree2_path ) {
let next = [ this as $mol_tree2 ]
for( const type of path ) {
if( !next.length ) break
const prev = next
next = []
for( var item of prev ) {
switch( typeof( type ) ) {
case 'string' :
for( var child of item.kids ) {
if( child.type == type ) {
next.push( child )
}
}
break
case 'number' :
if( type < item.kids.length ) next.push( item.kids[ type ] )
break;
default : next.push( ... item.kids )
}
}
}
return this.list( next )
}
/** Filter kids by path or value. */
filter( path : string[] , value? : string ) {
const sub = this.kids.filter( item => {
var found = item.select( ...path )
if( value === undefined ) {
return Boolean( found.kids.length )
} else {
return found.kids.some( child => child.value == value )
}
} )
return this.clone( sub )
}
hack_self< Context extends { span?: $mol_span; [ key: string ]: unknown } = {} >(
belt: $mol_tree2_belt< Context >,
context = {} as Context,
) {
let handle = belt[ this.type ] || belt[ '' ]
if( !handle || handle === Object.prototype[ this.type as keyof Object ] ) {
handle = ( input, belt, context )=> [
input.clone( input.hack( belt, context ), context.span )
]
}
try {
return handle( this , belt , context! )
} catch( error: any ) {
error.message += `\n${ this.clone([]) }${ this.span }`
$mol_fail_hidden( error )
}
}
/** Transform tree through context with transformers */
hack< Context extends { span?: $mol_span; [ key: string ]: unknown } = {} >(
belt: $mol_tree2_belt< Context >,
context = {} as Context,
) {
return ( [] as readonly $mol_tree2[] ).concat(
... this.kids.map( child => child.hack_self(belt, context) )
)
}
/** Makes Error with node coordinates. */
error( message : string , Class = Error ) {
return this.span.error( `${ message }\n${ this.clone([]) }` , Class )
}
}
export class $mol_tree2_empty extends $mol_tree2 {
constructor() {
super( '' , '' , [] , $mol_span.unknown )
}
}
}