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