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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selfies-js",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Pure JavaScript SELFIES encoder/decoder with DSL for molecular composition",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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
- // Skip structural tokens in branch
419
- if (content.includes('Branch') || content.includes('Ring') ||
420
- content.includes('ch') || content.includes('ng')) {
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 will catch many structural issues
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({