1
2
3
4
5 package modfile
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "os"
12 "strconv"
13 "strings"
14 "unicode"
15 "unicode/utf8"
16 )
17
18
19
20 type Position struct {
21 Line int
22 LineRune int
23 Byte int
24 }
25
26
27 func (p Position) add(s string) Position {
28 p.Byte += len(s)
29 if n := strings.Count(s, "\n"); n > 0 {
30 p.Line += n
31 s = s[strings.LastIndex(s, "\n")+1:]
32 p.LineRune = 1
33 }
34 p.LineRune += utf8.RuneCountInString(s)
35 return p
36 }
37
38
39 type Expr interface {
40
41
42 Span() (start, end Position)
43
44
45
46
47 Comment() *Comments
48 }
49
50
51 type Comment struct {
52 Start Position
53 Token string
54 Suffix bool
55 }
56
57
58 type Comments struct {
59 Before []Comment
60 Suffix []Comment
61
62
63
64 After []Comment
65 }
66
67
68
69
70
71 func (c *Comments) Comment() *Comments {
72 return c
73 }
74
75
76 type FileSyntax struct {
77 Name string
78 Comments
79 Stmt []Expr
80 }
81
82 func (x *FileSyntax) Span() (start, end Position) {
83 if len(x.Stmt) == 0 {
84 return
85 }
86 start, _ = x.Stmt[0].Span()
87 _, end = x.Stmt[len(x.Stmt)-1].Span()
88 return start, end
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
105 if hint == nil {
106
107 Loop:
108 for i := len(x.Stmt) - 1; i >= 0; i-- {
109 stmt := x.Stmt[i]
110 switch stmt := stmt.(type) {
111 case *Line:
112 if stmt.Token != nil && stmt.Token[0] == tokens[0] {
113 hint = stmt
114 break Loop
115 }
116 case *LineBlock:
117 if stmt.Token[0] == tokens[0] {
118 hint = stmt
119 break Loop
120 }
121 }
122 }
123 }
124
125 newLineAfter := func(i int) *Line {
126 new := &Line{Token: tokens}
127 if i == len(x.Stmt) {
128 x.Stmt = append(x.Stmt, new)
129 } else {
130 x.Stmt = append(x.Stmt, nil)
131 copy(x.Stmt[i+2:], x.Stmt[i+1:])
132 x.Stmt[i+1] = new
133 }
134 return new
135 }
136
137 if hint != nil {
138 for i, stmt := range x.Stmt {
139 switch stmt := stmt.(type) {
140 case *Line:
141 if stmt == hint {
142 if stmt.Token == nil || stmt.Token[0] != tokens[0] {
143 return newLineAfter(i)
144 }
145
146
147 stmt.InBlock = true
148 block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}}
149 stmt.Token = stmt.Token[1:]
150 x.Stmt[i] = block
151 new := &Line{Token: tokens[1:], InBlock: true}
152 block.Line = append(block.Line, new)
153 return new
154 }
155
156 case *LineBlock:
157 if stmt == hint {
158 if stmt.Token[0] != tokens[0] {
159 return newLineAfter(i)
160 }
161
162 new := &Line{Token: tokens[1:], InBlock: true}
163 stmt.Line = append(stmt.Line, new)
164 return new
165 }
166
167 for j, line := range stmt.Line {
168 if line == hint {
169 if stmt.Token[0] != tokens[0] {
170 return newLineAfter(i)
171 }
172
173
174 stmt.Line = append(stmt.Line, nil)
175 copy(stmt.Line[j+2:], stmt.Line[j+1:])
176 new := &Line{Token: tokens[1:], InBlock: true}
177 stmt.Line[j+1] = new
178 return new
179 }
180 }
181 }
182 }
183 }
184
185 new := &Line{Token: tokens}
186 x.Stmt = append(x.Stmt, new)
187 return new
188 }
189
190 func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
191 if line.InBlock {
192 tokens = tokens[1:]
193 }
194 line.Token = tokens
195 }
196
197
198
199 func (line *Line) markRemoved() {
200 line.Token = nil
201 line.Comments.Suffix = nil
202 }
203
204
205
206
207
208
209 func (x *FileSyntax) Cleanup() {
210 w := 0
211 for _, stmt := range x.Stmt {
212 switch stmt := stmt.(type) {
213 case *Line:
214 if stmt.Token == nil {
215 continue
216 }
217 case *LineBlock:
218 ww := 0
219 for _, line := range stmt.Line {
220 if line.Token != nil {
221 stmt.Line[ww] = line
222 ww++
223 }
224 }
225 if ww == 0 {
226 continue
227 }
228 if ww == 1 && len(stmt.RParen.Comments.Before) == 0 {
229
230
231 *stmt.Line[0] = Line{
232 Comments: Comments{
233 Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
234 Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
235 After: commentsAdd(stmt.Line[0].After, stmt.After),
236 },
237 Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
238 }
239 x.Stmt[w] = stmt.Line[0]
240 w++
241 continue
242 }
243 stmt.Line = stmt.Line[:ww]
244 }
245 x.Stmt[w] = stmt
246 w++
247 }
248 x.Stmt = x.Stmt[:w]
249 }
250
251 func commentsAdd(x, y []Comment) []Comment {
252 return append(x[:len(x):len(x)], y...)
253 }
254
255 func stringsAdd(x, y []string) []string {
256 return append(x[:len(x):len(x)], y...)
257 }
258
259
260
261 type CommentBlock struct {
262 Comments
263 Start Position
264 }
265
266 func (x *CommentBlock) Span() (start, end Position) {
267 return x.Start, x.Start
268 }
269
270
271 type Line struct {
272 Comments
273 Start Position
274 Token []string
275 InBlock bool
276 End Position
277 }
278
279 func (x *Line) Span() (start, end Position) {
280 return x.Start, x.End
281 }
282
283
284
285
286
287
288
289 type LineBlock struct {
290 Comments
291 Start Position
292 LParen LParen
293 Token []string
294 Line []*Line
295 RParen RParen
296 }
297
298 func (x *LineBlock) Span() (start, end Position) {
299 return x.Start, x.RParen.Pos.add(")")
300 }
301
302
303
304 type LParen struct {
305 Comments
306 Pos Position
307 }
308
309 func (x *LParen) Span() (start, end Position) {
310 return x.Pos, x.Pos.add(")")
311 }
312
313
314
315 type RParen struct {
316 Comments
317 Pos Position
318 }
319
320 func (x *RParen) Span() (start, end Position) {
321 return x.Pos, x.Pos.add(")")
322 }
323
324
325 type input struct {
326
327 filename string
328 complete []byte
329 remaining []byte
330 tokenStart []byte
331 token token
332 pos Position
333 comments []Comment
334
335
336 file *FileSyntax
337 parseErrors ErrorList
338
339
340 pre []Expr
341 post []Expr
342 }
343
344 func newInput(filename string, data []byte) *input {
345 return &input{
346 filename: filename,
347 complete: data,
348 remaining: data,
349 pos: Position{Line: 1, LineRune: 1, Byte: 0},
350 }
351 }
352
353
354 func parse(file string, data []byte) (f *FileSyntax, err error) {
355
356
357
358
359 in := newInput(file, data)
360 defer func() {
361 if e := recover(); e != nil && e != &in.parseErrors {
362 in.parseErrors = append(in.parseErrors, Error{
363 Filename: in.filename,
364 Pos: in.pos,
365 Err: fmt.Errorf("internal error: %v", e),
366 })
367 }
368 if err == nil && len(in.parseErrors) > 0 {
369 err = in.parseErrors
370 }
371 }()
372
373
374
375 in.readToken()
376
377
378 in.parseFile()
379 if len(in.parseErrors) > 0 {
380 return nil, in.parseErrors
381 }
382 in.file.Name = in.filename
383
384
385 in.assignComments()
386
387 return in.file, nil
388 }
389
390
391
392 func (in *input) Error(s string) {
393 in.parseErrors = append(in.parseErrors, Error{
394 Filename: in.filename,
395 Pos: in.pos,
396 Err: errors.New(s),
397 })
398 panic(&in.parseErrors)
399 }
400
401
402 func (in *input) eof() bool {
403 return len(in.remaining) == 0
404 }
405
406
407 func (in *input) peekRune() int {
408 if len(in.remaining) == 0 {
409 return 0
410 }
411 r, _ := utf8.DecodeRune(in.remaining)
412 return int(r)
413 }
414
415
416 func (in *input) peekPrefix(prefix string) bool {
417
418
419 for i := 0; i < len(prefix); i++ {
420 if i >= len(in.remaining) || in.remaining[i] != prefix[i] {
421 return false
422 }
423 }
424 return true
425 }
426
427
428 func (in *input) readRune() int {
429 if len(in.remaining) == 0 {
430 in.Error("internal lexer error: readRune at EOF")
431 }
432 r, size := utf8.DecodeRune(in.remaining)
433 in.remaining = in.remaining[size:]
434 if r == '\n' {
435 in.pos.Line++
436 in.pos.LineRune = 1
437 } else {
438 in.pos.LineRune++
439 }
440 in.pos.Byte += size
441 return int(r)
442 }
443
444 type token struct {
445 kind tokenKind
446 pos Position
447 endPos Position
448 text string
449 }
450
451 type tokenKind int
452
453 const (
454 _EOF tokenKind = -(iota + 1)
455 _EOLCOMMENT
456 _IDENT
457 _STRING
458 _COMMENT
459
460
461 )
462
463 func (k tokenKind) isComment() bool {
464 return k == _COMMENT || k == _EOLCOMMENT
465 }
466
467
468 func (k tokenKind) isEOL() bool {
469 return k == _EOF || k == _EOLCOMMENT || k == '\n'
470 }
471
472
473
474
475 func (in *input) startToken() {
476 in.tokenStart = in.remaining
477 in.token.text = ""
478 in.token.pos = in.pos
479 }
480
481
482
483
484 func (in *input) endToken(kind tokenKind) {
485 in.token.kind = kind
486 text := string(in.tokenStart[:len(in.tokenStart)-len(in.remaining)])
487 if kind.isComment() {
488 if strings.HasSuffix(text, "\r\n") {
489 text = text[:len(text)-2]
490 } else {
491 text = strings.TrimSuffix(text, "\n")
492 }
493 }
494 in.token.text = text
495 in.token.endPos = in.pos
496 }
497
498
499 func (in *input) peek() tokenKind {
500 return in.token.kind
501 }
502
503
504 func (in *input) lex() token {
505 tok := in.token
506 in.readToken()
507 return tok
508 }
509
510
511 func (in *input) readToken() {
512
513 for !in.eof() {
514 c := in.peekRune()
515 if c == ' ' || c == '\t' || c == '\r' {
516 in.readRune()
517 continue
518 }
519
520
521 if in.peekPrefix("//") {
522 in.startToken()
523
524
525
526
527 i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n"))
528 suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0
529 in.readRune()
530 in.readRune()
531
532
533 for len(in.remaining) > 0 && in.readRune() != '\n' {
534 }
535
536
537
538
539 if !suffix {
540 in.endToken(_COMMENT)
541 return
542 }
543
544
545 in.endToken(_EOLCOMMENT)
546 in.comments = append(in.comments, Comment{in.token.pos, in.token.text, suffix})
547 return
548 }
549
550 if in.peekPrefix("/*") {
551 in.Error("mod files must use // comments (not /* */ comments)")
552 }
553
554
555 break
556 }
557
558
559 in.startToken()
560
561
562 if in.eof() {
563 in.endToken(_EOF)
564 return
565 }
566
567
568 switch c := in.peekRune(); c {
569 case '\n', '(', ')', '[', ']', '{', '}', ',':
570 in.readRune()
571 in.endToken(tokenKind(c))
572 return
573
574 case '"', '`':
575 quote := c
576 in.readRune()
577 for {
578 if in.eof() {
579 in.pos = in.token.pos
580 in.Error("unexpected EOF in string")
581 }
582 if in.peekRune() == '\n' {
583 in.Error("unexpected newline in string")
584 }
585 c := in.readRune()
586 if c == quote {
587 break
588 }
589 if c == '\\' && quote != '`' {
590 if in.eof() {
591 in.pos = in.token.pos
592 in.Error("unexpected EOF in string")
593 }
594 in.readRune()
595 }
596 }
597 in.endToken(_STRING)
598 return
599 }
600
601
602 if c := in.peekRune(); !isIdent(c) {
603 in.Error(fmt.Sprintf("unexpected input character %#q", c))
604 }
605
606
607 for isIdent(in.peekRune()) {
608 if in.peekPrefix("//") {
609 break
610 }
611 if in.peekPrefix("/*") {
612 in.Error("mod files must use // comments (not /* */ comments)")
613 }
614 in.readRune()
615 }
616 in.endToken(_IDENT)
617 }
618
619
620
621
622 func isIdent(c int) bool {
623 switch r := rune(c); r {
624 case ' ', '(', ')', '[', ']', '{', '}', ',':
625 return false
626 default:
627 return !unicode.IsSpace(r) && unicode.IsPrint(r)
628 }
629 }
630
631
632
633
634
635
636
637
638
639
640
641 func (in *input) order(x Expr) {
642 if x != nil {
643 in.pre = append(in.pre, x)
644 }
645 switch x := x.(type) {
646 default:
647 panic(fmt.Errorf("order: unexpected type %T", x))
648 case nil:
649
650 case *LParen, *RParen:
651
652 case *CommentBlock:
653
654 case *Line:
655
656 case *FileSyntax:
657 for _, stmt := range x.Stmt {
658 in.order(stmt)
659 }
660 case *LineBlock:
661 in.order(&x.LParen)
662 for _, l := range x.Line {
663 in.order(l)
664 }
665 in.order(&x.RParen)
666 }
667 if x != nil {
668 in.post = append(in.post, x)
669 }
670 }
671
672
673 func (in *input) assignComments() {
674 const debug = false
675
676
677 in.order(in.file)
678
679
680 var line, suffix []Comment
681 for _, com := range in.comments {
682 if com.Suffix {
683 suffix = append(suffix, com)
684 } else {
685 line = append(line, com)
686 }
687 }
688
689 if debug {
690 for _, c := range line {
691 fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
692 }
693 }
694
695
696 for _, x := range in.pre {
697 start, _ := x.Span()
698 if debug {
699 fmt.Fprintf(os.Stderr, "pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte)
700 }
701 xcom := x.Comment()
702 for len(line) > 0 && start.Byte >= line[0].Start.Byte {
703 if debug {
704 fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte)
705 }
706 xcom.Before = append(xcom.Before, line[0])
707 line = line[1:]
708 }
709 }
710
711
712 in.file.After = append(in.file.After, line...)
713
714 if debug {
715 for _, c := range suffix {
716 fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
717 }
718 }
719
720
721 for i := len(in.post) - 1; i >= 0; i-- {
722 x := in.post[i]
723
724 start, end := x.Span()
725 if debug {
726 fmt.Fprintf(os.Stderr, "post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte)
727 }
728
729
730
731 switch x.(type) {
732 case *FileSyntax:
733 continue
734 }
735
736
737
738
739
740
741
742
743 if start.Line != end.Line {
744 continue
745 }
746 xcom := x.Comment()
747 for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte {
748 if debug {
749 fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte)
750 }
751 xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1])
752 suffix = suffix[:len(suffix)-1]
753 }
754 }
755
756
757
758
759 for _, x := range in.post {
760 reverseComments(x.Comment().Suffix)
761 }
762
763
764 in.file.Before = append(in.file.Before, suffix...)
765 }
766
767
768 func reverseComments(list []Comment) {
769 for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
770 list[i], list[j] = list[j], list[i]
771 }
772 }
773
774 func (in *input) parseFile() {
775 in.file = new(FileSyntax)
776 var cb *CommentBlock
777 for {
778 switch in.peek() {
779 case '\n':
780 in.lex()
781 if cb != nil {
782 in.file.Stmt = append(in.file.Stmt, cb)
783 cb = nil
784 }
785 case _COMMENT:
786 tok := in.lex()
787 if cb == nil {
788 cb = &CommentBlock{Start: tok.pos}
789 }
790 com := cb.Comment()
791 com.Before = append(com.Before, Comment{Start: tok.pos, Token: tok.text})
792 case _EOF:
793 if cb != nil {
794 in.file.Stmt = append(in.file.Stmt, cb)
795 }
796 return
797 default:
798 in.parseStmt()
799 if cb != nil {
800 in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before
801 cb = nil
802 }
803 }
804 }
805 }
806
807 func (in *input) parseStmt() {
808 tok := in.lex()
809 start := tok.pos
810 end := tok.endPos
811 tokens := []string{tok.text}
812 for {
813 tok := in.lex()
814 switch {
815 case tok.kind.isEOL():
816 in.file.Stmt = append(in.file.Stmt, &Line{
817 Start: start,
818 Token: tokens,
819 End: end,
820 })
821 return
822
823 case tok.kind == '(':
824 if next := in.peek(); next.isEOL() {
825
826 in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, tokens, tok))
827 return
828 } else if next == ')' {
829 rparen := in.lex()
830 if in.peek().isEOL() {
831
832 in.lex()
833 in.file.Stmt = append(in.file.Stmt, &LineBlock{
834 Start: start,
835 Token: tokens,
836 LParen: LParen{Pos: tok.pos},
837 RParen: RParen{Pos: rparen.pos},
838 })
839 return
840 }
841
842 tokens = append(tokens, tok.text, rparen.text)
843 } else {
844
845 tokens = append(tokens, tok.text)
846 }
847
848 default:
849 tokens = append(tokens, tok.text)
850 end = tok.endPos
851 }
852 }
853 }
854
855 func (in *input) parseLineBlock(start Position, token []string, lparen token) *LineBlock {
856 x := &LineBlock{
857 Start: start,
858 Token: token,
859 LParen: LParen{Pos: lparen.pos},
860 }
861 var comments []Comment
862 for {
863 switch in.peek() {
864 case _EOLCOMMENT:
865
866 in.lex()
867 case '\n':
868
869 in.lex()
870 if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" {
871 comments = append(comments, Comment{})
872 }
873 case _COMMENT:
874 tok := in.lex()
875 comments = append(comments, Comment{Start: tok.pos, Token: tok.text})
876 case _EOF:
877 in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
878 case ')':
879 rparen := in.lex()
880 x.RParen.Before = comments
881 x.RParen.Pos = rparen.pos
882 if !in.peek().isEOL() {
883 in.Error("syntax error (expected newline after closing paren)")
884 }
885 in.lex()
886 return x
887 default:
888 l := in.parseLine()
889 x.Line = append(x.Line, l)
890 l.Comment().Before = comments
891 comments = nil
892 }
893 }
894 }
895
896 func (in *input) parseLine() *Line {
897 tok := in.lex()
898 if tok.kind.isEOL() {
899 in.Error("internal parse error: parseLine at end of line")
900 }
901 start := tok.pos
902 end := tok.endPos
903 tokens := []string{tok.text}
904 for {
905 tok := in.lex()
906 if tok.kind.isEOL() {
907 return &Line{
908 Start: start,
909 Token: tokens,
910 End: end,
911 InBlock: true,
912 }
913 }
914 tokens = append(tokens, tok.text)
915 end = tok.endPos
916 }
917 }
918
919 var (
920 slashSlash = []byte("//")
921 moduleStr = []byte("module")
922 )
923
924
925
926
927 func ModulePath(mod []byte) string {
928 for len(mod) > 0 {
929 line := mod
930 mod = nil
931 if i := bytes.IndexByte(line, '\n'); i >= 0 {
932 line, mod = line[:i], line[i+1:]
933 }
934 if i := bytes.Index(line, slashSlash); i >= 0 {
935 line = line[:i]
936 }
937 line = bytes.TrimSpace(line)
938 if !bytes.HasPrefix(line, moduleStr) {
939 continue
940 }
941 line = line[len(moduleStr):]
942 n := len(line)
943 line = bytes.TrimSpace(line)
944 if len(line) == n || len(line) == 0 {
945 continue
946 }
947
948 if line[0] == '"' || line[0] == '`' {
949 p, err := strconv.Unquote(string(line))
950 if err != nil {
951 return ""
952 }
953 return p
954 }
955
956 return string(line)
957 }
958 return ""
959 }
960
View as plain text