selfies-js 0.3.4 → 0.3.5
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/package.json +1 -1
- package/src/decoder.js +74 -3
- package/src/dsl/valenceValidator.js +3 -19
package/package.json
CHANGED
package/src/decoder.js
CHANGED
|
@@ -415,10 +415,81 @@ export function deriveBranch(tokens, startIndex, maxDerive, initState, rootAtom,
|
|
|
415
415
|
const token = tokens[startIndex + consumed]
|
|
416
416
|
const content = token.slice(1, -1)
|
|
417
417
|
|
|
418
|
-
//
|
|
419
|
-
if (content.includes('
|
|
420
|
-
|
|
418
|
+
// Handle Ring tokens inside branch
|
|
419
|
+
if (content.includes('Ring') || content.includes('ng')) {
|
|
420
|
+
const ringInfo = processRingSymbol(token)
|
|
421
|
+
if (!ringInfo) {
|
|
422
|
+
throw new Error(`Invalid ring token in branch: ${token}`)
|
|
423
|
+
}
|
|
424
|
+
if (state === 0) {
|
|
425
|
+
throw new Error(`Ring ${token} at invalid state 0 inside branch`)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const { order: requestedOrder, L } = ringInfo
|
|
429
|
+
const [bondOrder, nextState] = nextRingState(requestedOrder, state)
|
|
430
|
+
|
|
431
|
+
consumed++
|
|
432
|
+
derived++ // Ring counts toward derive limit
|
|
433
|
+
|
|
434
|
+
if (consumed >= tokens.length - startIndex) {
|
|
435
|
+
state = nextState
|
|
436
|
+
break
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const Q = readIndexFromTokens(tokens, startIndex + consumed, L)
|
|
440
|
+
consumed += Q.consumed
|
|
441
|
+
derived += Q.consumed // Q index tokens also count
|
|
442
|
+
|
|
443
|
+
const targetIndex = Math.max(0, prevAtomIndex - (Q.value + 1))
|
|
444
|
+
if (targetIndex !== prevAtomIndex) {
|
|
445
|
+
handleRingClosure(targetIndex, prevAtomIndex, bondOrder, bonds, rings)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
state = nextState
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Handle nested Branch tokens - recursively process them
|
|
453
|
+
if (content.includes('Branch') || content.includes('ch')) {
|
|
454
|
+
const branchInfo = processBranchSymbol(token)
|
|
455
|
+
if (!branchInfo) {
|
|
456
|
+
throw new Error(`Invalid branch token in branch: ${token}`)
|
|
457
|
+
}
|
|
458
|
+
if (state <= 1) {
|
|
459
|
+
throw new Error(`Branch ${token} at invalid state ${state} inside branch`)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const { order: branchOrder, L } = branchInfo
|
|
463
|
+
const [branchInitState, nextState] = nextBranchState(branchOrder, state)
|
|
464
|
+
|
|
421
465
|
consumed++
|
|
466
|
+
derived++ // Branch counts toward derive limit
|
|
467
|
+
|
|
468
|
+
if (consumed >= tokens.length - startIndex) {
|
|
469
|
+
state = nextState
|
|
470
|
+
break
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Read Q index
|
|
474
|
+
const Q = readIndexFromTokens(tokens, startIndex + consumed, L)
|
|
475
|
+
consumed += Q.consumed
|
|
476
|
+
derived += Q.consumed // Q index tokens count
|
|
477
|
+
|
|
478
|
+
// Recursively derive nested branch
|
|
479
|
+
const nestedResult = deriveBranch(
|
|
480
|
+
tokens,
|
|
481
|
+
startIndex + consumed,
|
|
482
|
+
Q.value + 1,
|
|
483
|
+
branchInitState,
|
|
484
|
+
prevAtomIndex,
|
|
485
|
+
atoms,
|
|
486
|
+
bonds,
|
|
487
|
+
rings
|
|
488
|
+
)
|
|
489
|
+
consumed += nestedResult.consumed
|
|
490
|
+
derived += nestedResult.derived
|
|
491
|
+
|
|
492
|
+
state = nextState
|
|
422
493
|
continue
|
|
423
494
|
}
|
|
424
495
|
|
|
@@ -25,26 +25,10 @@ export function validateValence(selfies, defName) {
|
|
|
25
25
|
const errors = []
|
|
26
26
|
|
|
27
27
|
try {
|
|
28
|
-
// Try to decode to SMILES - this
|
|
28
|
+
// Try to decode to SMILES - this validates the structure
|
|
29
|
+
// The decoder's state machine properly enforces valence rules
|
|
29
30
|
const smiles = decode(selfies)
|
|
30
|
-
|
|
31
|
-
// Parse the SELFIES to extract atoms and bonds
|
|
32
|
-
const tokens = tokenizeSelfies(selfies)
|
|
33
|
-
const atomBonds = calculateBonds(tokens)
|
|
34
|
-
|
|
35
|
-
// Check each atom's valence
|
|
36
|
-
for (const [atom, bondCount] of Object.entries(atomBonds)) {
|
|
37
|
-
const { element, charge } = parseAtom(atom)
|
|
38
|
-
const maxBonds = getBondingCapacity(element, charge)
|
|
39
|
-
|
|
40
|
-
if (bondCount > maxBonds) {
|
|
41
|
-
errors.push({
|
|
42
|
-
message: `Valence error in '${defName}': ${element} has ${bondCount} bonds but max is ${maxBonds}`,
|
|
43
|
-
severity: 'error',
|
|
44
|
-
definitionName: defName
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
31
|
+
// If decoding succeeds, the SELFIES is valid
|
|
48
32
|
} catch (error) {
|
|
49
33
|
// If decoding fails, it's a structural error
|
|
50
34
|
errors.push({
|