1
2
3
4
5 package comment
6
7 import (
8 "sort"
9 "strings"
10 "unicode"
11 "unicode/utf8"
12 )
13
14
15 type Doc struct {
16
17 Content []Block
18
19
20 Links []*LinkDef
21 }
22
23
24 type LinkDef struct {
25 Text string
26 URL string
27 Used bool
28 }
29
30
31
32 type Block interface {
33 block()
34 }
35
36
37 type Heading struct {
38 Text []Text
39 }
40
41 func (*Heading) block() {}
42
43
44
45
46
47 type List struct {
48
49 Items []*ListItem
50
51
52
53
54
55
56
57
58 ForceBlankBefore bool
59
60
61
62
63
64
65
66
67 ForceBlankBetween bool
68 }
69
70 func (*List) block() {}
71
72
73
74
75
76
77
78
79 func (l *List) BlankBefore() bool {
80 return l.ForceBlankBefore || l.BlankBetween()
81 }
82
83
84
85
86
87
88
89 func (l *List) BlankBetween() bool {
90 if l.ForceBlankBetween {
91 return true
92 }
93 for _, item := range l.Items {
94 if len(item.Content) != 1 {
95
96
97
98
99 return true
100 }
101 }
102 return false
103 }
104
105
106 type ListItem struct {
107
108
109 Number string
110
111
112
113
114 Content []Block
115 }
116
117
118 type Paragraph struct {
119 Text []Text
120 }
121
122 func (*Paragraph) block() {}
123
124
125 type Code struct {
126
127
128
129 Text string
130 }
131
132 func (*Code) block() {}
133
134
135
136 type Text interface {
137 text()
138 }
139
140
141 type Plain string
142
143 func (Plain) text() {}
144
145
146 type Italic string
147
148 func (Italic) text() {}
149
150
151 type Link struct {
152 Auto bool
153 Text []Text
154 URL string
155 }
156
157 func (*Link) text() {}
158
159
160 type DocLink struct {
161 Text []Text
162
163
164
165
166
167
168
169
170
171 ImportPath string
172 Recv string
173 Name string
174 }
175
176 func (*DocLink) text() {}
177
178
179
180
181 type Parser struct {
182
183
184
185
186
187
188 Words map[string]string
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 LookupPackage func(name string) (importPath string, ok bool)
210
211
212
213
214
215
216
217
218
219
220
221
222
223 LookupSym func(recv, name string) (ok bool)
224 }
225
226
227 type parseDoc struct {
228 *Parser
229 *Doc
230 links map[string]*LinkDef
231 lines []string
232 lookupSym func(recv, name string) bool
233 }
234
235
236
237
238
239
240
241
242
243
244
245
246 func (d *parseDoc) lookupPkg(pkg string) (importPath string, ok bool) {
247 if strings.Contains(pkg, "/") {
248 if validImportPath(pkg) {
249 return pkg, true
250 }
251 return "", false
252 }
253 if d.LookupPackage != nil {
254
255 if path, ok := d.LookupPackage(pkg); ok {
256 return path, true
257 }
258 }
259 return DefaultLookupPackage(pkg)
260 }
261
262 func isStdPkg(path string) bool {
263
264
265 i := sort.Search(len(stdPkgs), func(i int) bool { return stdPkgs[i] >= path })
266 return i < len(stdPkgs) && stdPkgs[i] == path
267 }
268
269
270
271
272
273
274
275
276
277 func DefaultLookupPackage(name string) (importPath string, ok bool) {
278 if isStdPkg(name) {
279 return name, true
280 }
281 return "", false
282 }
283
284
285
286 func (p *Parser) Parse(text string) *Doc {
287 lines := unindent(strings.Split(text, "\n"))
288 d := &parseDoc{
289 Parser: p,
290 Doc: new(Doc),
291 links: make(map[string]*LinkDef),
292 lines: lines,
293 lookupSym: func(recv, name string) bool { return false },
294 }
295 if p.LookupSym != nil {
296 d.lookupSym = p.LookupSym
297 }
298
299
300
301 var prev span
302 for _, s := range parseSpans(lines) {
303 var b Block
304 switch s.kind {
305 default:
306 panic("go/doc/comment: internal error: unknown span kind")
307 case spanList:
308 b = d.list(lines[s.start:s.end], prev.end < s.start)
309 case spanCode:
310 b = d.code(lines[s.start:s.end])
311 case spanOldHeading:
312 b = d.oldHeading(lines[s.start])
313 case spanHeading:
314 b = d.heading(lines[s.start])
315 case spanPara:
316 b = d.paragraph(lines[s.start:s.end])
317 }
318 if b != nil {
319 d.Content = append(d.Content, b)
320 }
321 prev = s
322 }
323
324
325 for _, b := range d.Content {
326 switch b := b.(type) {
327 case *Paragraph:
328 b.Text = d.parseLinkedText(string(b.Text[0].(Plain)))
329 case *List:
330 for _, i := range b.Items {
331 for _, c := range i.Content {
332 p := c.(*Paragraph)
333 p.Text = d.parseLinkedText(string(p.Text[0].(Plain)))
334 }
335 }
336 }
337 }
338
339 return d.Doc
340 }
341
342
343
344 type span struct {
345 start int
346 end int
347 kind spanKind
348 }
349
350
351 type spanKind int
352
353 const (
354 _ spanKind = iota
355 spanCode
356 spanHeading
357 spanList
358 spanOldHeading
359 spanPara
360 )
361
362 func parseSpans(lines []string) []span {
363 var spans []span
364
365
366
367
368
369
370
371
372 watchdog := 2 * len(lines)
373
374 i := 0
375 forceIndent := 0
376 Spans:
377 for {
378
379 for i < len(lines) && lines[i] == "" {
380 i++
381 }
382 if i >= len(lines) {
383 break
384 }
385 if watchdog--; watchdog < 0 {
386 panic("go/doc/comment: internal error: not making progress")
387 }
388
389 var kind spanKind
390 start := i
391 end := i
392 if i < forceIndent || indented(lines[i]) {
393
394
395
396
397
398
399 unindentedListOK := isList(lines[i]) && i < forceIndent
400 i++
401 for i < len(lines) && (lines[i] == "" || i < forceIndent || indented(lines[i]) || (unindentedListOK && isList(lines[i]))) {
402 if lines[i] == "" {
403 unindentedListOK = false
404 }
405 i++
406 }
407
408
409 end = i
410 for end > start && lines[end-1] == "" {
411 end--
412 }
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427 if end < len(lines) && strings.HasPrefix(lines[end], "}") {
428 end++
429 }
430
431 if isList(lines[start]) {
432 kind = spanList
433 } else {
434 kind = spanCode
435 }
436 } else {
437
438 i++
439 for i < len(lines) && lines[i] != "" && !indented(lines[i]) {
440 i++
441 }
442 end = i
443
444
445
446
447
448
449
450
451
452
453 if i < len(lines) && lines[i] != "" && !isList(lines[i]) {
454 switch {
455 case isList(lines[i-1]):
456
457
458
459
460 forceIndent = end
461 end--
462 for end > start && isList(lines[end-1]) {
463 end--
464 }
465
466 case strings.HasSuffix(lines[i-1], "{") || strings.HasSuffix(lines[i-1], `\`):
467
468
469
470
471 forceIndent = end
472 end--
473 }
474
475 if start == end && forceIndent > start {
476 i = start
477 continue Spans
478 }
479 }
480
481
482 if end-start == 1 && isHeading(lines[start]) {
483 kind = spanHeading
484 } else if end-start == 1 && isOldHeading(lines[start], lines, start) {
485 kind = spanOldHeading
486 } else {
487 kind = spanPara
488 }
489 }
490
491 spans = append(spans, span{start, end, kind})
492 i = end
493 }
494
495 return spans
496 }
497
498
499
500 func indented(line string) bool {
501 return line != "" && (line[0] == ' ' || line[0] == '\t')
502 }
503
504
505
506
507
508 func unindent(lines []string) []string {
509
510 for len(lines) > 0 && isBlank(lines[0]) {
511 lines = lines[1:]
512 }
513 for len(lines) > 0 && isBlank(lines[len(lines)-1]) {
514 lines = lines[:len(lines)-1]
515 }
516 if len(lines) == 0 {
517 return nil
518 }
519
520
521 prefix := leadingSpace(lines[0])
522 for _, line := range lines[1:] {
523 if !isBlank(line) {
524 prefix = commonPrefix(prefix, leadingSpace(line))
525 }
526 }
527
528 out := make([]string, len(lines))
529 for i, line := range lines {
530 line = strings.TrimPrefix(line, prefix)
531 if strings.TrimSpace(line) == "" {
532 line = ""
533 }
534 out[i] = line
535 }
536 for len(out) > 0 && out[0] == "" {
537 out = out[1:]
538 }
539 for len(out) > 0 && out[len(out)-1] == "" {
540 out = out[:len(out)-1]
541 }
542 return out
543 }
544
545
546 func isBlank(s string) bool {
547 return len(s) == 0 || (len(s) == 1 && s[0] == '\n')
548 }
549
550
551 func commonPrefix(a, b string) string {
552 i := 0
553 for i < len(a) && i < len(b) && a[i] == b[i] {
554 i++
555 }
556 return a[0:i]
557 }
558
559
560 func leadingSpace(s string) string {
561 i := 0
562 for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
563 i++
564 }
565 return s[:i]
566 }
567
568
569
570 func isOldHeading(line string, all []string, off int) bool {
571 if off <= 0 || all[off-1] != "" || off+2 >= len(all) || all[off+1] != "" || leadingSpace(all[off+2]) != "" {
572 return false
573 }
574
575 line = strings.TrimSpace(line)
576
577
578 r, _ := utf8.DecodeRuneInString(line)
579 if !unicode.IsLetter(r) || !unicode.IsUpper(r) {
580 return false
581 }
582
583
584 r, _ = utf8.DecodeLastRuneInString(line)
585 if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
586 return false
587 }
588
589
590 if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") {
591 return false
592 }
593
594
595 for b := line; ; {
596 var ok bool
597 if _, b, ok = strings.Cut(b, "'"); !ok {
598 break
599 }
600 if b != "s" && !strings.HasPrefix(b, "s ") {
601 return false
602 }
603 }
604
605
606 for b := line; ; {
607 var ok bool
608 if _, b, ok = strings.Cut(b, "."); !ok {
609 break
610 }
611 if b == "" || strings.HasPrefix(b, " ") {
612 return false
613 }
614 }
615
616 return true
617 }
618
619
620 func (d *parseDoc) oldHeading(line string) Block {
621 return &Heading{Text: []Text{Plain(strings.TrimSpace(line))}}
622 }
623
624
625 func isHeading(line string) bool {
626 return len(line) >= 2 &&
627 line[0] == '#' &&
628 (line[1] == ' ' || line[1] == '\t') &&
629 strings.TrimSpace(line) != "#"
630 }
631
632
633 func (d *parseDoc) heading(line string) Block {
634 return &Heading{Text: []Text{Plain(strings.TrimSpace(line[1:]))}}
635 }
636
637
638 func (d *parseDoc) code(lines []string) *Code {
639 body := unindent(lines)
640 body = append(body, "")
641 return &Code{Text: strings.Join(body, "\n")}
642 }
643
644
645
646 func (d *parseDoc) paragraph(lines []string) Block {
647
648 var defs []*LinkDef
649 for _, line := range lines {
650 def, ok := parseLink(line)
651 if !ok {
652 goto NoDefs
653 }
654 defs = append(defs, def)
655 }
656 for _, def := range defs {
657 d.Links = append(d.Links, def)
658 if d.links[def.Text] == nil {
659 d.links[def.Text] = def
660 }
661 }
662 return nil
663 NoDefs:
664
665 return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}}
666 }
667
668
669
670
671
672
673 func parseLink(line string) (*LinkDef, bool) {
674 if line == "" || line[0] != '[' {
675 return nil, false
676 }
677 i := strings.Index(line, "]:")
678 if i < 0 || i+3 >= len(line) || (line[i+2] != ' ' && line[i+2] != '\t') {
679 return nil, false
680 }
681
682 text := line[1:i]
683 url := strings.TrimSpace(line[i+3:])
684 j := strings.Index(url, "://")
685 if j < 0 || !isScheme(url[:j]) {
686 return nil, false
687 }
688
689
690
691
692
693 return &LinkDef{Text: text, URL: url}, true
694 }
695
696
697
698 func (d *parseDoc) list(lines []string, forceBlankBefore bool) *List {
699 num, _, _ := listMarker(lines[0])
700 var (
701 list *List = &List{ForceBlankBefore: forceBlankBefore}
702 item *ListItem
703 text []string
704 )
705 flush := func() {
706 if item != nil {
707 if para := d.paragraph(text); para != nil {
708 item.Content = append(item.Content, para)
709 }
710 }
711 text = nil
712 }
713
714 for _, line := range lines {
715 if n, after, ok := listMarker(line); ok && (n != "") == (num != "") {
716
717 flush()
718
719 item = &ListItem{Number: n}
720 list.Items = append(list.Items, item)
721 line = after
722 }
723 line = strings.TrimSpace(line)
724 if line == "" {
725 list.ForceBlankBetween = true
726 flush()
727 continue
728 }
729 text = append(text, strings.TrimSpace(line))
730 }
731 flush()
732 return list
733 }
734
735
736
737
738
739 func listMarker(line string) (num, rest string, ok bool) {
740 line = strings.TrimSpace(line)
741 if line == "" {
742 return "", "", false
743 }
744
745
746 if r, n := utf8.DecodeRuneInString(line); r == '•' || r == '*' || r == '+' || r == '-' {
747 num, rest = "", line[n:]
748 } else if '0' <= line[0] && line[0] <= '9' {
749 n := 1
750 for n < len(line) && '0' <= line[n] && line[n] <= '9' {
751 n++
752 }
753 if n >= len(line) || (line[n] != '.' && line[n] != ')') {
754 return "", "", false
755 }
756 num, rest = line[:n], line[n+1:]
757 } else {
758 return "", "", false
759 }
760
761 if !indented(rest) || strings.TrimSpace(rest) == "" {
762 return "", "", false
763 }
764
765 return num, rest, true
766 }
767
768
769
770
771 func isList(line string) bool {
772 _, _, ok := listMarker(line)
773 return ok
774 }
775
776
777
778
779
780
781
782
783
784
785
786 func (d *parseDoc) parseLinkedText(text string) []Text {
787 var out []Text
788 wrote := 0
789 flush := func(i int) {
790 if wrote < i {
791 out = d.parseText(out, text[wrote:i], true)
792 wrote = i
793 }
794 }
795
796 start := -1
797 var buf []byte
798 for i := 0; i < len(text); i++ {
799 c := text[i]
800 if c == '\n' || c == '\t' {
801 c = ' '
802 }
803 switch c {
804 case '[':
805 start = i
806 case ']':
807 if start >= 0 {
808 if def, ok := d.links[string(buf)]; ok {
809 def.Used = true
810 flush(start)
811 out = append(out, &Link{
812 Text: d.parseText(nil, text[start+1:i], false),
813 URL: def.URL,
814 })
815 wrote = i + 1
816 } else if link, ok := d.docLink(text[start+1:i], text[:start], text[i+1:]); ok {
817 flush(start)
818 link.Text = d.parseText(nil, text[start+1:i], false)
819 out = append(out, link)
820 wrote = i + 1
821 }
822 }
823 start = -1
824 buf = buf[:0]
825 }
826 if start >= 0 && i != start {
827 buf = append(buf, c)
828 }
829 }
830
831 flush(len(text))
832 return out
833 }
834
835
836
837
838
839
840
841 func (d *parseDoc) docLink(text, before, after string) (link *DocLink, ok bool) {
842 if before != "" {
843 r, _ := utf8.DecodeLastRuneInString(before)
844 if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' {
845 return nil, false
846 }
847 }
848 if after != "" {
849 r, _ := utf8.DecodeRuneInString(after)
850 if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' {
851 return nil, false
852 }
853 }
854 if strings.HasPrefix(text, "*") {
855 text = text[1:]
856 }
857 pkg, name, ok := splitDocName(text)
858 var recv string
859 if ok {
860 pkg, recv, _ = splitDocName(pkg)
861 }
862 if pkg != "" {
863 if pkg, ok = d.lookupPkg(pkg); !ok {
864 return nil, false
865 }
866 } else {
867 if ok = d.lookupSym(recv, name); !ok {
868 return nil, false
869 }
870 }
871 link = &DocLink{
872 ImportPath: pkg,
873 Recv: recv,
874 Name: name,
875 }
876 return link, true
877 }
878
879
880
881
882 func splitDocName(text string) (before, name string, foundDot bool) {
883 i := strings.LastIndex(text, ".")
884 name = text[i+1:]
885 if !isName(name) {
886 return text, "", false
887 }
888 if i >= 0 {
889 before = text[:i]
890 }
891 return before, name, true
892 }
893
894
895
896
897
898
899
900 func (d *parseDoc) parseText(out []Text, s string, autoLink bool) []Text {
901 var w strings.Builder
902 wrote := 0
903 writeUntil := func(i int) {
904 w.WriteString(s[wrote:i])
905 wrote = i
906 }
907 flush := func(i int) {
908 writeUntil(i)
909 if w.Len() > 0 {
910 out = append(out, Plain(w.String()))
911 w.Reset()
912 }
913 }
914 for i := 0; i < len(s); {
915 t := s[i:]
916 if autoLink {
917 if url, ok := autoURL(t); ok {
918 flush(i)
919
920
921
922
923
924 out = append(out, &Link{Auto: true, Text: []Text{Plain(url)}, URL: url})
925 i += len(url)
926 wrote = i
927 continue
928 }
929 if id, ok := ident(t); ok {
930 url, italics := d.Words[id]
931 if !italics {
932 i += len(id)
933 continue
934 }
935 flush(i)
936 if url == "" {
937 out = append(out, Italic(id))
938 } else {
939 out = append(out, &Link{Auto: true, Text: []Text{Italic(id)}, URL: url})
940 }
941 i += len(id)
942 wrote = i
943 continue
944 }
945 }
946 switch {
947 case strings.HasPrefix(t, "``"):
948 if len(t) >= 3 && t[2] == '`' {
949
950 i += 3
951 for i < len(t) && t[i] == '`' {
952 i++
953 }
954 break
955 }
956 writeUntil(i)
957 w.WriteRune('“')
958 i += 2
959 wrote = i
960 case strings.HasPrefix(t, "''"):
961 writeUntil(i)
962 w.WriteRune('”')
963 i += 2
964 wrote = i
965 default:
966 i++
967 }
968 }
969 flush(len(s))
970 return out
971 }
972
973
974
975
976
977
978 func autoURL(s string) (url string, ok bool) {
979
980
981
982 var i int
983 switch {
984 case len(s) < 7:
985 return "", false
986 case s[3] == ':':
987 i = 3
988 case s[4] == ':':
989 i = 4
990 case s[5] == ':':
991 i = 5
992 case s[6] == ':':
993 i = 6
994 default:
995 return "", false
996 }
997 if i+3 > len(s) || s[i:i+3] != "://" {
998 return "", false
999 }
1000
1001
1002 if !isScheme(s[:i]) {
1003 return "", false
1004 }
1005
1006
1007
1008 i += 3
1009 if i >= len(s) || !isHost(s[i]) || isPunct(s[i]) {
1010 return "", false
1011 }
1012 i++
1013 end := i
1014 for i < len(s) && isHost(s[i]) {
1015 if !isPunct(s[i]) {
1016 end = i + 1
1017 }
1018 i++
1019 }
1020 i = end
1021
1022
1023
1024
1025
1026
1027
1028
1029 stk := []byte{}
1030 end = i
1031 Path:
1032 for ; i < len(s); i++ {
1033 if isPunct(s[i]) {
1034 continue
1035 }
1036 if !isPath(s[i]) {
1037 break
1038 }
1039 switch s[i] {
1040 case '(':
1041 stk = append(stk, ')')
1042 case '{':
1043 stk = append(stk, '}')
1044 case '[':
1045 stk = append(stk, ']')
1046 case ')', '}', ']':
1047 if len(stk) == 0 || stk[len(stk)-1] != s[i] {
1048 break Path
1049 }
1050 stk = stk[:len(stk)-1]
1051 }
1052 if len(stk) == 0 {
1053 end = i + 1
1054 }
1055 }
1056
1057 return s[:end], true
1058 }
1059
1060
1061
1062
1063 func isScheme(s string) bool {
1064 switch s {
1065 case "file",
1066 "ftp",
1067 "gopher",
1068 "http",
1069 "https",
1070 "mailto",
1071 "nntp":
1072 return true
1073 }
1074 return false
1075 }
1076
1077
1078
1079 func isHost(c byte) bool {
1080
1081
1082
1083
1084 const mask = 0 |
1085 (1<<26-1)<<'A' |
1086 (1<<26-1)<<'a' |
1087 (1<<10-1)<<'0' |
1088 1<<'_' |
1089 1<<'@' |
1090 1<<'-' |
1091 1<<'.' |
1092 1<<'[' |
1093 1<<']' |
1094 1<<':'
1095
1096 return ((uint64(1)<<c)&(mask&(1<<64-1)) |
1097 (uint64(1)<<(c-64))&(mask>>64)) != 0
1098 }
1099
1100
1101
1102 func isPunct(c byte) bool {
1103
1104
1105
1106
1107 const mask = 0 |
1108 1<<'.' |
1109 1<<',' |
1110 1<<':' |
1111 1<<';' |
1112 1<<'?' |
1113 1<<'!'
1114
1115 return ((uint64(1)<<c)&(mask&(1<<64-1)) |
1116 (uint64(1)<<(c-64))&(mask>>64)) != 0
1117 }
1118
1119
1120 func isPath(c byte) bool {
1121
1122
1123
1124
1125 const mask = 0 |
1126 (1<<26-1)<<'A' |
1127 (1<<26-1)<<'a' |
1128 (1<<10-1)<<'0' |
1129 1<<'$' |
1130 1<<'\'' |
1131 1<<'(' |
1132 1<<')' |
1133 1<<'*' |
1134 1<<'+' |
1135 1<<'&' |
1136 1<<'#' |
1137 1<<'=' |
1138 1<<'@' |
1139 1<<'~' |
1140 1<<'_' |
1141 1<<'/' |
1142 1<<'-' |
1143 1<<'[' |
1144 1<<']' |
1145 1<<'{' |
1146 1<<'}' |
1147 1<<'%'
1148
1149 return ((uint64(1)<<c)&(mask&(1<<64-1)) |
1150 (uint64(1)<<(c-64))&(mask>>64)) != 0
1151 }
1152
1153
1154 func isName(s string) bool {
1155 t, ok := ident(s)
1156 if !ok || t != s {
1157 return false
1158 }
1159 r, _ := utf8.DecodeRuneInString(s)
1160 return unicode.IsUpper(r)
1161 }
1162
1163
1164
1165
1166
1167
1168 func ident(s string) (id string, ok bool) {
1169
1170 n := 0
1171 for n < len(s) {
1172 if c := s[n]; c < utf8.RuneSelf {
1173 if isIdentASCII(c) && (n > 0 || c < '0' || c > '9') {
1174 n++
1175 continue
1176 }
1177 break
1178 }
1179 r, nr := utf8.DecodeRuneInString(s[n:])
1180 if unicode.IsLetter(r) {
1181 n += nr
1182 continue
1183 }
1184 break
1185 }
1186 return s[:n], n > 0
1187 }
1188
1189
1190 func isIdentASCII(c byte) bool {
1191
1192
1193
1194
1195 const mask = 0 |
1196 (1<<26-1)<<'A' |
1197 (1<<26-1)<<'a' |
1198 (1<<10-1)<<'0' |
1199 1<<'_'
1200
1201 return ((uint64(1)<<c)&(mask&(1<<64-1)) |
1202 (uint64(1)<<(c-64))&(mask>>64)) != 0
1203 }
1204
1205
1206
1207 func validImportPath(path string) bool {
1208 if !utf8.ValidString(path) {
1209 return false
1210 }
1211 if path == "" {
1212 return false
1213 }
1214 if path[0] == '-' {
1215 return false
1216 }
1217 if strings.Contains(path, "//") {
1218 return false
1219 }
1220 if path[len(path)-1] == '/' {
1221 return false
1222 }
1223 elemStart := 0
1224 for i, r := range path {
1225 if r == '/' {
1226 if !validImportPathElem(path[elemStart:i]) {
1227 return false
1228 }
1229 elemStart = i + 1
1230 }
1231 }
1232 return validImportPathElem(path[elemStart:])
1233 }
1234
1235 func validImportPathElem(elem string) bool {
1236 if elem == "" || elem[0] == '.' || elem[len(elem)-1] == '.' {
1237 return false
1238 }
1239 for i := 0; i < len(elem); i++ {
1240 if !importPathOK(elem[i]) {
1241 return false
1242 }
1243 }
1244 return true
1245 }
1246
1247 func importPathOK(c byte) bool {
1248
1249
1250
1251
1252 const mask = 0 |
1253 (1<<26-1)<<'A' |
1254 (1<<26-1)<<'a' |
1255 (1<<10-1)<<'0' |
1256 1<<'-' |
1257 1<<'.' |
1258 1<<'~' |
1259 1<<'_' |
1260 1<<'+'
1261
1262 return ((uint64(1)<<c)&(mask&(1<<64-1)) |
1263 (uint64(1)<<(c-64))&(mask>>64)) != 0
1264 }
1265
View as plain text