links-notation 0.13.0 → 0.15.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/README.md +55 -0
- package/bun.lock +74 -94
- package/dist/index.js +238 -166
- package/eslint.config.js +34 -0
- package/examples/empty_reference.js +43 -0
- package/examples/typescript-example.ts +95 -0
- package/index.d.ts +312 -0
- package/package.json +19 -7
- package/src/Link.js +7 -1
- package/src/Parser.js +36 -3
- package/src/grammar.pegjs +86 -12
- package/src/parser-generated.js +221 -147
- package/tests/EmptyReference.test.js +91 -0
- package/tests/Link.test.js +2 -2
- package/tests/MultilineQuotedString.test.js +4 -2
- package/tests/NestedIndentation.test.js +114 -0
- package/tests/SingleLineParser.test.js +7 -7
- package/tsconfig.json +15 -0
package/src/parser-generated.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @generated by Peggy 5.0.
|
|
1
|
+
// @generated by Peggy 5.1.0.
|
|
2
2
|
//
|
|
3
3
|
// https://peggyjs.org/
|
|
4
4
|
|
|
@@ -164,9 +164,9 @@ function peg$parse(input, options) {
|
|
|
164
164
|
};
|
|
165
165
|
let peg$startRuleFunction = peg$parsedocument;
|
|
166
166
|
|
|
167
|
-
const peg$c0 = "
|
|
168
|
-
const peg$c1 = "
|
|
169
|
-
const peg$c2 = "
|
|
167
|
+
const peg$c0 = "(";
|
|
168
|
+
const peg$c1 = ")";
|
|
169
|
+
const peg$c2 = ":";
|
|
170
170
|
const peg$c3 = "\"";
|
|
171
171
|
const peg$c4 = "'";
|
|
172
172
|
const peg$c5 = "`";
|
|
@@ -179,9 +179,9 @@ function peg$parse(input, options) {
|
|
|
179
179
|
|
|
180
180
|
const peg$e0 = peg$classExpectation([" ", "\t"], false, false, false);
|
|
181
181
|
const peg$e1 = peg$classExpectation(["\r", "\n"], false, false, false);
|
|
182
|
-
const peg$e2 = peg$literalExpectation("
|
|
183
|
-
const peg$e3 = peg$literalExpectation("
|
|
184
|
-
const peg$e4 = peg$literalExpectation("
|
|
182
|
+
const peg$e2 = peg$literalExpectation("(", false);
|
|
183
|
+
const peg$e3 = peg$literalExpectation(")", false);
|
|
184
|
+
const peg$e4 = peg$literalExpectation(":", false);
|
|
185
185
|
const peg$e5 = peg$literalExpectation("\"", false);
|
|
186
186
|
const peg$e6 = peg$anyExpectation();
|
|
187
187
|
const peg$e7 = peg$literalExpectation("'", false);
|
|
@@ -190,15 +190,15 @@ function peg$parse(input, options) {
|
|
|
190
190
|
const peg$e10 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false, false);
|
|
191
191
|
const peg$e11 = peg$classExpectation([" ", "\t", "\n", "\r", "(", ":", ")"], true, false, false);
|
|
192
192
|
|
|
193
|
-
function peg$f0() {
|
|
193
|
+
function peg$f0() { return resetState(); }
|
|
194
194
|
function peg$f1(links) { return links; }
|
|
195
|
-
function peg$f2() {
|
|
195
|
+
function peg$f2() { return resetState(); }
|
|
196
196
|
function peg$f3() { return []; }
|
|
197
197
|
function peg$f4(fl, list) { popIndentation(); return [fl].concat(list || []); }
|
|
198
198
|
function peg$f5(l) { return l; }
|
|
199
199
|
function peg$f6(l) { return l; }
|
|
200
200
|
function peg$f7(e, l) {
|
|
201
|
-
return {
|
|
201
|
+
return Object.assign({}, e, { children: l });
|
|
202
202
|
}
|
|
203
203
|
function peg$f8(e) { return e; }
|
|
204
204
|
function peg$f9(l) { return l; }
|
|
@@ -208,13 +208,16 @@ function peg$parse(input, options) {
|
|
|
208
208
|
function peg$f13(sl) { return sl; }
|
|
209
209
|
function peg$f14(fl) { return fl; }
|
|
210
210
|
function peg$f15(vl) { return vl; }
|
|
211
|
-
function peg$f16(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
function peg$
|
|
216
|
-
function peg$
|
|
217
|
-
function peg$
|
|
211
|
+
function peg$f16(body) {
|
|
212
|
+
exitNestedContext();
|
|
213
|
+
return body;
|
|
214
|
+
}
|
|
215
|
+
function peg$f17(l) { return { nested: l }; }
|
|
216
|
+
function peg$f18() { return { nested: [] }; }
|
|
217
|
+
function peg$f19() { return enterNestedContext(); }
|
|
218
|
+
function peg$f20(value) { return value; }
|
|
219
|
+
function peg$f21(list) { return list; }
|
|
220
|
+
function peg$f22(id, v) { return { id: id, values: v }; }
|
|
218
221
|
function peg$f23(v) { return { values: v }; }
|
|
219
222
|
function peg$f24(id) { return { id: id, values: [] }; }
|
|
220
223
|
function peg$f25(chars) { return chars.join(''); }
|
|
@@ -264,6 +267,7 @@ function peg$parse(input, options) {
|
|
|
264
267
|
function peg$f42(spaces) { return normalizeIndentation(spaces) > getCurrentIndentation(); }
|
|
265
268
|
function peg$f43(spaces) { pushIndentation(spaces); }
|
|
266
269
|
function peg$f44(spaces) { return checkIndentation(spaces); }
|
|
270
|
+
function peg$f45() { return isInsideNestedContext(); }
|
|
267
271
|
let peg$currPos = options.peg$currPos | 0;
|
|
268
272
|
let peg$savedPos = peg$currPos;
|
|
269
273
|
const peg$posDetailsCache = [{ line: 1, column: 1 }];
|
|
@@ -673,7 +677,7 @@ function peg$parse(input, options) {
|
|
|
673
677
|
let s0, s1;
|
|
674
678
|
|
|
675
679
|
s0 = peg$currPos;
|
|
676
|
-
s1 = peg$
|
|
680
|
+
s1 = peg$parsenestedGroup();
|
|
677
681
|
if (s1 !== peg$FAILED) {
|
|
678
682
|
peg$savedPos = s0;
|
|
679
683
|
s1 = peg$f9(s1);
|
|
@@ -696,7 +700,7 @@ function peg$parse(input, options) {
|
|
|
696
700
|
let s0, s1, s2;
|
|
697
701
|
|
|
698
702
|
s0 = peg$currPos;
|
|
699
|
-
s1 = peg$
|
|
703
|
+
s1 = peg$parsenestedGroup();
|
|
700
704
|
if (s1 !== peg$FAILED) {
|
|
701
705
|
s2 = peg$parseeol();
|
|
702
706
|
if (s2 !== peg$FAILED) {
|
|
@@ -732,17 +736,6 @@ function peg$parse(input, options) {
|
|
|
732
736
|
return s0;
|
|
733
737
|
}
|
|
734
738
|
|
|
735
|
-
function peg$parsemultiLineAnyLink() {
|
|
736
|
-
let s0;
|
|
737
|
-
|
|
738
|
-
s0 = peg$parsemultiLineValueLink();
|
|
739
|
-
if (s0 === peg$FAILED) {
|
|
740
|
-
s0 = peg$parsemultiLineLink();
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
return s0;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
739
|
function peg$parsesingleLineAnyLink() {
|
|
747
740
|
let s0, s1, s2;
|
|
748
741
|
|
|
@@ -782,15 +775,32 @@ function peg$parse(input, options) {
|
|
|
782
775
|
return s0;
|
|
783
776
|
}
|
|
784
777
|
|
|
785
|
-
function peg$
|
|
786
|
-
let s0, s1, s2;
|
|
778
|
+
function peg$parsenestedGroup() {
|
|
779
|
+
let s0, s1, s2, s3;
|
|
787
780
|
|
|
788
781
|
s0 = peg$currPos;
|
|
789
|
-
|
|
782
|
+
if (input.charCodeAt(peg$currPos) === 40) {
|
|
783
|
+
s1 = peg$c0;
|
|
784
|
+
peg$currPos++;
|
|
785
|
+
} else {
|
|
786
|
+
s1 = peg$FAILED;
|
|
787
|
+
if (peg$silentFails === 0) { peg$fail(peg$e2); }
|
|
788
|
+
}
|
|
790
789
|
if (s1 !== peg$FAILED) {
|
|
791
|
-
s2 = peg$
|
|
792
|
-
peg$
|
|
793
|
-
|
|
790
|
+
s2 = peg$parseENTER_NESTED_CONTEXT();
|
|
791
|
+
if (s2 !== peg$FAILED) {
|
|
792
|
+
s3 = peg$parsenestedGroupBody();
|
|
793
|
+
if (s3 !== peg$FAILED) {
|
|
794
|
+
peg$savedPos = s0;
|
|
795
|
+
s0 = peg$f16(s3);
|
|
796
|
+
} else {
|
|
797
|
+
peg$currPos = s0;
|
|
798
|
+
s0 = peg$FAILED;
|
|
799
|
+
}
|
|
800
|
+
} else {
|
|
801
|
+
peg$currPos = s0;
|
|
802
|
+
s0 = peg$FAILED;
|
|
803
|
+
}
|
|
794
804
|
} else {
|
|
795
805
|
peg$currPos = s0;
|
|
796
806
|
s0 = peg$FAILED;
|
|
@@ -799,19 +809,64 @@ function peg$parse(input, options) {
|
|
|
799
809
|
return s0;
|
|
800
810
|
}
|
|
801
811
|
|
|
802
|
-
function peg$
|
|
803
|
-
let s0, s1, s2, s3;
|
|
812
|
+
function peg$parsenestedGroupBody() {
|
|
813
|
+
let s0, s1, s2, s3, s4;
|
|
804
814
|
|
|
805
815
|
s0 = peg$currPos;
|
|
806
|
-
s1 = peg$
|
|
807
|
-
s2 =
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
816
|
+
s1 = peg$parseskipEmptyLines();
|
|
817
|
+
s2 = peg$parselinks();
|
|
818
|
+
if (s2 !== peg$FAILED) {
|
|
819
|
+
s3 = peg$parse_();
|
|
820
|
+
if (input.charCodeAt(peg$currPos) === 41) {
|
|
821
|
+
s4 = peg$c1;
|
|
822
|
+
peg$currPos++;
|
|
823
|
+
} else {
|
|
824
|
+
s4 = peg$FAILED;
|
|
825
|
+
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
|
826
|
+
}
|
|
827
|
+
if (s4 !== peg$FAILED) {
|
|
828
|
+
peg$savedPos = s0;
|
|
829
|
+
s0 = peg$f17(s2);
|
|
830
|
+
} else {
|
|
831
|
+
peg$currPos = s0;
|
|
832
|
+
s0 = peg$FAILED;
|
|
833
|
+
}
|
|
834
|
+
} else {
|
|
835
|
+
peg$currPos = s0;
|
|
836
|
+
s0 = peg$FAILED;
|
|
837
|
+
}
|
|
838
|
+
if (s0 === peg$FAILED) {
|
|
839
|
+
s0 = peg$currPos;
|
|
840
|
+
s1 = peg$parse_();
|
|
841
|
+
if (input.charCodeAt(peg$currPos) === 41) {
|
|
842
|
+
s2 = peg$c1;
|
|
843
|
+
peg$currPos++;
|
|
844
|
+
} else {
|
|
845
|
+
s2 = peg$FAILED;
|
|
846
|
+
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
|
847
|
+
}
|
|
848
|
+
if (s2 !== peg$FAILED) {
|
|
849
|
+
peg$savedPos = s0;
|
|
850
|
+
s0 = peg$f18();
|
|
851
|
+
} else {
|
|
852
|
+
peg$currPos = s0;
|
|
853
|
+
s0 = peg$FAILED;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return s0;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function peg$parseENTER_NESTED_CONTEXT() {
|
|
861
|
+
let s0;
|
|
862
|
+
|
|
863
|
+
peg$savedPos = peg$currPos;
|
|
864
|
+
s0 = peg$f19();
|
|
865
|
+
if (s0) {
|
|
866
|
+
s0 = undefined;
|
|
867
|
+
} else {
|
|
868
|
+
s0 = peg$FAILED;
|
|
812
869
|
}
|
|
813
|
-
peg$savedPos = s0;
|
|
814
|
-
s0 = peg$f17(s2);
|
|
815
870
|
|
|
816
871
|
return s0;
|
|
817
872
|
}
|
|
@@ -824,7 +879,7 @@ function peg$parse(input, options) {
|
|
|
824
879
|
s2 = peg$parsereferenceOrLink();
|
|
825
880
|
if (s2 !== peg$FAILED) {
|
|
826
881
|
peg$savedPos = s0;
|
|
827
|
-
s0 = peg$
|
|
882
|
+
s0 = peg$f20(s2);
|
|
828
883
|
} else {
|
|
829
884
|
peg$currPos = s0;
|
|
830
885
|
s0 = peg$FAILED;
|
|
@@ -849,7 +904,7 @@ function peg$parse(input, options) {
|
|
|
849
904
|
}
|
|
850
905
|
if (s1 !== peg$FAILED) {
|
|
851
906
|
peg$savedPos = s0;
|
|
852
|
-
s1 = peg$
|
|
907
|
+
s1 = peg$f21(s1);
|
|
853
908
|
}
|
|
854
909
|
s0 = s1;
|
|
855
910
|
|
|
@@ -865,73 +920,17 @@ function peg$parse(input, options) {
|
|
|
865
920
|
if (s2 !== peg$FAILED) {
|
|
866
921
|
s3 = peg$parse__();
|
|
867
922
|
if (input.charCodeAt(peg$currPos) === 58) {
|
|
868
|
-
s4 = peg$
|
|
923
|
+
s4 = peg$c2;
|
|
869
924
|
peg$currPos++;
|
|
870
925
|
} else {
|
|
871
926
|
s4 = peg$FAILED;
|
|
872
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
927
|
+
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
|
873
928
|
}
|
|
874
929
|
if (s4 !== peg$FAILED) {
|
|
875
930
|
s5 = peg$parsesingleLineValues();
|
|
876
931
|
if (s5 !== peg$FAILED) {
|
|
877
932
|
peg$savedPos = s0;
|
|
878
|
-
s0 = peg$
|
|
879
|
-
} else {
|
|
880
|
-
peg$currPos = s0;
|
|
881
|
-
s0 = peg$FAILED;
|
|
882
|
-
}
|
|
883
|
-
} else {
|
|
884
|
-
peg$currPos = s0;
|
|
885
|
-
s0 = peg$FAILED;
|
|
886
|
-
}
|
|
887
|
-
} else {
|
|
888
|
-
peg$currPos = s0;
|
|
889
|
-
s0 = peg$FAILED;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
return s0;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
function peg$parsemultiLineLink() {
|
|
896
|
-
let s0, s1, s2, s3, s4, s5, s6, s7, s8;
|
|
897
|
-
|
|
898
|
-
s0 = peg$currPos;
|
|
899
|
-
if (input.charCodeAt(peg$currPos) === 40) {
|
|
900
|
-
s1 = peg$c1;
|
|
901
|
-
peg$currPos++;
|
|
902
|
-
} else {
|
|
903
|
-
s1 = peg$FAILED;
|
|
904
|
-
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
|
905
|
-
}
|
|
906
|
-
if (s1 !== peg$FAILED) {
|
|
907
|
-
s2 = peg$parse_();
|
|
908
|
-
s3 = peg$parsereference();
|
|
909
|
-
if (s3 !== peg$FAILED) {
|
|
910
|
-
s4 = peg$parse_();
|
|
911
|
-
if (input.charCodeAt(peg$currPos) === 58) {
|
|
912
|
-
s5 = peg$c0;
|
|
913
|
-
peg$currPos++;
|
|
914
|
-
} else {
|
|
915
|
-
s5 = peg$FAILED;
|
|
916
|
-
if (peg$silentFails === 0) { peg$fail(peg$e2); }
|
|
917
|
-
}
|
|
918
|
-
if (s5 !== peg$FAILED) {
|
|
919
|
-
s6 = peg$parsemultiLineValues();
|
|
920
|
-
s7 = peg$parse_();
|
|
921
|
-
if (input.charCodeAt(peg$currPos) === 41) {
|
|
922
|
-
s8 = peg$c2;
|
|
923
|
-
peg$currPos++;
|
|
924
|
-
} else {
|
|
925
|
-
s8 = peg$FAILED;
|
|
926
|
-
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
|
927
|
-
}
|
|
928
|
-
if (s8 !== peg$FAILED) {
|
|
929
|
-
peg$savedPos = s0;
|
|
930
|
-
s0 = peg$f21(s3, s6);
|
|
931
|
-
} else {
|
|
932
|
-
peg$currPos = s0;
|
|
933
|
-
s0 = peg$FAILED;
|
|
934
|
-
}
|
|
933
|
+
s0 = peg$f22(s2, s5);
|
|
935
934
|
} else {
|
|
936
935
|
peg$currPos = s0;
|
|
937
936
|
s0 = peg$FAILED;
|
|
@@ -955,49 +954,13 @@ function peg$parse(input, options) {
|
|
|
955
954
|
s1 = peg$parsesingleLineValues();
|
|
956
955
|
if (s1 !== peg$FAILED) {
|
|
957
956
|
peg$savedPos = s0;
|
|
958
|
-
s1 = peg$
|
|
957
|
+
s1 = peg$f23(s1);
|
|
959
958
|
}
|
|
960
959
|
s0 = s1;
|
|
961
960
|
|
|
962
961
|
return s0;
|
|
963
962
|
}
|
|
964
963
|
|
|
965
|
-
function peg$parsemultiLineValueLink() {
|
|
966
|
-
let s0, s1, s2, s3, s4;
|
|
967
|
-
|
|
968
|
-
s0 = peg$currPos;
|
|
969
|
-
if (input.charCodeAt(peg$currPos) === 40) {
|
|
970
|
-
s1 = peg$c1;
|
|
971
|
-
peg$currPos++;
|
|
972
|
-
} else {
|
|
973
|
-
s1 = peg$FAILED;
|
|
974
|
-
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
|
975
|
-
}
|
|
976
|
-
if (s1 !== peg$FAILED) {
|
|
977
|
-
s2 = peg$parsemultiLineValues();
|
|
978
|
-
s3 = peg$parse_();
|
|
979
|
-
if (input.charCodeAt(peg$currPos) === 41) {
|
|
980
|
-
s4 = peg$c2;
|
|
981
|
-
peg$currPos++;
|
|
982
|
-
} else {
|
|
983
|
-
s4 = peg$FAILED;
|
|
984
|
-
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
|
985
|
-
}
|
|
986
|
-
if (s4 !== peg$FAILED) {
|
|
987
|
-
peg$savedPos = s0;
|
|
988
|
-
s0 = peg$f23(s2);
|
|
989
|
-
} else {
|
|
990
|
-
peg$currPos = s0;
|
|
991
|
-
s0 = peg$FAILED;
|
|
992
|
-
}
|
|
993
|
-
} else {
|
|
994
|
-
peg$currPos = s0;
|
|
995
|
-
s0 = peg$FAILED;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
return s0;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
964
|
function peg$parseindentedIdLink() {
|
|
1002
965
|
let s0, s1, s2, s3, s4;
|
|
1003
966
|
|
|
@@ -1006,11 +969,11 @@ function peg$parse(input, options) {
|
|
|
1006
969
|
if (s1 !== peg$FAILED) {
|
|
1007
970
|
s2 = peg$parse__();
|
|
1008
971
|
if (input.charCodeAt(peg$currPos) === 58) {
|
|
1009
|
-
s3 = peg$
|
|
972
|
+
s3 = peg$c2;
|
|
1010
973
|
peg$currPos++;
|
|
1011
974
|
} else {
|
|
1012
975
|
s3 = peg$FAILED;
|
|
1013
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
976
|
+
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
|
1014
977
|
}
|
|
1015
978
|
if (s3 !== peg$FAILED) {
|
|
1016
979
|
s4 = peg$parseeol();
|
|
@@ -1552,6 +1515,9 @@ function peg$parse(input, options) {
|
|
|
1552
1515
|
}
|
|
1553
1516
|
if (s2 === peg$FAILED) {
|
|
1554
1517
|
s2 = peg$parseeof();
|
|
1518
|
+
if (s2 === peg$FAILED) {
|
|
1519
|
+
s2 = peg$parsenestedGroupEnd();
|
|
1520
|
+
}
|
|
1555
1521
|
}
|
|
1556
1522
|
if (s2 !== peg$FAILED) {
|
|
1557
1523
|
s1 = [s1, s2];
|
|
@@ -1564,6 +1530,49 @@ function peg$parse(input, options) {
|
|
|
1564
1530
|
return s0;
|
|
1565
1531
|
}
|
|
1566
1532
|
|
|
1533
|
+
function peg$parsenestedGroupEnd() {
|
|
1534
|
+
let s0, s1, s2, s3;
|
|
1535
|
+
|
|
1536
|
+
s0 = peg$currPos;
|
|
1537
|
+
peg$savedPos = peg$currPos;
|
|
1538
|
+
s1 = peg$f45();
|
|
1539
|
+
if (s1) {
|
|
1540
|
+
s1 = undefined;
|
|
1541
|
+
} else {
|
|
1542
|
+
s1 = peg$FAILED;
|
|
1543
|
+
}
|
|
1544
|
+
if (s1 !== peg$FAILED) {
|
|
1545
|
+
s2 = peg$currPos;
|
|
1546
|
+
peg$silentFails++;
|
|
1547
|
+
if (input.charCodeAt(peg$currPos) === 41) {
|
|
1548
|
+
s3 = peg$c1;
|
|
1549
|
+
peg$currPos++;
|
|
1550
|
+
} else {
|
|
1551
|
+
s3 = peg$FAILED;
|
|
1552
|
+
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
|
1553
|
+
}
|
|
1554
|
+
peg$silentFails--;
|
|
1555
|
+
if (s3 !== peg$FAILED) {
|
|
1556
|
+
peg$currPos = s2;
|
|
1557
|
+
s2 = undefined;
|
|
1558
|
+
} else {
|
|
1559
|
+
s2 = peg$FAILED;
|
|
1560
|
+
}
|
|
1561
|
+
if (s2 !== peg$FAILED) {
|
|
1562
|
+
s1 = [s1, s2];
|
|
1563
|
+
s0 = s1;
|
|
1564
|
+
} else {
|
|
1565
|
+
peg$currPos = s0;
|
|
1566
|
+
s0 = peg$FAILED;
|
|
1567
|
+
}
|
|
1568
|
+
} else {
|
|
1569
|
+
peg$currPos = s0;
|
|
1570
|
+
s0 = peg$FAILED;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
return s0;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1567
1576
|
function peg$parseeof() {
|
|
1568
1577
|
let s0, s1;
|
|
1569
1578
|
|
|
@@ -1656,6 +1665,37 @@ function peg$parse(input, options) {
|
|
|
1656
1665
|
|
|
1657
1666
|
let indentationStack = [0];
|
|
1658
1667
|
let baseIndentation = null;
|
|
1668
|
+
// Saved indentation contexts of the enclosing scopes. Every parenthesised
|
|
1669
|
+
// group opens a nested context that starts fresh at indentation level zero,
|
|
1670
|
+
// so line breaks and indentation mean the same thing at every depth.
|
|
1671
|
+
let contextStack = [];
|
|
1672
|
+
|
|
1673
|
+
function resetState() {
|
|
1674
|
+
indentationStack = [0];
|
|
1675
|
+
baseIndentation = null;
|
|
1676
|
+
contextStack = [];
|
|
1677
|
+
return true;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
function enterNestedContext() {
|
|
1681
|
+
contextStack.push({ indentationStack, baseIndentation });
|
|
1682
|
+
indentationStack = [0];
|
|
1683
|
+
baseIndentation = null;
|
|
1684
|
+
return true;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
function exitNestedContext() {
|
|
1688
|
+
const saved = contextStack.pop();
|
|
1689
|
+
if (saved) {
|
|
1690
|
+
indentationStack = saved.indentationStack;
|
|
1691
|
+
baseIndentation = saved.baseIndentation;
|
|
1692
|
+
}
|
|
1693
|
+
return true;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
function isInsideNestedContext() {
|
|
1697
|
+
return contextStack.length > 0;
|
|
1698
|
+
}
|
|
1659
1699
|
|
|
1660
1700
|
function setBaseIndentation(spaces) {
|
|
1661
1701
|
if (baseIndentation === null) {
|
|
@@ -1690,8 +1730,36 @@ function peg$parse(input, options) {
|
|
|
1690
1730
|
return indentationStack[indentationStack.length - 1];
|
|
1691
1731
|
}
|
|
1692
1732
|
|
|
1733
|
+
// A body written between an even run of delimiters is substantive when it
|
|
1734
|
+
// holds at least one visible character and does not straddle a parenthesis.
|
|
1735
|
+
// An even run can always be read as delimiter pairs enclosing nothing, so the
|
|
1736
|
+
// n-quote reading is only taken when it carries something the pairs cannot.
|
|
1737
|
+
function isSubstantiveBody(content) {
|
|
1738
|
+
let depth = 0;
|
|
1739
|
+
let hasVisible = false;
|
|
1740
|
+
|
|
1741
|
+
for (const c of content) {
|
|
1742
|
+
if (c === '(') {
|
|
1743
|
+
depth++;
|
|
1744
|
+
} else if (c === ')') {
|
|
1745
|
+
depth--;
|
|
1746
|
+
if (depth < 0) {
|
|
1747
|
+
return false;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (!/\s/.test(c)) {
|
|
1751
|
+
hasVisible = true;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
return hasVisible && depth === 0;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1693
1758
|
// Universal procedural parser for N-quote strings (any N >= 1)
|
|
1694
1759
|
// Parses from the given position in the input string
|
|
1760
|
+
// A run of an even number of delimiters that does not open a reference with a
|
|
1761
|
+
// substantive body is the empty reference: the shortest reading, a bare
|
|
1762
|
+
// delimiter pair enclosing nothing, wins over a longer n-quote delimiter.
|
|
1695
1763
|
// Returns { value, length } or null
|
|
1696
1764
|
function parseQuotedStringAt(inputStr, startPos, quoteChar) {
|
|
1697
1765
|
if (startPos >= inputStr.length || inputStr[startPos] !== quoteChar) {
|
|
@@ -1706,6 +1774,9 @@ function peg$parse(input, options) {
|
|
|
1706
1774
|
pos++;
|
|
1707
1775
|
}
|
|
1708
1776
|
|
|
1777
|
+
const isEvenRun = quoteCount % 2 === 0;
|
|
1778
|
+
const emptyReference = isEvenRun ? { value: '', length: quoteCount } : null;
|
|
1779
|
+
|
|
1709
1780
|
const closeSeq = quoteChar.repeat(quoteCount);
|
|
1710
1781
|
const escapeSeq = quoteChar.repeat(quoteCount * 2);
|
|
1711
1782
|
|
|
@@ -1724,6 +1795,9 @@ function peg$parse(input, options) {
|
|
|
1724
1795
|
const afterClose = pos + quoteCount;
|
|
1725
1796
|
if (afterClose >= inputStr.length || inputStr[afterClose] !== quoteChar) {
|
|
1726
1797
|
// Found valid closing
|
|
1798
|
+
if (isEvenRun && !isSubstantiveBody(content)) {
|
|
1799
|
+
return emptyReference;
|
|
1800
|
+
}
|
|
1727
1801
|
return {
|
|
1728
1802
|
value: content,
|
|
1729
1803
|
length: afterClose - startPos
|
|
@@ -1736,7 +1810,7 @@ function peg$parse(input, options) {
|
|
|
1736
1810
|
pos++;
|
|
1737
1811
|
}
|
|
1738
1812
|
|
|
1739
|
-
return
|
|
1813
|
+
return emptyReference; // No valid closing found
|
|
1740
1814
|
}
|
|
1741
1815
|
|
|
1742
1816
|
// Global state for passing parsed values between predicate and action
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Conformance tests for the empty reference (issue #288).
|
|
2
|
+
//
|
|
3
|
+
// A bare delimiter pair is the empty reference. The three delimiters `"`, `'`
|
|
4
|
+
// and ` (backtick) behave identically, and every longer n-quote run keeps the
|
|
5
|
+
// meaning it already had. The table below is shared with the Rust, Python, C#,
|
|
6
|
+
// Go, Java and PHP suites, so a document written by one implementation reads
|
|
7
|
+
// the same in all of them.
|
|
8
|
+
|
|
9
|
+
import { test, expect } from 'bun:test';
|
|
10
|
+
import { Parser } from '../src/Parser.js';
|
|
11
|
+
import { formatLinks } from '../src/Link.js';
|
|
12
|
+
|
|
13
|
+
const parser = new Parser();
|
|
14
|
+
|
|
15
|
+
// Render a parsed node unambiguously: every reference is wrapped in angle
|
|
16
|
+
// brackets so an empty one is visible as `<>`.
|
|
17
|
+
function render(node) {
|
|
18
|
+
if (!node.values || node.values.length === 0) {
|
|
19
|
+
return `<${node.id ?? ''}>`;
|
|
20
|
+
}
|
|
21
|
+
const head =
|
|
22
|
+
node.id === null || node.id === undefined ? '' : `<${node.id}>: `;
|
|
23
|
+
return `(${head}${node.values.map(render).join(' ')})`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parsesAs(input) {
|
|
27
|
+
return parser.parse(input).map(render).join('\n');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('TestBareDelimiterPairIsTheEmptyReference', () => {
|
|
31
|
+
expect(parsesAs('(a "" b)')).toBe('(<a> <> <b>)');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('TestEveryDelimiterStyleYieldsTheSameEmptyReference', () => {
|
|
35
|
+
expect(parsesAs('(a "" b)')).toBe('(<a> <> <b>)');
|
|
36
|
+
expect(parsesAs("(a '' b)")).toBe('(<a> <> <b>)');
|
|
37
|
+
expect(parsesAs('(a `` b)')).toBe('(<a> <> <b>)');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('TestAdjacentEmptyReferencesStaySeparate', () => {
|
|
41
|
+
expect(parsesAs('(a "" "" b)')).toBe('(<a> <> <> <b>)');
|
|
42
|
+
expect(parsesAs("(a '' '' b)")).toBe('(<a> <> <> <b>)');
|
|
43
|
+
expect(parsesAs('(a `` `` b)')).toBe('(<a> <> <> <b>)');
|
|
44
|
+
expect(parsesAs('(a "" "" b)')).toBe('(<a> <> <> <b>)');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('TestNestedEmptyReferencesParse', () => {
|
|
48
|
+
expect(parsesAs('("" ("" 1))')).toBe('(<> (<> <1>))');
|
|
49
|
+
expect(parsesAs('("" (\'\' 1))')).toBe('(<> (<> <1>))');
|
|
50
|
+
expect(parsesAs('("x" ("" 1))')).toBe('(<x> (<> <1>))');
|
|
51
|
+
expect(parsesAs('("" ("x" 1))')).toBe('(<> (<x> <1>))');
|
|
52
|
+
expect(parsesAs('("" x ("" 1))')).toBe('(<> <x> (<> <1>))');
|
|
53
|
+
expect(parsesAs('("" 1 ("" 1))')).toBe('(<> <1> (<> <1>))');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('TestEmptyReferenceIsValidAsAnId', () => {
|
|
57
|
+
expect(parsesAs('("": 1)')).toBe('(<>: <1>)');
|
|
58
|
+
expect(parsesAs('(o: ("" (o: ("" 1))))')).toBe('(<o>: (<> (<o>: (<> <1>))))');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('TestNQuoteDelimitedBodiesAreUnchanged', () => {
|
|
62
|
+
// A run that encloses a substantive body keeps its n-quote meaning.
|
|
63
|
+
expect(parsesAs('(a ""x"" b)')).toBe('(<a> <x> <b>)');
|
|
64
|
+
expect(parsesAs('(x "" " "")')).toBe('(<x> < " >)');
|
|
65
|
+
expect(parsesAs("(x ' \" ')")).toBe('(<x> < " >)');
|
|
66
|
+
// An n-quote-delimited empty is still empty.
|
|
67
|
+
expect(parsesAs('(a """" b)')).toBe('(<a> <> <b>)');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('TestSingleSpaceStillReadsAsASpace', () => {
|
|
71
|
+
expect(parsesAs('(a " " b)')).toBe('(<a> < > <b>)');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('TestEmptyReferenceSurvivesARoundTrip', () => {
|
|
75
|
+
const inputs = [
|
|
76
|
+
'(a "" b)',
|
|
77
|
+
'(a "" "" b)',
|
|
78
|
+
'("" ("" 1))',
|
|
79
|
+
'("": 1)',
|
|
80
|
+
'(o: ("" (o: ("" 1))))',
|
|
81
|
+
];
|
|
82
|
+
for (const input of inputs) {
|
|
83
|
+
const links = parser.parse(input);
|
|
84
|
+
const formatted = formatLinks(links);
|
|
85
|
+
expect(parser.parse(formatted)).toEqual(links);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('TestEmptyReferenceIsWrittenAsADelimiterPair', () => {
|
|
90
|
+
expect(formatLinks(parser.parse('(a "" b)'))).toBe('(a "" b)');
|
|
91
|
+
});
|
package/tests/Link.test.js
CHANGED
|
@@ -38,8 +38,8 @@ test('Link escapeReference with special characters', () => {
|
|
|
38
38
|
expect(Link.escapeReference('has:colon')).toBe("'has:colon'");
|
|
39
39
|
expect(Link.escapeReference('has space')).toBe("'has space'");
|
|
40
40
|
expect(Link.escapeReference('has(paren)')).toBe("'has(paren)'");
|
|
41
|
-
expect(Link.escapeReference('has"quote')).toBe(
|
|
42
|
-
expect(Link.escapeReference("has'quote")).toBe(
|
|
41
|
+
expect(Link.escapeReference('has"quote')).toBe("'has\"quote'");
|
|
42
|
+
expect(Link.escapeReference("has'quote")).toBe('"has\'quote"');
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
test('Link simplify', () => {
|
|
@@ -18,16 +18,18 @@ as another reference'
|
|
|
18
18
|
expect(result.length).toBeGreaterThan(0);
|
|
19
19
|
expect(result.length).toBe(1);
|
|
20
20
|
|
|
21
|
+
// The two references sit on separate lines inside the parentheses, so the
|
|
22
|
+
// nested context turns each of them into its own link.
|
|
21
23
|
const link = result[0];
|
|
22
24
|
expect(link.id).toBe(null);
|
|
23
25
|
expect(link.values).toBeTruthy();
|
|
24
26
|
expect(link.values.length).toBe(2);
|
|
25
27
|
|
|
26
|
-
expect(link.values[0].id).toBe(`long
|
|
28
|
+
expect(link.values[0].values[0].id).toBe(`long
|
|
27
29
|
string literal representing
|
|
28
30
|
the reference`);
|
|
29
31
|
|
|
30
|
-
expect(link.values[1].id).toBe(`another
|
|
32
|
+
expect(link.values[1].values[0].id).toBe(`another
|
|
31
33
|
long string literal
|
|
32
34
|
as another reference`);
|
|
33
35
|
});
|