smiles-js 2.0.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +162 -0
- package/README.md +39 -0
- package/docs/MIRROR_PLAN.md +204 -0
- package/docs/smiles.peggy +215 -0
- package/package.json +1 -1
- package/scripts/coverage-summary.js +1 -1
- package/src/codegen/branch-crossing-ring.js +27 -6
- package/src/codegen/interleaved-fused-ring.js +24 -0
- package/src/decompiler.js +236 -51
- package/src/decompiler.test.js +232 -60
- package/src/fragment.test.js +7 -2
- package/src/manipulation.js +409 -4
- package/src/manipulation.test.js +359 -1
- package/src/method-attachers.js +37 -8
- package/src/node-creators.js +7 -0
- package/src/parser/ast-builder.js +23 -8
- package/src/parser/ring-group-builder.js +14 -2
- package/src/parser/ring-utils.js +28 -0
- package/test-integration/__snapshots__/acetaminophen.test.js.snap +20 -0
- package/test-integration/__snapshots__/adjuvant-analgesics.test.js.snap +63 -1
- package/test-integration/__snapshots__/cholesterol-drugs.test.js.snap +437 -0
- package/test-integration/__snapshots__/dexamethasone.test.js.snap +31 -0
- package/test-integration/__snapshots__/endocannabinoids.test.js.snap +79 -2
- package/test-integration/__snapshots__/endogenous-opioids.test.js.snap +1116 -0
- package/test-integration/__snapshots__/hypertension-medication.test.js.snap +70 -1
- package/test-integration/__snapshots__/local-anesthetics.test.js.snap +97 -0
- package/test-integration/__snapshots__/nsaids-otc.test.js.snap +61 -1
- package/test-integration/__snapshots__/nsaids-prescription.test.js.snap +115 -2
- package/test-integration/__snapshots__/opioids.test.js.snap +113 -4
- package/test-integration/__snapshots__/steroids.test.js.snap +381 -2
- package/test-integration/acetaminophen.test.js +15 -3
- package/test-integration/adjuvant-analgesics.test.js +43 -7
- package/test-integration/cholesterol-drugs.test.js +127 -20
- package/test-integration/cholesterol.test.js +112 -0
- package/test-integration/dexamethasone.test.js +8 -2
- package/test-integration/endocannabinoids.test.js +48 -12
- package/test-integration/endogenous-opioids.smiles.js +32 -0
- package/test-integration/endogenous-opioids.test.js +192 -0
- package/test-integration/hypertension-medication.test.js +32 -8
- package/test-integration/local-anesthetics.smiles.js +33 -0
- package/test-integration/local-anesthetics.test.js +64 -16
- package/test-integration/mirror.test.js +151 -0
- package/test-integration/nsaids-otc.test.js +40 -10
- package/test-integration/nsaids-prescription.test.js +72 -18
- package/test-integration/opioids.test.js +56 -14
- package/test-integration/polymer.test.js +148 -0
- package/test-integration/steroids.test.js +112 -28
- package/test-integration/utils.js +4 -2
- package/todo +2 -3
|
@@ -26,6 +26,12 @@ describe('Celecoxib Integration Test', () => {
|
|
|
26
26
|
expect(code).toMatchSnapshot();
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
test('generates valid verbose code via toCode()', () => {
|
|
30
|
+
const ast = parse(CELECOXIB_SMILES);
|
|
31
|
+
const code = ast.toCode('v', { verbose: true });
|
|
32
|
+
expect(code).toMatchSnapshot();
|
|
33
|
+
});
|
|
34
|
+
|
|
29
35
|
test('generated code is valid JavaScript', () => {
|
|
30
36
|
const ast = parse(CELECOXIB_SMILES);
|
|
31
37
|
const code = ast.toCode('v');
|
|
@@ -33,14 +39,14 @@ describe('Celecoxib Integration Test', () => {
|
|
|
33
39
|
|
|
34
40
|
let factory;
|
|
35
41
|
expect(() => {
|
|
36
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
42
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
37
43
|
}).not.toThrow();
|
|
38
44
|
expect(typeof factory).toBe('function');
|
|
39
45
|
});
|
|
40
46
|
|
|
41
47
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
42
48
|
const ast = parse(CELECOXIB_SMILES);
|
|
43
|
-
const code = ast.toCode('v');
|
|
49
|
+
const code = ast.toCode('v', { verbose: true });
|
|
44
50
|
const executableCode = stripExports(code);
|
|
45
51
|
|
|
46
52
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -66,6 +72,12 @@ describe('Meloxicam Integration Test', () => {
|
|
|
66
72
|
expect(code).toMatchSnapshot();
|
|
67
73
|
});
|
|
68
74
|
|
|
75
|
+
test('generates valid verbose code via toCode()', () => {
|
|
76
|
+
const ast = parse(MELOXICAM_SMILES);
|
|
77
|
+
const code = ast.toCode('v', { verbose: true });
|
|
78
|
+
expect(code).toMatchSnapshot();
|
|
79
|
+
});
|
|
80
|
+
|
|
69
81
|
test('generated code is valid JavaScript', () => {
|
|
70
82
|
const ast = parse(MELOXICAM_SMILES);
|
|
71
83
|
const code = ast.toCode('v');
|
|
@@ -73,14 +85,14 @@ describe('Meloxicam Integration Test', () => {
|
|
|
73
85
|
|
|
74
86
|
let factory;
|
|
75
87
|
expect(() => {
|
|
76
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
88
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
77
89
|
}).not.toThrow();
|
|
78
90
|
expect(typeof factory).toBe('function');
|
|
79
91
|
});
|
|
80
92
|
|
|
81
93
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
82
94
|
const ast = parse(MELOXICAM_SMILES);
|
|
83
|
-
const code = ast.toCode('v');
|
|
95
|
+
const code = ast.toCode('v', { verbose: true });
|
|
84
96
|
const executableCode = stripExports(code);
|
|
85
97
|
|
|
86
98
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -106,6 +118,12 @@ describe('Piroxicam Integration Test', () => {
|
|
|
106
118
|
expect(code).toMatchSnapshot();
|
|
107
119
|
});
|
|
108
120
|
|
|
121
|
+
test('generates valid verbose code via toCode()', () => {
|
|
122
|
+
const ast = parse(PIROXICAM_SMILES);
|
|
123
|
+
const code = ast.toCode('v', { verbose: true });
|
|
124
|
+
expect(code).toMatchSnapshot();
|
|
125
|
+
});
|
|
126
|
+
|
|
109
127
|
test('generated code is valid JavaScript', () => {
|
|
110
128
|
const ast = parse(PIROXICAM_SMILES);
|
|
111
129
|
const code = ast.toCode('v');
|
|
@@ -113,14 +131,14 @@ describe('Piroxicam Integration Test', () => {
|
|
|
113
131
|
|
|
114
132
|
let factory;
|
|
115
133
|
expect(() => {
|
|
116
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
134
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
117
135
|
}).not.toThrow();
|
|
118
136
|
expect(typeof factory).toBe('function');
|
|
119
137
|
});
|
|
120
138
|
|
|
121
139
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
122
140
|
const ast = parse(PIROXICAM_SMILES);
|
|
123
|
-
const code = ast.toCode('v');
|
|
141
|
+
const code = ast.toCode('v', { verbose: true });
|
|
124
142
|
const executableCode = stripExports(code);
|
|
125
143
|
|
|
126
144
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -146,6 +164,12 @@ describe('Etodolac Integration Test', () => {
|
|
|
146
164
|
expect(code).toMatchSnapshot();
|
|
147
165
|
});
|
|
148
166
|
|
|
167
|
+
test('generates valid verbose code via toCode()', () => {
|
|
168
|
+
const ast = parse(ETODOLAC_SMILES);
|
|
169
|
+
const code = ast.toCode('v', { verbose: true });
|
|
170
|
+
expect(code).toMatchSnapshot();
|
|
171
|
+
});
|
|
172
|
+
|
|
149
173
|
test('generated code is valid JavaScript', () => {
|
|
150
174
|
const ast = parse(ETODOLAC_SMILES);
|
|
151
175
|
const code = ast.toCode('v');
|
|
@@ -153,14 +177,14 @@ describe('Etodolac Integration Test', () => {
|
|
|
153
177
|
|
|
154
178
|
let factory;
|
|
155
179
|
expect(() => {
|
|
156
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
180
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
157
181
|
}).not.toThrow();
|
|
158
182
|
expect(typeof factory).toBe('function');
|
|
159
183
|
});
|
|
160
184
|
|
|
161
185
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
162
186
|
const ast = parse(ETODOLAC_SMILES);
|
|
163
|
-
const code = ast.toCode('v');
|
|
187
|
+
const code = ast.toCode('v', { verbose: true });
|
|
164
188
|
const executableCode = stripExports(code);
|
|
165
189
|
|
|
166
190
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -186,6 +210,12 @@ describe('Ketorolac Integration Test', () => {
|
|
|
186
210
|
expect(code).toMatchSnapshot();
|
|
187
211
|
});
|
|
188
212
|
|
|
213
|
+
test('generates valid verbose code via toCode()', () => {
|
|
214
|
+
const ast = parse(KETOROLAC_SMILES);
|
|
215
|
+
const code = ast.toCode('v', { verbose: true });
|
|
216
|
+
expect(code).toMatchSnapshot();
|
|
217
|
+
});
|
|
218
|
+
|
|
189
219
|
test('generated code is valid JavaScript', () => {
|
|
190
220
|
const ast = parse(KETOROLAC_SMILES);
|
|
191
221
|
const code = ast.toCode('v');
|
|
@@ -193,14 +223,14 @@ describe('Ketorolac Integration Test', () => {
|
|
|
193
223
|
|
|
194
224
|
let factory;
|
|
195
225
|
expect(() => {
|
|
196
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
226
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
197
227
|
}).not.toThrow();
|
|
198
228
|
expect(typeof factory).toBe('function');
|
|
199
229
|
});
|
|
200
230
|
|
|
201
231
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
202
232
|
const ast = parse(KETOROLAC_SMILES);
|
|
203
|
-
const code = ast.toCode('v');
|
|
233
|
+
const code = ast.toCode('v', { verbose: true });
|
|
204
234
|
const executableCode = stripExports(code);
|
|
205
235
|
|
|
206
236
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -227,6 +257,12 @@ describe('Rofecoxib Integration Test', () => {
|
|
|
227
257
|
expect(code).toMatchSnapshot();
|
|
228
258
|
});
|
|
229
259
|
|
|
260
|
+
test('generates valid verbose code via toCode()', () => {
|
|
261
|
+
const ast = parse(ROFECOXIB_SMILES);
|
|
262
|
+
const code = ast.toCode('v', { verbose: true });
|
|
263
|
+
expect(code).toMatchSnapshot();
|
|
264
|
+
});
|
|
265
|
+
|
|
230
266
|
test('generated code is valid JavaScript', () => {
|
|
231
267
|
const ast = parse(ROFECOXIB_SMILES);
|
|
232
268
|
const code = ast.toCode('v');
|
|
@@ -234,14 +270,14 @@ describe('Rofecoxib Integration Test', () => {
|
|
|
234
270
|
|
|
235
271
|
let factory;
|
|
236
272
|
expect(() => {
|
|
237
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
273
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
238
274
|
}).not.toThrow();
|
|
239
275
|
expect(typeof factory).toBe('function');
|
|
240
276
|
});
|
|
241
277
|
|
|
242
278
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
243
279
|
const ast = parse(ROFECOXIB_SMILES);
|
|
244
|
-
const code = ast.toCode('v');
|
|
280
|
+
const code = ast.toCode('v', { verbose: true });
|
|
245
281
|
const executableCode = stripExports(code);
|
|
246
282
|
|
|
247
283
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -267,6 +303,12 @@ describe('Etoricoxib Integration Test', () => {
|
|
|
267
303
|
expect(code).toMatchSnapshot();
|
|
268
304
|
});
|
|
269
305
|
|
|
306
|
+
test('generates valid verbose code via toCode()', () => {
|
|
307
|
+
const ast = parse(ETORICOXIB_SMILES);
|
|
308
|
+
const code = ast.toCode('v', { verbose: true });
|
|
309
|
+
expect(code).toMatchSnapshot();
|
|
310
|
+
});
|
|
311
|
+
|
|
270
312
|
test('generated code is valid JavaScript', () => {
|
|
271
313
|
const ast = parse(ETORICOXIB_SMILES);
|
|
272
314
|
const code = ast.toCode('v');
|
|
@@ -274,14 +316,14 @@ describe('Etoricoxib Integration Test', () => {
|
|
|
274
316
|
|
|
275
317
|
let factory;
|
|
276
318
|
expect(() => {
|
|
277
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
319
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
278
320
|
}).not.toThrow();
|
|
279
321
|
expect(typeof factory).toBe('function');
|
|
280
322
|
});
|
|
281
323
|
|
|
282
324
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
283
325
|
const ast = parse(ETORICOXIB_SMILES);
|
|
284
|
-
const code = ast.toCode('v');
|
|
326
|
+
const code = ast.toCode('v', { verbose: true });
|
|
285
327
|
const executableCode = stripExports(code);
|
|
286
328
|
|
|
287
329
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -307,6 +349,12 @@ describe('Nabumetone Integration Test', () => {
|
|
|
307
349
|
expect(code).toMatchSnapshot();
|
|
308
350
|
});
|
|
309
351
|
|
|
352
|
+
test('generates valid verbose code via toCode()', () => {
|
|
353
|
+
const ast = parse(NABUMETONE_SMILES);
|
|
354
|
+
const code = ast.toCode('v', { verbose: true });
|
|
355
|
+
expect(code).toMatchSnapshot();
|
|
356
|
+
});
|
|
357
|
+
|
|
310
358
|
test('generated code is valid JavaScript', () => {
|
|
311
359
|
const ast = parse(NABUMETONE_SMILES);
|
|
312
360
|
const code = ast.toCode('v');
|
|
@@ -314,14 +362,14 @@ describe('Nabumetone Integration Test', () => {
|
|
|
314
362
|
|
|
315
363
|
let factory;
|
|
316
364
|
expect(() => {
|
|
317
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
365
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
318
366
|
}).not.toThrow();
|
|
319
367
|
expect(typeof factory).toBe('function');
|
|
320
368
|
});
|
|
321
369
|
|
|
322
370
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
323
371
|
const ast = parse(NABUMETONE_SMILES);
|
|
324
|
-
const code = ast.toCode('v');
|
|
372
|
+
const code = ast.toCode('v', { verbose: true });
|
|
325
373
|
const executableCode = stripExports(code);
|
|
326
374
|
|
|
327
375
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -348,6 +396,12 @@ describe('Oxaprozin Integration Test', () => {
|
|
|
348
396
|
expect(code).toMatchSnapshot();
|
|
349
397
|
});
|
|
350
398
|
|
|
399
|
+
test('generates valid verbose code via toCode()', () => {
|
|
400
|
+
const ast = parse(OXAPROZIN_SMILES);
|
|
401
|
+
const code = ast.toCode('v', { verbose: true });
|
|
402
|
+
expect(code).toMatchSnapshot();
|
|
403
|
+
});
|
|
404
|
+
|
|
351
405
|
test('generated code is valid JavaScript', () => {
|
|
352
406
|
const ast = parse(OXAPROZIN_SMILES);
|
|
353
407
|
const code = ast.toCode('v');
|
|
@@ -355,14 +409,14 @@ describe('Oxaprozin Integration Test', () => {
|
|
|
355
409
|
|
|
356
410
|
let factory;
|
|
357
411
|
expect(() => {
|
|
358
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
412
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
359
413
|
}).not.toThrow();
|
|
360
414
|
expect(typeof factory).toBe('function');
|
|
361
415
|
});
|
|
362
416
|
|
|
363
417
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
364
418
|
const ast = parse(OXAPROZIN_SMILES);
|
|
365
|
-
const code = ast.toCode('v');
|
|
419
|
+
const code = ast.toCode('v', { verbose: true });
|
|
366
420
|
const executableCode = stripExports(code);
|
|
367
421
|
|
|
368
422
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -23,6 +23,12 @@ describe('Morphine Integration Test', () => {
|
|
|
23
23
|
expect(code).toMatchSnapshot();
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
test('generates valid verbose code via toCode()', () => {
|
|
27
|
+
const ast = parse(MORPHINE_SMILES);
|
|
28
|
+
const code = ast.toCode('v', { verbose: true });
|
|
29
|
+
expect(code).toMatchSnapshot();
|
|
30
|
+
});
|
|
31
|
+
|
|
26
32
|
test('generated code is valid JavaScript', () => {
|
|
27
33
|
const ast = parse(MORPHINE_SMILES);
|
|
28
34
|
const code = ast.toCode('v');
|
|
@@ -30,14 +36,14 @@ describe('Morphine Integration Test', () => {
|
|
|
30
36
|
|
|
31
37
|
let factory;
|
|
32
38
|
expect(() => {
|
|
33
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
39
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
34
40
|
}).not.toThrow();
|
|
35
41
|
expect(typeof factory).toBe('function');
|
|
36
42
|
});
|
|
37
43
|
|
|
38
44
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
39
45
|
const ast = parse(MORPHINE_SMILES);
|
|
40
|
-
const code = ast.toCode('v');
|
|
46
|
+
const code = ast.toCode('v', { verbose: true });
|
|
41
47
|
const executableCode = stripExports(code);
|
|
42
48
|
|
|
43
49
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -63,6 +69,12 @@ describe('Codeine Integration Test', () => {
|
|
|
63
69
|
expect(code).toMatchSnapshot();
|
|
64
70
|
});
|
|
65
71
|
|
|
72
|
+
test('generates valid verbose code via toCode()', () => {
|
|
73
|
+
const ast = parse(CODEINE_SMILES);
|
|
74
|
+
const code = ast.toCode('v', { verbose: true });
|
|
75
|
+
expect(code).toMatchSnapshot();
|
|
76
|
+
});
|
|
77
|
+
|
|
66
78
|
test('generated code is valid JavaScript', () => {
|
|
67
79
|
const ast = parse(CODEINE_SMILES);
|
|
68
80
|
const code = ast.toCode('v');
|
|
@@ -70,14 +82,14 @@ describe('Codeine Integration Test', () => {
|
|
|
70
82
|
|
|
71
83
|
let factory;
|
|
72
84
|
expect(() => {
|
|
73
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
85
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
74
86
|
}).not.toThrow();
|
|
75
87
|
expect(typeof factory).toBe('function');
|
|
76
88
|
});
|
|
77
89
|
|
|
78
90
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
79
91
|
const ast = parse(CODEINE_SMILES);
|
|
80
|
-
const code = ast.toCode('v');
|
|
92
|
+
const code = ast.toCode('v', { verbose: true });
|
|
81
93
|
const executableCode = stripExports(code);
|
|
82
94
|
|
|
83
95
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -102,6 +114,12 @@ describe('Oxycodone Integration Test', () => {
|
|
|
102
114
|
expect(code).toMatchSnapshot();
|
|
103
115
|
});
|
|
104
116
|
|
|
117
|
+
test('generates valid verbose code via toCode()', () => {
|
|
118
|
+
const ast = parse(OXYCODONE_SMILES);
|
|
119
|
+
const code = ast.toCode('v', { verbose: true });
|
|
120
|
+
expect(code).toMatchSnapshot();
|
|
121
|
+
});
|
|
122
|
+
|
|
105
123
|
test('generated code is valid JavaScript', () => {
|
|
106
124
|
const ast = parse(OXYCODONE_SMILES);
|
|
107
125
|
const code = ast.toCode('v');
|
|
@@ -109,14 +127,14 @@ describe('Oxycodone Integration Test', () => {
|
|
|
109
127
|
|
|
110
128
|
let factory;
|
|
111
129
|
expect(() => {
|
|
112
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
130
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
113
131
|
}).not.toThrow();
|
|
114
132
|
expect(typeof factory).toBe('function');
|
|
115
133
|
});
|
|
116
134
|
|
|
117
135
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
118
136
|
const ast = parse(OXYCODONE_SMILES);
|
|
119
|
-
const code = ast.toCode('v');
|
|
137
|
+
const code = ast.toCode('v', { verbose: true });
|
|
120
138
|
const executableCode = stripExports(code);
|
|
121
139
|
|
|
122
140
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -141,6 +159,12 @@ describe('Hydrocodone Integration Test', () => {
|
|
|
141
159
|
expect(code).toMatchSnapshot();
|
|
142
160
|
});
|
|
143
161
|
|
|
162
|
+
test('generates valid verbose code via toCode()', () => {
|
|
163
|
+
const ast = parse(HYDROCODONE_SMILES);
|
|
164
|
+
const code = ast.toCode('v', { verbose: true });
|
|
165
|
+
expect(code).toMatchSnapshot();
|
|
166
|
+
});
|
|
167
|
+
|
|
144
168
|
test('generated code is valid JavaScript', () => {
|
|
145
169
|
const ast = parse(HYDROCODONE_SMILES);
|
|
146
170
|
const code = ast.toCode('v');
|
|
@@ -148,14 +172,14 @@ describe('Hydrocodone Integration Test', () => {
|
|
|
148
172
|
|
|
149
173
|
let factory;
|
|
150
174
|
expect(() => {
|
|
151
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
175
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
152
176
|
}).not.toThrow();
|
|
153
177
|
expect(typeof factory).toBe('function');
|
|
154
178
|
});
|
|
155
179
|
|
|
156
180
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
157
181
|
const ast = parse(HYDROCODONE_SMILES);
|
|
158
|
-
const code = ast.toCode('v');
|
|
182
|
+
const code = ast.toCode('v', { verbose: true });
|
|
159
183
|
const executableCode = stripExports(code);
|
|
160
184
|
|
|
161
185
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -181,6 +205,12 @@ describe('Fentanyl Integration Test', () => {
|
|
|
181
205
|
expect(code).toMatchSnapshot();
|
|
182
206
|
});
|
|
183
207
|
|
|
208
|
+
test('generates valid verbose code via toCode()', () => {
|
|
209
|
+
const ast = parse(FENTANYL_SMILES);
|
|
210
|
+
const code = ast.toCode('v', { verbose: true });
|
|
211
|
+
expect(code).toMatchSnapshot();
|
|
212
|
+
});
|
|
213
|
+
|
|
184
214
|
test('generated code is valid JavaScript', () => {
|
|
185
215
|
const ast = parse(FENTANYL_SMILES);
|
|
186
216
|
const code = ast.toCode('v');
|
|
@@ -188,14 +218,14 @@ describe('Fentanyl Integration Test', () => {
|
|
|
188
218
|
|
|
189
219
|
let factory;
|
|
190
220
|
expect(() => {
|
|
191
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
221
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
192
222
|
}).not.toThrow();
|
|
193
223
|
expect(typeof factory).toBe('function');
|
|
194
224
|
});
|
|
195
225
|
|
|
196
226
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
197
227
|
const ast = parse(FENTANYL_SMILES);
|
|
198
|
-
const code = ast.toCode('v');
|
|
228
|
+
const code = ast.toCode('v', { verbose: true });
|
|
199
229
|
const executableCode = stripExports(code);
|
|
200
230
|
|
|
201
231
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -221,6 +251,12 @@ describe('Tramadol Integration Test', () => {
|
|
|
221
251
|
expect(code).toMatchSnapshot();
|
|
222
252
|
});
|
|
223
253
|
|
|
254
|
+
test('generates valid verbose code via toCode()', () => {
|
|
255
|
+
const ast = parse(TRAMADOL_SMILES);
|
|
256
|
+
const code = ast.toCode('v', { verbose: true });
|
|
257
|
+
expect(code).toMatchSnapshot();
|
|
258
|
+
});
|
|
259
|
+
|
|
224
260
|
test('generated code is valid JavaScript', () => {
|
|
225
261
|
const ast = parse(TRAMADOL_SMILES);
|
|
226
262
|
const code = ast.toCode('v');
|
|
@@ -228,14 +264,14 @@ describe('Tramadol Integration Test', () => {
|
|
|
228
264
|
|
|
229
265
|
let factory;
|
|
230
266
|
expect(() => {
|
|
231
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
267
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
232
268
|
}).not.toThrow();
|
|
233
269
|
expect(typeof factory).toBe('function');
|
|
234
270
|
});
|
|
235
271
|
|
|
236
272
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
237
273
|
const ast = parse(TRAMADOL_SMILES);
|
|
238
|
-
const code = ast.toCode('v');
|
|
274
|
+
const code = ast.toCode('v', { verbose: true });
|
|
239
275
|
const executableCode = stripExports(code);
|
|
240
276
|
|
|
241
277
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -261,6 +297,12 @@ describe('Methadone Integration Test', () => {
|
|
|
261
297
|
expect(code).toMatchSnapshot();
|
|
262
298
|
});
|
|
263
299
|
|
|
300
|
+
test('generates valid verbose code via toCode()', () => {
|
|
301
|
+
const ast = parse(METHADONE_SMILES);
|
|
302
|
+
const code = ast.toCode('v', { verbose: true });
|
|
303
|
+
expect(code).toMatchSnapshot();
|
|
304
|
+
});
|
|
305
|
+
|
|
264
306
|
test('generated code is valid JavaScript', () => {
|
|
265
307
|
const ast = parse(METHADONE_SMILES);
|
|
266
308
|
const code = ast.toCode('v');
|
|
@@ -268,14 +310,14 @@ describe('Methadone Integration Test', () => {
|
|
|
268
310
|
|
|
269
311
|
let factory;
|
|
270
312
|
expect(() => {
|
|
271
|
-
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', executableCode);
|
|
313
|
+
factory = createFunction('Ring', 'Linear', 'FusedRing', 'Molecule', 'RawFragment', 'Fragment', executableCode);
|
|
272
314
|
}).not.toThrow();
|
|
273
315
|
expect(typeof factory).toBe('function');
|
|
274
316
|
});
|
|
275
317
|
|
|
276
318
|
test('codegen round-trip: generated code produces valid SMILES', () => {
|
|
277
319
|
const ast = parse(METHADONE_SMILES);
|
|
278
|
-
const code = ast.toCode('v');
|
|
320
|
+
const code = ast.toCode('v', { verbose: true });
|
|
279
321
|
const executableCode = stripExports(code);
|
|
280
322
|
|
|
281
323
|
const varMatch = code.match(/export const (v\d+) = /g);
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { Ring, Linear, Molecule } from '../src/constructors.js';
|
|
3
|
+
|
|
4
|
+
describe('Polymer repeat() Integration', () => {
|
|
5
|
+
test('polyethylene trimer: -[CH2CH2]3-', () => {
|
|
6
|
+
const ethylene = Linear(['C', 'C']);
|
|
7
|
+
const pe = ethylene.repeat(3, 1, 2);
|
|
8
|
+
expect(pe.smiles).toBe('CCCCCC');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('polystyrene dimer: styrene with phenyl pendant', () => {
|
|
12
|
+
const styrene = Linear(['C', 'C']).attach(
|
|
13
|
+
2,
|
|
14
|
+
Ring({ atoms: 'c', size: 6 }),
|
|
15
|
+
);
|
|
16
|
+
const ps = styrene.repeat(2, 1, 2);
|
|
17
|
+
expect(ps.smiles).toBe('CC(c1ccccc1)CC(c2ccccc2)');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('polystyrene trimer', () => {
|
|
21
|
+
const styrene = Linear(['C', 'C']).attach(
|
|
22
|
+
2,
|
|
23
|
+
Ring({ atoms: 'c', size: 6 }),
|
|
24
|
+
);
|
|
25
|
+
const ps = styrene.repeat(3, 1, 2);
|
|
26
|
+
expect(ps.smiles).toBe('CC(c1ccccc1)CC(c2ccccc2)CC(c3ccccc3)');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('poly(vinyl alcohol) dimer: -[CH2CH(OH)]2-', () => {
|
|
30
|
+
const vinylAlcohol = Linear(['C', 'C']).attach(
|
|
31
|
+
2,
|
|
32
|
+
Linear(['O']),
|
|
33
|
+
);
|
|
34
|
+
const pva = vinylAlcohol.repeat(2, 1, 2);
|
|
35
|
+
expect(pva.smiles).toBe('CC(O)CC(O)');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('biphenyl: two linked benzene rings', () => {
|
|
39
|
+
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
40
|
+
const biphenyl = benzene.repeat(2, 1, 6);
|
|
41
|
+
expect(biphenyl.smiles).toBe('c1ccccc1c2ccccc2');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('terphenyl: three linked benzene rings', () => {
|
|
45
|
+
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
46
|
+
const terphenyl = benzene.repeat(3, 1, 6);
|
|
47
|
+
expect(terphenyl.smiles).toBe('c1ccccc1c2ccccc2c3ccccc3');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('bipyridine: two linked pyridine rings', () => {
|
|
51
|
+
const pyridine = Ring({
|
|
52
|
+
atoms: 'c',
|
|
53
|
+
size: 6,
|
|
54
|
+
substitutions: { 3: 'n' },
|
|
55
|
+
});
|
|
56
|
+
const bipyridine = pyridine.repeat(2, 1, 6);
|
|
57
|
+
expect(bipyridine.smiles).toBe('c1cnccc1c2cnccc2');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('polyoxymethylene trimer: -[CH2O]3-', () => {
|
|
61
|
+
const oxymethylene = Linear(['C', 'O']);
|
|
62
|
+
const pom = oxymethylene.repeat(3, 1, 2);
|
|
63
|
+
expect(pom.smiles).toBe('COCOCO');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('nylon 6,6 repeating unit dimer', () => {
|
|
67
|
+
const unit = Molecule([
|
|
68
|
+
Linear(['N', 'C'], [null, '=']),
|
|
69
|
+
Linear(['C', 'C', 'C', 'C']),
|
|
70
|
+
Linear(['C', 'N'], ['=', null]),
|
|
71
|
+
Linear(['C', 'C', 'C', 'C', 'C', 'C']),
|
|
72
|
+
]);
|
|
73
|
+
const dimer = unit.repeat(2, 1, 1);
|
|
74
|
+
expect(dimer.type).toBe('molecule');
|
|
75
|
+
expect(dimer.components).toHaveLength(2);
|
|
76
|
+
expect(dimer.smiles).toBe('N=CCCCC=CNCCCCCCN=CCCCC=CNCCCCCC');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('Polymer fusedRepeat() Integration', () => {
|
|
81
|
+
test('naphthalene: 2 fused aromatic rings', () => {
|
|
82
|
+
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
83
|
+
const naphthalene = benzene.fusedRepeat(2, 4);
|
|
84
|
+
expect(naphthalene.type).toBe('fused_ring');
|
|
85
|
+
expect(naphthalene.rings).toHaveLength(2);
|
|
86
|
+
expect(naphthalene.rings[0].ringNumber).toBe(1);
|
|
87
|
+
expect(naphthalene.rings[1].ringNumber).toBe(2);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('anthracene: 3 fused aromatic rings', () => {
|
|
91
|
+
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
92
|
+
const anthracene = benzene.fusedRepeat(3, 4);
|
|
93
|
+
expect(anthracene.type).toBe('fused_ring');
|
|
94
|
+
expect(anthracene.rings).toHaveLength(3);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('tetracene: 4 fused aromatic rings', () => {
|
|
98
|
+
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
99
|
+
const tetracene = benzene.fusedRepeat(4, 4);
|
|
100
|
+
expect(tetracene.type).toBe('fused_ring');
|
|
101
|
+
expect(tetracene.rings).toHaveLength(4);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('fused cyclohexane dimer (decalin-like)', () => {
|
|
105
|
+
const cyclohexane = Ring({ atoms: 'C', size: 6 });
|
|
106
|
+
const decalin = cyclohexane.fusedRepeat(2, 4);
|
|
107
|
+
expect(decalin.type).toBe('fused_ring');
|
|
108
|
+
expect(decalin.rings).toHaveLength(2);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('fused 5-membered rings (pentalene-like)', () => {
|
|
112
|
+
const cp = Ring({ atoms: 'C', size: 5 });
|
|
113
|
+
const pentalene = cp.fusedRepeat(2, 3);
|
|
114
|
+
expect(pentalene.type).toBe('fused_ring');
|
|
115
|
+
expect(pentalene.rings).toHaveLength(2);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('Polymer edge cases', () => {
|
|
120
|
+
test('repeat n=1 returns clone, not original', () => {
|
|
121
|
+
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
122
|
+
const clone = benzene.repeat(1, 1, 6);
|
|
123
|
+
expect(clone.smiles).toBe('c1ccccc1');
|
|
124
|
+
expect(clone).not.toBe(benzene);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('fusedRepeat n=1 returns ring clone', () => {
|
|
128
|
+
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
129
|
+
const clone = benzene.fusedRepeat(1, 4);
|
|
130
|
+
expect(clone.smiles).toBe('c1ccccc1');
|
|
131
|
+
expect(clone.type).toBe('ring');
|
|
132
|
+
expect(clone).not.toBe(benzene);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('large repeat count (n=10)', () => {
|
|
136
|
+
const ethylene = Linear(['C', 'C']);
|
|
137
|
+
const pe10 = ethylene.repeat(10, 1, 2);
|
|
138
|
+
expect(pe10.smiles).toBe('C'.repeat(20));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('repeat preserves immutability of original', () => {
|
|
142
|
+
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
143
|
+
const original = benzene.smiles;
|
|
144
|
+
benzene.repeat(5, 1, 6);
|
|
145
|
+
benzene.fusedRepeat(3, 4);
|
|
146
|
+
expect(benzene.smiles).toBe(original);
|
|
147
|
+
});
|
|
148
|
+
});
|