1 package d2parser
2
3 import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "io"
8 "math/big"
9 "strconv"
10 "strings"
11 "unicode"
12 "unicode/utf8"
13
14 tunicode "golang.org/x/text/encoding/unicode"
15 "golang.org/x/text/transform"
16
17 "oss.terrastruct.com/d2/d2ast"
18 "oss.terrastruct.com/util-go/go2"
19 )
20
21 type ParseOptions struct {
22
23
24
25 UTF16Pos bool
26
27 ParseError *ParseError
28 }
29
30
31
32
33
34
35
36
37
38
39
40
41
42 func Parse(path string, r io.Reader, opts *ParseOptions) (*d2ast.Map, error) {
43 if opts == nil {
44 opts = &ParseOptions{
45 UTF16Pos: false,
46 }
47 }
48
49 p := &parser{
50 path: path,
51
52 utf16Pos: opts.UTF16Pos,
53 err: opts.ParseError,
54 }
55 br := bufio.NewReader(r)
56 p.reader = br
57
58 bom, err := br.Peek(2)
59 if err == nil {
60
61
62
63 if bom[0] == 0xFF && bom[1] == 0xFE {
64 p.utf16Pos = true
65
66 buf := make([]byte, br.Buffered())
67 io.ReadFull(br, buf)
68
69 mr := io.MultiReader(bytes.NewBuffer(buf), r)
70 tr := transform.NewReader(mr, tunicode.UTF16(tunicode.LittleEndian, tunicode.UseBOM).NewDecoder())
71 br.Reset(tr)
72 }
73 }
74
75 if p.err == nil {
76 p.err = &ParseError{}
77 }
78
79 m := p.parseMap(true)
80 if !p.err.Empty() {
81 return m, p.err
82 }
83 return m, nil
84 }
85
86 func ParseKey(key string) (*d2ast.KeyPath, error) {
87 p := &parser{
88 reader: strings.NewReader(key),
89 err: &ParseError{},
90 }
91
92 k := p.parseKey()
93 if !p.err.Empty() {
94 return nil, fmt.Errorf("failed to parse key %q: %w", key, p.err)
95 }
96 if k == nil {
97 return nil, fmt.Errorf("empty key: %q", key)
98 }
99 return k, nil
100 }
101
102 func ParseMapKey(mapKey string) (*d2ast.Key, error) {
103 p := &parser{
104 reader: strings.NewReader(mapKey),
105 err: &ParseError{},
106 }
107
108 mk := p.parseMapKey()
109 if !p.err.Empty() {
110 return nil, fmt.Errorf("failed to parse map key %q: %w", mapKey, p.err)
111 }
112 if mk == nil {
113 return nil, fmt.Errorf("empty map key: %q", mapKey)
114 }
115 return mk, nil
116 }
117
118 func ParseValue(value string) (d2ast.Value, error) {
119 p := &parser{
120 reader: strings.NewReader(value),
121 err: &ParseError{},
122 }
123
124 v := p.parseValue()
125 if !p.err.Empty() {
126 return nil, fmt.Errorf("failed to parse value %q: %w", value, p.err)
127 }
128 if v.Unbox() == nil {
129 return nil, fmt.Errorf("empty value: %q", value)
130 }
131 return v.Unbox(), nil
132 }
133
134
135
136
137
138
139
140
141
142 type parser struct {
143 path string
144 pos d2ast.Position
145 utf16Pos bool
146
147 reader io.RuneReader
148 readerPos d2ast.Position
149
150 readahead []rune
151 lookahead []rune
152 lookaheadPos d2ast.Position
153
154 ioerr bool
155 err *ParseError
156
157 inEdgeGroup bool
158
159 depth int
160 }
161
162
163 type ParseError struct {
164
165 ErrorsLookup map[d2ast.Error]struct{} `json:"-"`
166 Errors []d2ast.Error `json:"errs"`
167 }
168
169 func Errorf(n d2ast.Node, f string, v ...interface{}) error {
170 f = "%v: " + f
171 v = append([]interface{}{n.GetRange()}, v...)
172 return d2ast.Error{
173 Range: n.GetRange(),
174 Message: fmt.Sprintf(f, v...),
175 }
176 }
177
178 func (pe *ParseError) Empty() bool {
179 if pe == nil {
180 return true
181 }
182 return len(pe.Errors) == 0
183 }
184
185 func (pe *ParseError) Error() string {
186 var sb strings.Builder
187 for i, err := range pe.Errors {
188 if i > 0 {
189 sb.WriteByte('\n')
190 }
191 sb.WriteString(err.Error())
192 }
193 return sb.String()
194 }
195
196 func (p *parser) errorf(start d2ast.Position, end d2ast.Position, f string, v ...interface{}) {
197 r := d2ast.Range{
198 Path: p.path,
199 Start: start,
200 End: end,
201 }
202 f = "%v: " + f
203 v = append([]interface{}{r}, v...)
204 p.err.Errors = append(p.err.Errors, d2ast.Error{
205 Range: r,
206 Message: fmt.Sprintf(f, v...),
207 })
208 }
209
210
211 func (p *parser) _readRune() (r rune, eof bool) {
212 if len(p.readahead) > 0 {
213 r = p.readahead[0]
214 p.readahead = append(p.readahead[:0], p.readahead[1:]...)
215 return r, false
216 }
217
218 if p.ioerr {
219 p.rewind()
220 return 0, true
221 }
222
223 p.readerPos = p.lookaheadPos
224
225 r, _, err := p.reader.ReadRune()
226 if err != nil {
227 p.ioerr = true
228 if err != io.EOF {
229 p.err.Errors = append(p.err.Errors, d2ast.Error{
230 Range: d2ast.Range{
231 Path: p.path,
232 Start: p.readerPos,
233 End: p.readerPos,
234 },
235 Message: fmt.Sprintf("io error: %v", err),
236 })
237 }
238 p.rewind()
239 return 0, true
240 }
241 return r, false
242 }
243
244 func (p *parser) read() (r rune, eof bool) {
245 r, eof = p._readRune()
246 if eof {
247 return 0, true
248 }
249 p.pos = p.pos.Advance(r, p.utf16Pos)
250 p.lookaheadPos = p.pos
251 return r, false
252 }
253
254 func (p *parser) replay(r rune) {
255 p.pos = p.pos.Subtract(r, p.utf16Pos)
256
257
258
259 newcap := len(p.lookahead) + 1
260 if newcap > cap(p.lookahead) {
261 lookahead2 := make([]rune, newcap)
262 copy(lookahead2[1:], p.lookahead)
263 p.lookahead = lookahead2
264 } else {
265 p.lookahead = p.lookahead[:newcap]
266 copy(p.lookahead[1:], p.lookahead)
267 }
268 p.lookahead[0] = r
269
270 p.rewind()
271 }
272
273
274
275 func (p *parser) peek() (r rune, eof bool) {
276 r, eof = p._readRune()
277 if eof {
278 return 0, true
279 }
280
281 p.lookahead = append(p.lookahead, r)
282 p.lookaheadPos = p.lookaheadPos.Advance(r, p.utf16Pos)
283 return r, false
284 }
285
286
287 func (p *parser) peekn(n int) (s string, eof bool) {
288 var sb strings.Builder
289 for i := 0; i < n; i++ {
290 r, eof := p.peek()
291 if eof {
292 return sb.String(), true
293 }
294 sb.WriteRune(r)
295 }
296 return sb.String(), false
297 }
298
299 func (p *parser) readNotSpace() (r rune, eof bool) {
300 for {
301 r, eof = p.read()
302 if eof {
303 return 0, true
304 }
305 if unicode.IsSpace(r) {
306 continue
307 }
308 return r, false
309 }
310 }
311
312
313
314
315
316
317
318
319
320
321 func (p *parser) peekNotSpace() (r rune, newlines int, eof bool) {
322 for {
323 r, eof = p.peek()
324 if eof {
325 return 0, 0, true
326 }
327 if unicode.IsSpace(r) {
328 if r == '\n' {
329 newlines++
330 }
331 continue
332 }
333 return r, newlines, false
334 }
335 }
336
337
338 func (p *parser) commit() {
339 p.pos = p.lookaheadPos
340 p.lookahead = p.lookahead[:0]
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363 func (p *parser) rewind() {
364 if len(p.lookahead) == 0 {
365 return
366 }
367
368
369
370 newcap := len(p.lookahead) + len(p.readahead)
371 if cap(p.readahead) < newcap {
372 readahead2 := make([]rune, newcap)
373 copy(readahead2[len(p.lookahead):], p.readahead)
374 p.readahead = readahead2
375 } else {
376 p.readahead = p.readahead[:newcap]
377 copy(p.readahead[len(p.lookahead):], p.readahead)
378 }
379 copy(p.readahead, p.lookahead)
380
381 p.lookahead = p.lookahead[:0]
382 p.lookaheadPos = p.pos
383 }
384
385
386 func (p *parser) parseMap(isFileMap bool) *d2ast.Map {
387 m := &d2ast.Map{
388 Range: d2ast.Range{
389 Path: p.path,
390 Start: p.pos,
391 },
392 }
393 defer m.Range.End.From(&p.pos)
394
395 if !isFileMap {
396 m.Range.Start = m.Range.Start.Subtract('{', p.utf16Pos)
397 p.depth++
398 defer dec(&p.depth)
399 }
400
401 for {
402 r, eof := p.readNotSpace()
403 if eof {
404 if !isFileMap {
405 p.errorf(m.Range.Start, p.readerPos, "maps must be terminated with }")
406 }
407 return m
408 }
409
410 switch r {
411 case ';':
412 continue
413 case '}':
414 if isFileMap {
415 p.errorf(p.pos.Subtract(r, p.utf16Pos), p.pos, "unexpected map termination character } in file map")
416 continue
417 }
418 return m
419 }
420
421 n := p.parseMapNode(r)
422 if n.Unbox() != nil {
423 m.Nodes = append(m.Nodes, n)
424
425
426
427 }
428
429 if n.BlockComment != nil {
430
431 continue
432 }
433
434 after := p.pos
435 for {
436 r, newlines, eof := p.peekNotSpace()
437 if eof || newlines != 0 || r == ';' || r == '}' || r == '#' {
438 p.rewind()
439 break
440 }
441 p.commit()
442 }
443
444
445
446
447
448 if after != p.pos {
449 if n.Unbox() != nil {
450 if n.MapKey != nil && n.MapKey.Value.Unbox() != nil {
451 ps := ""
452 if _, ok := n.MapKey.Value.Unbox().(*d2ast.BlockString); ok {
453 ps = ". See https://d2lang.com/tour/text#advanced-block-strings."
454 }
455 p.errorf(after, p.pos, "unexpected text after %v%s", n.MapKey.Value.Unbox().Type(), ps)
456 } else {
457 p.errorf(after, p.pos, "unexpected text after %v", n.Unbox().Type())
458 }
459 } else {
460 p.errorf(after, p.pos, "invalid text beginning unquoted key")
461 }
462 }
463 }
464 }
465
466 func (p *parser) parseMapNode(r rune) d2ast.MapNodeBox {
467 var box d2ast.MapNodeBox
468
469 switch r {
470 case '#':
471 box.Comment = p.parseComment()
472 return box
473 case '"':
474 s, eof := p.peekn(2)
475 if eof {
476 break
477 }
478 if s != `""` {
479 p.rewind()
480 break
481 }
482 p.commit()
483 box.BlockComment = p.parseBlockComment()
484 return box
485 case '.':
486 s, eof := p.peekn(2)
487 if eof {
488 break
489 }
490 if s != ".." {
491 p.rewind()
492 break
493 }
494 r, eof := p.peek()
495 if eof {
496 break
497 }
498 if r == '$' {
499 p.commit()
500 box.Substitution = p.parseSubstitution(true)
501 return box
502 }
503 if r == '@' {
504 p.commit()
505 box.Import = p.parseImport(true)
506 return box
507 }
508 p.rewind()
509 break
510 }
511
512 p.replay(r)
513 box.MapKey = p.parseMapKey()
514 return box
515 }
516
517 func (p *parser) parseComment() *d2ast.Comment {
518 c := &d2ast.Comment{
519 Range: d2ast.Range{
520 Path: p.path,
521 Start: p.pos.Subtract('#', p.utf16Pos),
522 },
523 }
524 defer c.Range.End.From(&p.pos)
525
526 var sb strings.Builder
527 defer func() {
528 c.Value = sb.String()
529 }()
530 p.parseCommentLine(c, &sb)
531
532 for {
533 r, newlines, eof := p.peekNotSpace()
534 if eof {
535 return c
536 }
537 if r != '#' || newlines >= 2 {
538 p.rewind()
539 return c
540 }
541 p.commit()
542
543 if newlines == 1 {
544 sb.WriteByte('\n')
545 }
546
547 p.parseCommentLine(c, &sb)
548 }
549 }
550
551 func (p *parser) parseCommentLine(c *d2ast.Comment, sb *strings.Builder) {
552 firstRune := true
553 for {
554 r, eof := p.peek()
555 if eof {
556 return
557 }
558 if r == '\n' {
559 p.rewind()
560 return
561 }
562 p.commit()
563
564 if firstRune {
565 firstRune = false
566 if r == ' ' {
567 continue
568 }
569 }
570 sb.WriteRune(r)
571 }
572 }
573
574 func (p *parser) parseBlockComment() *d2ast.BlockComment {
575 bc := &d2ast.BlockComment{
576 Range: d2ast.Range{
577 Path: p.path,
578 Start: p.pos.SubtractString(`"""`, p.utf16Pos),
579 },
580 }
581 defer bc.Range.End.From(&p.pos)
582
583 p.depth++
584 defer dec(&p.depth)
585
586 var sb strings.Builder
587 defer func() {
588 bc.Value = trimSpaceAfterLastNewline(sb.String())
589 bc.Value = trimCommonIndent(bc.Value)
590 }()
591
592 for {
593 r, eof := p.peek()
594 if eof {
595 p.errorf(bc.Range.Start, p.readerPos, `block comments must be terminated with """`)
596 return bc
597 }
598
599 if !unicode.IsSpace(r) {
600 p.rewind()
601 break
602 }
603 p.commit()
604 if r == '\n' {
605 break
606 }
607 }
608
609 for {
610 r, eof := p.read()
611 if eof {
612 p.errorf(bc.Range.Start, p.readerPos, `block comments must be terminated with """`)
613 return bc
614 }
615
616 if r != '"' {
617 sb.WriteRune(r)
618 continue
619 }
620
621 s, eof := p.peekn(2)
622 if eof {
623 p.errorf(bc.Range.Start, p.readerPos, `block comments must be terminated with """`)
624 return bc
625 }
626 if s != `""` {
627 sb.WriteByte('"')
628 p.rewind()
629 continue
630 }
631 p.commit()
632 return bc
633 }
634 }
635
636 func trimSpaceAfterLastNewline(s string) string {
637 lastNewline := strings.LastIndexByte(s, '\n')
638 if lastNewline == -1 {
639 return strings.TrimRightFunc(s, unicode.IsSpace)
640 }
641
642 lastLine := s[lastNewline+1:]
643 lastLine = strings.TrimRightFunc(lastLine, unicode.IsSpace)
644 if len(lastLine) == 0 {
645 return s[:lastNewline]
646 }
647 return s[:lastNewline+1] + lastLine
648 }
649
650 func (p *parser) parseMapKey() (mk *d2ast.Key) {
651 mk = &d2ast.Key{
652 Range: d2ast.Range{
653 Path: p.path,
654 Start: p.pos,
655 },
656 }
657 defer mk.Range.End.From(&p.pos)
658
659 defer func() {
660 if mk.Key == nil && len(mk.Edges) == 0 {
661 mk = nil
662 }
663 }()
664
665
666 r, eof := p.peek()
667 if eof {
668 return mk
669 }
670 if r == '!' {
671 r, eof := p.peek()
672 if eof {
673 return mk
674 }
675 if r == '&' {
676 p.commit()
677 mk.NotAmpersand = true
678 } else {
679 p.rewind()
680 }
681 } else if r == '&' {
682 p.commit()
683 mk.Ampersand = true
684 } else {
685 p.rewind()
686 }
687
688 r, eof = p.peek()
689 if eof {
690 return mk
691 }
692 if r == '(' {
693 p.commit()
694 p.parseEdgeGroup(mk)
695 return mk
696 }
697 p.rewind()
698
699 k := p.parseKey()
700 if k != nil {
701 mk.Key = k
702 }
703
704 r, newlines, eof := p.peekNotSpace()
705 if eof {
706 return mk
707 }
708 if newlines > 0 {
709 p.rewind()
710 return mk
711 }
712 switch r {
713 case '(':
714 p.commit()
715 p.parseEdgeGroup(mk)
716 return mk
717 case '<', '>', '-':
718 p.rewind()
719 mk.Key = nil
720 p.parseEdges(mk, k)
721 p.parseMapKeyValue(mk)
722 return mk
723 default:
724 p.rewind()
725 p.parseMapKeyValue(mk)
726 return mk
727 }
728 }
729
730 func (p *parser) parseMapKeyValue(mk *d2ast.Key) {
731 r, newlines, eof := p.peekNotSpace()
732 if eof {
733 return
734 }
735 if newlines > 0 {
736 p.rewind()
737 return
738 }
739
740 switch r {
741 case '{':
742 p.rewind()
743 if mk.Key == nil && len(mk.Edges) == 0 {
744 return
745 }
746 case ':':
747 p.commit()
748 if mk.Key == nil && len(mk.Edges) == 0 {
749 p.errorf(mk.Range.Start, p.pos, "map value without key")
750 }
751 default:
752 p.rewind()
753 return
754 }
755 mk.Value = p.parseValue()
756 if mk.Value.Unbox() == nil {
757 p.errorf(p.pos.Subtract(':', p.utf16Pos), p.pos, "missing value after colon")
758 }
759
760 sb := mk.Value.ScalarBox()
761
762 if sb.Unbox() != nil {
763 r, newlines, eof := p.peekNotSpace()
764 if eof || newlines > 0 || r != '{' {
765 p.rewind()
766 return
767 }
768
769
770 p.commit()
771 p.replay(r)
772 mk.Primary = sb
773 mk.Value = p.parseValue()
774 }
775 }
776
777 func (p *parser) parseEdgeGroup(mk *d2ast.Key) {
778
779 p.inEdgeGroup = true
780 defer func() {
781 p.inEdgeGroup = false
782 }()
783
784 src := p.parseKey()
785 p.parseEdges(mk, src)
786
787 r, newlines, eof := p.peekNotSpace()
788 if eof || newlines > 0 {
789 p.rewind()
790 return
791 }
792 if r != ')' {
793 p.rewind()
794 p.errorf(mk.Range.Start, p.pos, "edge groups must be terminated with )")
795 return
796 }
797 p.commit()
798
799 r, newlines, eof = p.peekNotSpace()
800 if eof || newlines > 0 {
801 p.rewind()
802 return
803 }
804 if r == '[' {
805 p.commit()
806 mk.EdgeIndex = p.parseEdgeIndex()
807 } else {
808 p.rewind()
809 }
810
811 r, newlines, eof = p.peekNotSpace()
812 if eof || newlines > 0 {
813 p.rewind()
814 return
815 }
816 if r == '.' {
817 p.commit()
818 mk.EdgeKey = p.parseKey()
819 } else {
820 p.rewind()
821 }
822
823 p.inEdgeGroup = false
824 p.parseMapKeyValue(mk)
825 }
826
827 func (p *parser) parseEdgeIndex() *d2ast.EdgeIndex {
828 ei := &d2ast.EdgeIndex{
829 Range: d2ast.Range{
830 Path: p.path,
831 Start: p.pos.Subtract('[', p.utf16Pos),
832 },
833 }
834 defer ei.Range.End.From(&p.pos)
835
836 r, newlines, eof := p.peekNotSpace()
837 if eof || newlines > 0 {
838 p.rewind()
839 return nil
840 }
841
842 if unicode.IsDigit(r) {
843 p.commit()
844 var sb strings.Builder
845 sb.WriteRune(r)
846 for {
847 r, newlines, eof = p.peekNotSpace()
848 if eof || newlines > 0 {
849 p.rewind()
850 p.errorf(ei.Range.Start, p.pos, "unterminated edge index")
851 return nil
852 }
853 if r == ']' {
854 p.rewind()
855 break
856 }
857 p.commit()
858 if !unicode.IsDigit(r) {
859 p.errorf(p.pos.Subtract(r, p.utf16Pos), p.pos, "unexpected character in edge index")
860 continue
861 }
862 sb.WriteRune(r)
863 }
864 i, _ := strconv.Atoi(sb.String())
865 ei.Int = &i
866 } else if r == '*' {
867 p.commit()
868 ei.Glob = true
869 } else {
870 p.errorf(p.pos.Subtract(r, p.utf16Pos), p.pos, "unexpected character in edge index")
871
872 }
873
874 r, newlines, eof = p.peekNotSpace()
875 if eof || newlines > 0 || r != ']' {
876 p.rewind()
877 p.errorf(ei.Range.Start, p.pos, "unterminated edge index")
878 return ei
879 }
880 p.commit()
881 return ei
882 }
883
884 func (p *parser) parseEdges(mk *d2ast.Key, src *d2ast.KeyPath) {
885 for {
886 e := &d2ast.Edge{
887 Range: d2ast.Range{
888 Path: p.path,
889 },
890 Src: src,
891 }
892 if src != nil {
893 e.Range.Start = src.Range.Start
894 } else {
895 e.Range.Start = p.pos
896 }
897
898 r, newlines, eof := p.peekNotSpace()
899 if eof {
900 return
901 }
902 if newlines > 0 {
903 p.rewind()
904 return
905 }
906 if r == '<' || r == '*' {
907 e.SrcArrow = string(r)
908 } else if r != '-' {
909 p.rewind()
910 return
911 }
912 if src == nil {
913 p.errorf(p.lookaheadPos.Subtract(r, p.utf16Pos), p.lookaheadPos, "connection missing source")
914 e.Range.Start = p.lookaheadPos.Subtract(r, p.utf16Pos)
915 }
916 p.commit()
917
918 if !p.parseEdge(e) {
919 return
920 }
921
922 dst := p.parseKey()
923 if dst == nil {
924 p.errorf(e.Range.Start, p.pos, "connection missing destination")
925 } else {
926 e.Dst = dst
927 e.Range.End = e.Dst.Range.End
928 }
929 mk.Edges = append(mk.Edges, e)
930 src = dst
931 }
932 }
933
934 func (p *parser) parseEdge(e *d2ast.Edge) (ok bool) {
935 defer e.Range.End.From(&p.pos)
936
937 for {
938 r, eof := p.peek()
939 if eof {
940 p.errorf(e.Range.Start, p.readerPos, "unterminated connection")
941 return false
942 }
943 switch r {
944 case '>', '*':
945 e.DstArrow = string(r)
946 p.commit()
947 return true
948 case '\\':
949 p.commit()
950 r, newlines, eof := p.peekNotSpace()
951 if eof {
952 continue
953 }
954 if newlines == 0 {
955 p.rewind()
956 p.errorf(e.Range.Start, p.readerPos, "only newline escapes are allowed in connections")
957 return false
958 }
959 if newlines > 1 {
960 p.rewind()
961 continue
962 }
963 p.commit()
964 p.replay(r)
965 case '-':
966 p.commit()
967 default:
968 p.rewind()
969 return true
970 }
971 }
972 }
973
974 func (p *parser) parseKey() (k *d2ast.KeyPath) {
975 k = &d2ast.KeyPath{
976 Range: d2ast.Range{
977 Path: p.path,
978 Start: p.pos,
979 },
980 }
981
982 defer func() {
983 if len(k.Path) == 0 {
984 k = nil
985 } else {
986 k.Range.End = k.Path[len(k.Path)-1].Unbox().GetRange().End
987 }
988 }()
989
990 for {
991 r, newlines, eof := p.peekNotSpace()
992 if eof {
993 return k
994 }
995 if newlines > 0 || r == '(' {
996 p.rewind()
997 return k
998 }
999
1000 if r == '.' {
1001 continue
1002 }
1003 p.rewind()
1004
1005 sb := p.parseString(true)
1006 s := sb.Unbox()
1007 if s == nil {
1008 return k
1009 }
1010 if sb.UnquotedString != nil && strings.HasPrefix(s.ScalarString(), "@") {
1011 p.errorf(s.GetRange().Start, s.GetRange().End, "%s is not a valid import, did you mean ...%[2]s?", s.ScalarString())
1012 }
1013
1014 if len(k.Path) == 0 {
1015 k.Range.Start = s.GetRange().Start
1016 }
1017 k.Path = append(k.Path, &sb)
1018
1019 r, newlines, eof = p.peekNotSpace()
1020 if eof {
1021 return k
1022 }
1023 if newlines > 0 || r != '.' {
1024 p.rewind()
1025 return k
1026 }
1027
1028 p.commit()
1029 }
1030 }
1031
1032
1033 func (p *parser) parseString(inKey bool) d2ast.StringBox {
1034 var box d2ast.StringBox
1035
1036 r, newlines, eof := p.peekNotSpace()
1037 if eof || newlines > 0 {
1038 p.rewind()
1039 return box
1040 }
1041 p.commit()
1042
1043 switch r {
1044 case '"':
1045 box.DoubleQuotedString = p.parseDoubleQuotedString(inKey)
1046 return box
1047 case '\'':
1048 box.SingleQuotedString = p.parseSingleQuotedString()
1049 return box
1050 case '|':
1051 box.BlockString = p.parseBlockString()
1052 return box
1053 default:
1054 p.replay(r)
1055 box.UnquotedString = p.parseUnquotedString(inKey)
1056 return box
1057 }
1058 }
1059
1060 func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
1061 s = &d2ast.UnquotedString{
1062 Range: d2ast.Range{
1063 Path: p.path,
1064 Start: p.pos,
1065 },
1066 }
1067
1068 lastNonSpace := p.pos
1069 defer s.Range.End.From(&lastNonSpace)
1070
1071 var sb strings.Builder
1072 var rawb strings.Builder
1073 lastPatternIndex := 0
1074 defer func() {
1075 sv := strings.TrimRightFunc(sb.String(), unicode.IsSpace)
1076 rawv := strings.TrimRightFunc(rawb.String(), unicode.IsSpace)
1077 if s.Pattern != nil {
1078 if lastPatternIndex < len(sv) {
1079 s.Pattern = append(s.Pattern, sv[lastPatternIndex:])
1080 }
1081 }
1082 if sv == "" {
1083 if len(s.Value) > 0 {
1084 return
1085 }
1086 s = nil
1087
1088
1089
1090 return
1091 }
1092 s.Value = append(s.Value, d2ast.InterpolationBox{String: &sv, StringRaw: &rawv})
1093 }()
1094
1095 _s, eof := p.peekn(4)
1096 p.rewind()
1097 if !eof {
1098 if _s == "...@" {
1099 p.errorf(p.pos, p.pos.AdvanceString("...@", p.utf16Pos), "unquoted strings cannot begin with ...@ as that's import spread syntax")
1100 }
1101 }
1102
1103 for {
1104 r, eof := p.peek()
1105 if eof {
1106 return s
1107 }
1108
1109 if p.inEdgeGroup && r == ')' {
1110
1111 r2, newlines, eof := p.peekNotSpace()
1112 if eof || newlines > 0 {
1113 p.rewind()
1114 return s
1115 }
1116 switch r2 {
1117 case '\n', '#', '{', '}', '[', ']', ':', '.':
1118 p.rewind()
1119 return s
1120 }
1121 p.rewind()
1122 p.peek()
1123 p.commit()
1124 lastNonSpace = p.pos
1125 sb.WriteRune(r)
1126 rawb.WriteRune(r)
1127 continue
1128 }
1129
1130
1131
1132
1133
1134 switch r {
1135 case '\n', ';', '#', '{', '}', '[', ']':
1136 p.rewind()
1137 return s
1138 }
1139 if inKey {
1140 switch r {
1141 case ':', '.', '<', '>', '&':
1142 p.rewind()
1143 return s
1144 case '-':
1145
1146 r2, eof := p.peek()
1147 if eof {
1148 return s
1149 }
1150 switch r2 {
1151 case '\n', ';', '#', '{', '}', '[', ']':
1152 p.rewind()
1153 p.peek()
1154 p.commit()
1155 sb.WriteRune(r)
1156 rawb.WriteRune(r)
1157 return s
1158 }
1159 if r2 == '-' || r2 == '>' || r2 == '*' {
1160 p.rewind()
1161 return s
1162 }
1163 sb.WriteRune(r)
1164 rawb.WriteRune(r)
1165 r = r2
1166 case '*':
1167 if sb.Len() == 0 {
1168 s.Pattern = append(s.Pattern, "*")
1169 } else {
1170 s.Pattern = append(s.Pattern, sb.String()[lastPatternIndex:], "*")
1171 }
1172 lastPatternIndex = len(sb.String()) + 1
1173 }
1174 }
1175
1176 p.commit()
1177
1178 if !unicode.IsSpace(r) {
1179 lastNonSpace = p.pos
1180 }
1181
1182 if !inKey && r == '$' {
1183 subst := p.parseSubstitution(false)
1184 if subst != nil {
1185 if sb.Len() > 0 {
1186 sv := sb.String()
1187 rawv := rawb.String()
1188 s.Value = append(s.Value, d2ast.InterpolationBox{String: &sv, StringRaw: &rawv})
1189 sb.Reset()
1190 rawb.Reset()
1191 }
1192 s.Value = append(s.Value, d2ast.InterpolationBox{Substitution: subst})
1193 continue
1194 }
1195 continue
1196 }
1197
1198 if r != '\\' {
1199 sb.WriteRune(r)
1200 rawb.WriteRune(r)
1201 continue
1202 }
1203
1204 r2, eof := p.read()
1205 if eof {
1206 p.errorf(p.pos.Subtract('\\', p.utf16Pos), p.readerPos, "unfinished escape sequence")
1207 return s
1208 }
1209
1210 if r2 == '\n' {
1211 r, newlines, eof := p.peekNotSpace()
1212 if eof || newlines > 0 {
1213 p.rewind()
1214 return s
1215 }
1216 p.commit()
1217 p.replay(r)
1218 continue
1219 }
1220
1221 sb.WriteRune(decodeEscape(r2))
1222 rawb.WriteByte('\\')
1223 rawb.WriteRune(r2)
1224 }
1225 }
1226
1227
1228
1229 func decodeEscape(r2 rune) rune {
1230 switch r2 {
1231 case 'a':
1232 return '\a'
1233 case 'b':
1234 return '\b'
1235 case 'f':
1236 return '\f'
1237 case 'n':
1238 return '\n'
1239 case 'r':
1240 return '\r'
1241 case 't':
1242 return '\t'
1243 case 'v':
1244 return '\v'
1245 case '\\':
1246 return '\\'
1247 case '"':
1248 return '"'
1249 default:
1250 return r2
1251 }
1252 }
1253
1254 func (p *parser) parseDoubleQuotedString(inKey bool) *d2ast.DoubleQuotedString {
1255 s := &d2ast.DoubleQuotedString{
1256 Range: d2ast.Range{
1257 Path: p.path,
1258 Start: p.pos.Subtract('"', p.utf16Pos),
1259 },
1260 }
1261 defer s.Range.End.From(&p.pos)
1262
1263 var sb strings.Builder
1264 var rawb strings.Builder
1265 defer func() {
1266 if sb.Len() > 0 {
1267 sv := sb.String()
1268 rawv := rawb.String()
1269 s.Value = append(s.Value, d2ast.InterpolationBox{String: &sv, StringRaw: &rawv})
1270 }
1271 }()
1272
1273 for {
1274 r, eof := p.peek()
1275 if eof {
1276 p.errorf(s.Range.Start, p.readerPos, `double quoted strings must be terminated with "`)
1277 return s
1278 }
1279 if r == '\n' {
1280 p.rewind()
1281 p.errorf(s.Range.Start, p.pos, `double quoted strings must be terminated with "`)
1282 return s
1283 }
1284
1285 p.commit()
1286 if !inKey && r == '$' {
1287 subst := p.parseSubstitution(false)
1288 if subst != nil {
1289 if sb.Len() > 0 {
1290 s.Value = append(s.Value, d2ast.InterpolationBox{String: go2.Pointer(sb.String())})
1291 sb.Reset()
1292 }
1293 s.Value = append(s.Value, d2ast.InterpolationBox{Substitution: subst})
1294 continue
1295 }
1296 }
1297
1298 if r == '"' {
1299 return s
1300 }
1301
1302 if r != '\\' {
1303 sb.WriteRune(r)
1304 rawb.WriteRune(r)
1305 continue
1306 }
1307
1308 r2, eof := p.read()
1309 if eof {
1310 p.errorf(p.pos.Subtract('\\', p.utf16Pos), p.readerPos, "unfinished escape sequence")
1311 p.errorf(s.Range.Start, p.readerPos, `double quoted strings must be terminated with "`)
1312 return s
1313 }
1314
1315 if r2 == '\n' {
1316
1317 continue
1318 }
1319 sb.WriteRune(decodeEscape(r2))
1320 rawb.WriteByte('\\')
1321 rawb.WriteRune(r2)
1322 }
1323 }
1324
1325 func (p *parser) parseSingleQuotedString() *d2ast.SingleQuotedString {
1326 s := &d2ast.SingleQuotedString{
1327 Range: d2ast.Range{
1328 Path: p.path,
1329 Start: p.pos.Subtract('\'', p.utf16Pos),
1330 },
1331 }
1332 defer s.Range.End.From(&p.pos)
1333
1334 var sb strings.Builder
1335 defer func() {
1336 s.Value = sb.String()
1337 }()
1338
1339 for {
1340 r, eof := p.peek()
1341 if eof {
1342 p.errorf(s.Range.Start, p.readerPos, `single quoted strings must be terminated with '`)
1343 return s
1344 }
1345 if r == '\n' {
1346 p.rewind()
1347 p.errorf(s.Range.Start, p.pos, `single quoted strings must be terminated with '`)
1348 return s
1349 }
1350 p.commit()
1351
1352 if r == '\'' {
1353 r, eof = p.peek()
1354 if eof {
1355 return s
1356 }
1357 if r == '\'' {
1358 p.commit()
1359 sb.WriteByte('\'')
1360 continue
1361 }
1362 p.rewind()
1363 return s
1364 }
1365
1366 if r != '\\' {
1367 sb.WriteRune(r)
1368 continue
1369 }
1370
1371 r2, eof := p.peek()
1372 if eof {
1373 continue
1374 }
1375
1376 switch r2 {
1377 case '\n':
1378 p.commit()
1379 continue
1380 default:
1381 sb.WriteRune(r)
1382 p.rewind()
1383 }
1384 }
1385 }
1386
1387 func (p *parser) parseBlockString() *d2ast.BlockString {
1388 bs := &d2ast.BlockString{
1389 Range: d2ast.Range{
1390 Path: p.path,
1391 Start: p.pos.Subtract('|', p.utf16Pos),
1392 },
1393 }
1394 defer bs.Range.End.From(&p.pos)
1395
1396 p.depth++
1397 defer dec(&p.depth)
1398
1399 var sb strings.Builder
1400 defer func() {
1401 bs.Value = trimSpaceAfterLastNewline(sb.String())
1402 bs.Value = trimCommonIndent(bs.Value)
1403 }()
1404
1405
1406 bs.Quote = ""
1407 for {
1408 r, eof := p.peek()
1409 if eof {
1410 p.errorf(bs.Range.Start, p.readerPos, `block string must be terminated with %v`, bs.Quote+"|")
1411 return bs
1412 }
1413
1414 if unicode.IsSpace(r) || unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
1415 p.rewind()
1416 break
1417 }
1418 p.commit()
1419 bs.Quote += string(r)
1420 }
1421
1422
1423 for {
1424 r, eof := p.peek()
1425 if eof {
1426 p.errorf(bs.Range.Start, p.readerPos, `block string must be terminated with %v`, bs.Quote+"|")
1427 return bs
1428 }
1429
1430 if unicode.IsSpace(r) {
1431 p.rewind()
1432 break
1433 }
1434 p.commit()
1435 bs.Tag += string(r)
1436 }
1437 if bs.Tag == "" {
1438
1439
1440
1441
1442
1443 bs.Tag = "md"
1444 }
1445
1446
1447 for {
1448 r, eof := p.peek()
1449 if eof {
1450 p.errorf(bs.Range.Start, p.readerPos, `block string must be terminated with %v`, bs.Quote+"|")
1451 return bs
1452 }
1453 if !unicode.IsSpace(r) {
1454
1455 sb.WriteString(p.getIndent())
1456 p.rewind()
1457 break
1458 }
1459 p.commit()
1460 if r == '\n' {
1461 break
1462 }
1463 }
1464
1465 endHint := '|'
1466 endRest := ""
1467 if len(bs.Quote) > 0 {
1468 var size int
1469 endHint, size = utf8.DecodeLastRuneInString(bs.Quote)
1470 endRest = bs.Quote[size:] + "|"
1471 }
1472
1473 for {
1474 r, eof := p.read()
1475 if eof {
1476 p.errorf(bs.Range.Start, p.readerPos, `block string must be terminated with %v`, bs.Quote+"|")
1477 return bs
1478 }
1479
1480 if r != endHint {
1481 sb.WriteRune(r)
1482 continue
1483 }
1484
1485 s, eof := p.peekn(len(endRest))
1486 if eof {
1487 p.errorf(bs.Range.Start, p.readerPos, `block string must be terminated with %v`, bs.Quote+"|")
1488 return bs
1489 }
1490 if s != endRest {
1491 sb.WriteRune(endHint)
1492 p.rewind()
1493 continue
1494 }
1495 p.commit()
1496 return bs
1497 }
1498 }
1499
1500 func (p *parser) parseArray() *d2ast.Array {
1501 a := &d2ast.Array{
1502 Range: d2ast.Range{
1503 Path: p.path,
1504 Start: p.pos.Subtract('[', p.utf16Pos),
1505 },
1506 }
1507 defer a.Range.End.From(&p.readerPos)
1508
1509 p.depth++
1510 defer dec(&p.depth)
1511
1512 for {
1513 r, eof := p.readNotSpace()
1514 if eof {
1515 p.errorf(a.Range.Start, p.readerPos, "arrays must be terminated with ]")
1516 return a
1517 }
1518
1519 switch r {
1520 case ';':
1521 continue
1522 case ']':
1523 return a
1524 }
1525
1526 n := p.parseArrayNode(r)
1527 if n.Unbox() != nil {
1528 a.Nodes = append(a.Nodes, n)
1529 }
1530
1531 if n.BlockComment != nil {
1532
1533 continue
1534 }
1535
1536 after := p.pos
1537 for {
1538 r, newlines, eof := p.peekNotSpace()
1539 if eof || newlines != 0 || r == ';' || r == ']' || r == '#' {
1540 p.rewind()
1541 break
1542 }
1543 p.commit()
1544 }
1545
1546 if after != p.pos {
1547 if n.Unbox() != nil {
1548 p.errorf(after, p.pos, "unexpected text after %v", n.Unbox().Type())
1549 } else {
1550 p.errorf(after, p.pos, "invalid text beginning unquoted string")
1551 }
1552 }
1553 }
1554 }
1555
1556 func (p *parser) parseArrayNode(r rune) d2ast.ArrayNodeBox {
1557 var box d2ast.ArrayNodeBox
1558
1559 switch r {
1560 case '#':
1561 box.Comment = p.parseComment()
1562 return box
1563 case '"':
1564 s, eof := p.peekn(2)
1565 if eof {
1566 break
1567 }
1568 if s != `""` {
1569 p.rewind()
1570 break
1571 }
1572 p.commit()
1573 box.BlockComment = p.parseBlockComment()
1574 return box
1575 case '.':
1576 s, eof := p.peekn(2)
1577 if eof {
1578 break
1579 }
1580 if s != ".." {
1581 p.rewind()
1582 break
1583 }
1584 r, eof := p.peek()
1585 if eof {
1586 break
1587 }
1588 if r == '$' {
1589 p.commit()
1590 box.Substitution = p.parseSubstitution(true)
1591 return box
1592 }
1593 if r == '@' {
1594 p.commit()
1595 box.Import = p.parseImport(true)
1596 return box
1597 }
1598 p.rewind()
1599 break
1600 }
1601
1602 p.replay(r)
1603 vbox := p.parseValue()
1604 if vbox.UnquotedString != nil && vbox.UnquotedString.ScalarString() == "" &&
1605 !(len(vbox.UnquotedString.Value) > 0 && vbox.UnquotedString.Value[0].Substitution != nil) {
1606 p.errorf(p.pos, p.pos.Advance(r, p.utf16Pos), "unquoted strings cannot start on %q", r)
1607 }
1608 box.Null = vbox.Null
1609 box.Boolean = vbox.Boolean
1610 box.Number = vbox.Number
1611 box.UnquotedString = vbox.UnquotedString
1612 box.DoubleQuotedString = vbox.DoubleQuotedString
1613 box.SingleQuotedString = vbox.SingleQuotedString
1614 box.BlockString = vbox.BlockString
1615 box.Array = vbox.Array
1616 box.Map = vbox.Map
1617 box.Import = vbox.Import
1618 return box
1619 }
1620
1621 func (p *parser) parseValue() d2ast.ValueBox {
1622 var box d2ast.ValueBox
1623
1624 r, newlines, eof := p.peekNotSpace()
1625 if eof || newlines > 0 {
1626 p.rewind()
1627 return box
1628 }
1629 p.commit()
1630
1631 switch r {
1632 case '[':
1633 box.Array = p.parseArray()
1634 return box
1635 case '{':
1636 box.Map = p.parseMap(false)
1637 return box
1638 case '@':
1639 box.Import = p.parseImport(false)
1640 return box
1641 }
1642
1643 p.replay(r)
1644 sb := p.parseString(false)
1645 if sb.DoubleQuotedString != nil {
1646 box.DoubleQuotedString = sb.DoubleQuotedString
1647 return box
1648 }
1649 if sb.SingleQuotedString != nil {
1650 box.SingleQuotedString = sb.SingleQuotedString
1651 return box
1652 }
1653 if sb.BlockString != nil {
1654 box.BlockString = sb.BlockString
1655 return box
1656 }
1657
1658 if sb.UnquotedString == nil {
1659 return box
1660 }
1661
1662 s := sb.UnquotedString
1663 if strings.EqualFold(s.ScalarString(), "null") {
1664 box.Null = &d2ast.Null{
1665 Range: s.Range,
1666 }
1667 return box
1668 }
1669
1670 if strings.EqualFold(s.ScalarString(), "true") {
1671 box.Boolean = &d2ast.Boolean{
1672 Range: s.Range,
1673 Value: true,
1674 }
1675 return box
1676 }
1677
1678 if strings.EqualFold(s.ScalarString(), "false") {
1679 box.Boolean = &d2ast.Boolean{
1680 Range: s.Range,
1681 Value: false,
1682 }
1683 return box
1684 }
1685
1686
1687 rat, ok := big.NewRat(0, 1).SetString(s.ScalarString())
1688 if ok {
1689 box.Number = &d2ast.Number{
1690 Range: s.Range,
1691 Raw: s.ScalarString(),
1692 Value: rat,
1693 }
1694 return box
1695 }
1696
1697 box.UnquotedString = s
1698 return box
1699 }
1700
1701 func (p *parser) parseSubstitution(spread bool) *d2ast.Substitution {
1702 subst := &d2ast.Substitution{
1703 Range: d2ast.Range{
1704 Path: p.path,
1705 Start: p.pos.SubtractString("$", p.utf16Pos),
1706 },
1707 Spread: spread,
1708 }
1709 defer subst.Range.End.From(&p.pos)
1710
1711 if subst.Spread {
1712 subst.Range.Start = subst.Range.Start.SubtractString("...", p.utf16Pos)
1713 }
1714
1715 r, newlines, eof := p.peekNotSpace()
1716 if eof {
1717 return nil
1718 }
1719 if newlines > 0 {
1720 p.rewind()
1721 return nil
1722 }
1723 if r != '{' {
1724 p.rewind()
1725 p.errorf(subst.Range.Start, p.readerPos, "substitutions must begin on {")
1726 return nil
1727 } else {
1728 p.commit()
1729 }
1730
1731 k := p.parseKey()
1732 if k != nil {
1733 subst.Path = k.Path
1734 }
1735
1736 r, newlines, eof = p.peekNotSpace()
1737 if eof {
1738 p.errorf(subst.Range.Start, p.readerPos, "substitutions must be terminated by }")
1739 return subst
1740 }
1741 if newlines > 0 || r != '}' {
1742 p.rewind()
1743 p.errorf(subst.Range.Start, p.pos, "substitutions must be terminated by }")
1744 return subst
1745 }
1746 p.commit()
1747
1748 return subst
1749 }
1750
1751 func (p *parser) parseImport(spread bool) *d2ast.Import {
1752 imp := &d2ast.Import{
1753 Range: d2ast.Range{
1754 Path: p.path,
1755 Start: p.pos.SubtractString("$", p.utf16Pos),
1756 },
1757 Spread: spread,
1758 }
1759 defer imp.Range.End.From(&p.pos)
1760
1761 if imp.Spread {
1762 imp.Range.Start = imp.Range.Start.SubtractString("...", p.utf16Pos)
1763 }
1764
1765 var pre strings.Builder
1766 for {
1767 r, eof := p.peek()
1768 if eof {
1769 break
1770 }
1771 if r != '.' && r != '/' {
1772 p.rewind()
1773 break
1774 }
1775 pre.WriteRune(r)
1776 p.commit()
1777 }
1778 imp.Pre = pre.String()
1779
1780 k := p.parseKey()
1781 if k == nil {
1782 return imp
1783 }
1784 if k.Path[0].UnquotedString != nil && len(k.Path) > 1 && k.Path[1].UnquotedString != nil && k.Path[1].Unbox().ScalarString() == "d2" {
1785 k.Path = append(k.Path[:1], k.Path[2:]...)
1786 }
1787 imp.Path = k.Path
1788 return imp
1789 }
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803 func dec(i *int) {
1804 *i -= 1
1805 }
1806
1807 func (p *parser) getIndent() string {
1808 return strings.Repeat(" ", p.depth*2)
1809 }
1810
1811 func trimIndent(s, indent string) string {
1812 lines := strings.Split(s, "\n")
1813 for i, l := range lines {
1814 if l == "" {
1815 continue
1816 }
1817 _, l = splitLeadingIndent(l, len(indent))
1818 lines[i] = l
1819 }
1820 return strings.Join(lines, "\n")
1821 }
1822
1823 func trimCommonIndent(s string) string {
1824 commonIndent := ""
1825 for _, l := range strings.Split(s, "\n") {
1826 if l == "" {
1827 continue
1828 }
1829 lineIndent, l := splitLeadingIndent(l, -1)
1830 if lineIndent == "" {
1831
1832 return s
1833 }
1834 if l == "" {
1835
1836 continue
1837 }
1838 if commonIndent == "" || len(lineIndent) < len(commonIndent) {
1839 commonIndent = lineIndent
1840 }
1841 }
1842 if commonIndent == "" {
1843 return s
1844 }
1845 return trimIndent(s, commonIndent)
1846 }
1847
1848 func splitLeadingIndent(s string, maxSpaces int) (indent, rets string) {
1849 var indentb strings.Builder
1850 i := 0
1851 for _, r := range s {
1852 if !unicode.IsSpace(r) {
1853 break
1854 }
1855 i++
1856 if r != '\t' {
1857 indentb.WriteRune(r)
1858 } else {
1859 indentb.WriteByte(' ')
1860 indentb.WriteByte(' ')
1861 }
1862 if maxSpaces > -1 && indentb.Len() == maxSpaces {
1863 break
1864 }
1865 }
1866 return indentb.String(), s[i:]
1867 }
1868
View as plain text