eyeling 1.24.26 → 1.24.27

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.
@@ -844,6 +844,62 @@ function compileSwapRegex(pattern, extraFlags) {
844
844
  }
845
845
  }
846
846
 
847
+ function expandSwapRegexReplacement(template, captures) {
848
+ // SWAP/N3 examples use $1-style capture references, but tests also rely on
849
+ // Perl-ish escaping in replacement strings: \\$ means a literal dollar sign
850
+ // and \\ means a literal backslash. JavaScript's replacement strings do not
851
+ // interpret those escapes, so expand the replacement explicitly in a callback.
852
+ let out = '';
853
+ const text = String(template);
854
+
855
+ for (let i = 0; i < text.length; i++) {
856
+ const ch = text[i];
857
+
858
+ if (ch === '\\') {
859
+ if (i + 1 < text.length && (text[i + 1] === '$' || text[i + 1] === '\\')) {
860
+ out += text[++i];
861
+ } else {
862
+ out += ch;
863
+ }
864
+ continue;
865
+ }
866
+
867
+ if (ch === '$') {
868
+ if (i + 1 < text.length && text[i + 1] === '$') {
869
+ out += '$';
870
+ i++;
871
+ continue;
872
+ }
873
+
874
+ let j = i + 1;
875
+ while (j < text.length && /[0-9]/.test(text[j])) j++;
876
+ if (j > i + 1) {
877
+ const digits = text.slice(i + 1, j);
878
+ let chosen = null;
879
+ for (let k = digits.length; k > 0; k--) {
880
+ const n = Number(digits.slice(0, k));
881
+ if (n > 0 && n < captures.length) {
882
+ chosen = { n, len: k };
883
+ break;
884
+ }
885
+ }
886
+ if (chosen) {
887
+ out += captures[chosen.n] == null ? '' : String(captures[chosen.n]);
888
+ out += digits.slice(chosen.len);
889
+ } else {
890
+ out += '$' + digits;
891
+ }
892
+ i = j - 1;
893
+ continue;
894
+ }
895
+ }
896
+
897
+ out += ch;
898
+ }
899
+
900
+ return out;
901
+ }
902
+
847
903
  // -----------------------------------------------------------------------------
848
904
  // Strict numeric literal parsing for math: builtins
849
905
  // -----------------------------------------------------------------------------
@@ -4222,7 +4278,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
4222
4278
  const re = compileSwapRegex(searchStr, 'g');
4223
4279
  if (!re) return [];
4224
4280
 
4225
- const outStr = dataStr.replace(re, replStr);
4281
+ const outStr = dataStr.replace(re, (...args) => {
4282
+ const captureEnd = args.length - (typeof args.at(-1) === 'object' ? 3 : 2);
4283
+ const captures = args.slice(0, Math.max(1, captureEnd));
4284
+ return expandSwapRegexReplacement(replStr, captures);
4285
+ });
4226
4286
  const lit = makeStringLiteral(outStr);
4227
4287
 
4228
4288
  if (g.o instanceof Var) {
package/eyeling.js CHANGED
@@ -844,6 +844,62 @@ function compileSwapRegex(pattern, extraFlags) {
844
844
  }
845
845
  }
846
846
 
847
+ function expandSwapRegexReplacement(template, captures) {
848
+ // SWAP/N3 examples use $1-style capture references, but tests also rely on
849
+ // Perl-ish escaping in replacement strings: \\$ means a literal dollar sign
850
+ // and \\ means a literal backslash. JavaScript's replacement strings do not
851
+ // interpret those escapes, so expand the replacement explicitly in a callback.
852
+ let out = '';
853
+ const text = String(template);
854
+
855
+ for (let i = 0; i < text.length; i++) {
856
+ const ch = text[i];
857
+
858
+ if (ch === '\\') {
859
+ if (i + 1 < text.length && (text[i + 1] === '$' || text[i + 1] === '\\')) {
860
+ out += text[++i];
861
+ } else {
862
+ out += ch;
863
+ }
864
+ continue;
865
+ }
866
+
867
+ if (ch === '$') {
868
+ if (i + 1 < text.length && text[i + 1] === '$') {
869
+ out += '$';
870
+ i++;
871
+ continue;
872
+ }
873
+
874
+ let j = i + 1;
875
+ while (j < text.length && /[0-9]/.test(text[j])) j++;
876
+ if (j > i + 1) {
877
+ const digits = text.slice(i + 1, j);
878
+ let chosen = null;
879
+ for (let k = digits.length; k > 0; k--) {
880
+ const n = Number(digits.slice(0, k));
881
+ if (n > 0 && n < captures.length) {
882
+ chosen = { n, len: k };
883
+ break;
884
+ }
885
+ }
886
+ if (chosen) {
887
+ out += captures[chosen.n] == null ? '' : String(captures[chosen.n]);
888
+ out += digits.slice(chosen.len);
889
+ } else {
890
+ out += '$' + digits;
891
+ }
892
+ i = j - 1;
893
+ continue;
894
+ }
895
+ }
896
+
897
+ out += ch;
898
+ }
899
+
900
+ return out;
901
+ }
902
+
847
903
  // -----------------------------------------------------------------------------
848
904
  // Strict numeric literal parsing for math: builtins
849
905
  // -----------------------------------------------------------------------------
@@ -4222,7 +4278,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
4222
4278
  const re = compileSwapRegex(searchStr, 'g');
4223
4279
  if (!re) return [];
4224
4280
 
4225
- const outStr = dataStr.replace(re, replStr);
4281
+ const outStr = dataStr.replace(re, (...args) => {
4282
+ const captureEnd = args.length - (typeof args.at(-1) === 'object' ? 3 : 2);
4283
+ const captures = args.slice(0, Math.max(1, captureEnd));
4284
+ return expandSwapRegexReplacement(replStr, captures);
4285
+ });
4226
4286
  const lit = makeStringLiteral(outStr);
4227
4287
 
4228
4288
  if (g.o instanceof Var) {
package/lib/builtins.js CHANGED
@@ -833,6 +833,62 @@ function compileSwapRegex(pattern, extraFlags) {
833
833
  }
834
834
  }
835
835
 
836
+ function expandSwapRegexReplacement(template, captures) {
837
+ // SWAP/N3 examples use $1-style capture references, but tests also rely on
838
+ // Perl-ish escaping in replacement strings: \\$ means a literal dollar sign
839
+ // and \\ means a literal backslash. JavaScript's replacement strings do not
840
+ // interpret those escapes, so expand the replacement explicitly in a callback.
841
+ let out = '';
842
+ const text = String(template);
843
+
844
+ for (let i = 0; i < text.length; i++) {
845
+ const ch = text[i];
846
+
847
+ if (ch === '\\') {
848
+ if (i + 1 < text.length && (text[i + 1] === '$' || text[i + 1] === '\\')) {
849
+ out += text[++i];
850
+ } else {
851
+ out += ch;
852
+ }
853
+ continue;
854
+ }
855
+
856
+ if (ch === '$') {
857
+ if (i + 1 < text.length && text[i + 1] === '$') {
858
+ out += '$';
859
+ i++;
860
+ continue;
861
+ }
862
+
863
+ let j = i + 1;
864
+ while (j < text.length && /[0-9]/.test(text[j])) j++;
865
+ if (j > i + 1) {
866
+ const digits = text.slice(i + 1, j);
867
+ let chosen = null;
868
+ for (let k = digits.length; k > 0; k--) {
869
+ const n = Number(digits.slice(0, k));
870
+ if (n > 0 && n < captures.length) {
871
+ chosen = { n, len: k };
872
+ break;
873
+ }
874
+ }
875
+ if (chosen) {
876
+ out += captures[chosen.n] == null ? '' : String(captures[chosen.n]);
877
+ out += digits.slice(chosen.len);
878
+ } else {
879
+ out += '$' + digits;
880
+ }
881
+ i = j - 1;
882
+ continue;
883
+ }
884
+ }
885
+
886
+ out += ch;
887
+ }
888
+
889
+ return out;
890
+ }
891
+
836
892
  // -----------------------------------------------------------------------------
837
893
  // Strict numeric literal parsing for math: builtins
838
894
  // -----------------------------------------------------------------------------
@@ -4211,7 +4267,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
4211
4267
  const re = compileSwapRegex(searchStr, 'g');
4212
4268
  if (!re) return [];
4213
4269
 
4214
- const outStr = dataStr.replace(re, replStr);
4270
+ const outStr = dataStr.replace(re, (...args) => {
4271
+ const captureEnd = args.length - (typeof args.at(-1) === 'object' ? 3 : 2);
4272
+ const captures = args.slice(0, Math.max(1, captureEnd));
4273
+ return expandSwapRegexReplacement(replStr, captures);
4274
+ });
4215
4275
  const lit = makeStringLiteral(outStr);
4216
4276
 
4217
4277
  if (g.o instanceof Var) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.24.26",
3
+ "version": "1.24.27",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -1533,6 +1533,35 @@ res:CITY_Chañaral rdfs:label "Chañaral".
1533
1533
  expect: [/:result\s+:has\s+:success-literal-30\s*\./, /:test\s+:is\s+true\s*\./],
1534
1534
  },
1535
1535
 
1536
+ {
1537
+ name: '52g regression: string:replace honors escaped dollar and backslash in replacement',
1538
+ opt: { proofComments: false },
1539
+ input: `@prefix : <http://example.org/> .
1540
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
1541
+
1542
+ {
1543
+ ("abcd" "b" "\\$\\\\") string:replace "a$\\cd".
1544
+ }
1545
+ =>
1546
+ {
1547
+ :result :has :success-literal-8.
1548
+ }.
1549
+
1550
+ {} => {
1551
+ :test :contains :success-literal-8.
1552
+ }.
1553
+
1554
+ {
1555
+ :result :has :success-literal-8.
1556
+ }
1557
+ =>
1558
+ {
1559
+ :test :is true.
1560
+ }.
1561
+ `,
1562
+ expect: [/:result\s+:has\s+:success-literal-8\s*\./, /:test\s+:is\s+true\s*\./],
1563
+ },
1564
+
1536
1565
  {
1537
1566
  name: '53 --stream: prints prefixes used in input (not just derived output) before streaming triples',
1538
1567
  opt: ['--stream', '-n'],