Skip to content

Commit

Permalink
Merge pull request #1371 from nhooyr/imports
Browse files Browse the repository at this point in the history
Add imports
  • Loading branch information
alixander authored Jun 7, 2023
2 parents be8def7 + 0bd11c4 commit 202ffa2
Show file tree
Hide file tree
Showing 392 changed files with 6,131 additions and 833 deletions.
2 changes: 2 additions & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#### Features 🚀

- D2 files have the ability to import from other D2 files. See [docs](https://d2lang.com/tour/imports). [#1371](https://github.com/terrastruct/d2/pull/1371)

#### Improvements 🧹

- Use shape specific sizing for grid containers [#1294](https://github.com/terrastruct/d2/pull/1294)
Expand Down
73 changes: 71 additions & 2 deletions d2ast/d2ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"errors"
"fmt"
"math/big"
"path"
"strconv"
"strings"
"unicode"
Expand Down Expand Up @@ -63,6 +64,7 @@ var _ Node = &DoubleQuotedString{}
var _ Node = &SingleQuotedString{}
var _ Node = &BlockString{}
var _ Node = &Substitution{}
var _ Node = &Import{}

var _ Node = &Array{}
var _ Node = &Map{}
Expand Down Expand Up @@ -179,6 +181,10 @@ func (p Position) String() string {
return fmt.Sprintf("%d:%d", p.Line+1, p.Column+1)
}

func (p Position) Debug() string {
return fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)
}

// See docs on Range.
func (p Position) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)), nil
Expand Down Expand Up @@ -256,6 +262,13 @@ func (p Position) Subtract(r rune, byUTF16 bool) Position {
return p
}

func (p Position) AdvanceString(s string, byUTF16 bool) Position {
for _, r := range s {
p = p.Advance(r, byUTF16)
}
return p
}

func (p Position) SubtractString(s string, byUTF16 bool) Position {
for _, r := range s {
p = p.Subtract(r, byUTF16)
Expand All @@ -277,6 +290,7 @@ var _ MapNode = &Comment{}
var _ MapNode = &BlockComment{}
var _ MapNode = &Key{}
var _ MapNode = &Substitution{}
var _ MapNode = &Import{}

// ArrayNode is implemented by nodes that may be children of Arrays.
type ArrayNode interface {
Expand All @@ -288,6 +302,7 @@ type ArrayNode interface {
var _ ArrayNode = &Comment{}
var _ ArrayNode = &BlockComment{}
var _ ArrayNode = &Substitution{}
var _ ArrayNode = &Import{}

// Value is implemented by nodes that may be values of a key.
type Value interface {
Expand Down Expand Up @@ -334,6 +349,7 @@ func (s *DoubleQuotedString) node() {}
func (s *SingleQuotedString) node() {}
func (s *BlockString) node() {}
func (s *Substitution) node() {}
func (i *Import) node() {}
func (a *Array) node() {}
func (m *Map) node() {}
func (k *Key) node() {}
Expand All @@ -351,6 +367,7 @@ func (s *DoubleQuotedString) Type() string { return "double quoted string" }
func (s *SingleQuotedString) Type() string { return "single quoted string" }
func (s *BlockString) Type() string { return s.Tag + " block string" }
func (s *Substitution) Type() string { return "substitution" }
func (i *Import) Type() string { return "import" }
func (a *Array) Type() string { return "array" }
func (m *Map) Type() string { return "map" }
func (k *Key) Type() string { return "map key" }
Expand All @@ -368,6 +385,7 @@ func (s *DoubleQuotedString) GetRange() Range { return s.Range }
func (s *SingleQuotedString) GetRange() Range { return s.Range }
func (s *BlockString) GetRange() Range { return s.Range }
func (s *Substitution) GetRange() Range { return s.Range }
func (i *Import) GetRange() Range { return i.Range }
func (a *Array) GetRange() Range { return a.Range }
func (m *Map) GetRange() Range { return m.Range }
func (k *Key) GetRange() Range { return k.Range }
Expand All @@ -379,6 +397,7 @@ func (c *Comment) mapNode() {}
func (c *BlockComment) mapNode() {}
func (k *Key) mapNode() {}
func (s *Substitution) mapNode() {}
func (i *Import) mapNode() {}

func (c *Comment) arrayNode() {}
func (c *BlockComment) arrayNode() {}
Expand All @@ -390,6 +409,7 @@ func (s *DoubleQuotedString) arrayNode() {}
func (s *SingleQuotedString) arrayNode() {}
func (s *BlockString) arrayNode() {}
func (s *Substitution) arrayNode() {}
func (i *Import) arrayNode() {}
func (a *Array) arrayNode() {}
func (m *Map) arrayNode() {}

Expand All @@ -402,6 +422,7 @@ func (s *SingleQuotedString) value() {}
func (s *BlockString) value() {}
func (a *Array) value() {}
func (m *Map) value() {}
func (i *Import) value() {}

func (n *Null) scalar() {}
func (b *Boolean) scalar() {}
Expand Down Expand Up @@ -722,11 +743,20 @@ type Substitution struct {
Path []*StringBox `json:"path"`
}

type Import struct {
Range Range `json:"range"`

Spread bool `json:"spread"`
Pre string `json:"pre"`
Path []*StringBox `json:"path"`
}

// MapNodeBox is used to box MapNode for JSON persistence.
type MapNodeBox struct {
Comment *Comment `json:"comment,omitempty"`
BlockComment *BlockComment `json:"block_comment,omitempty"`
Substitution *Substitution `json:"substitution,omitempty"`
Import *Import `json:"import,omitempty"`
MapKey *Key `json:"map_key,omitempty"`
}

Expand All @@ -739,6 +769,8 @@ func MakeMapNodeBox(n MapNode) MapNodeBox {
box.BlockComment = n
case *Substitution:
box.Substitution = n
case *Import:
box.Import = n
case *Key:
box.MapKey = n
}
Expand All @@ -753,6 +785,8 @@ func (mb MapNodeBox) Unbox() MapNode {
return mb.BlockComment
case mb.Substitution != nil:
return mb.Substitution
case mb.Import != nil:
return mb.Import
case mb.MapKey != nil:
return mb.MapKey
default:
Expand All @@ -765,6 +799,7 @@ type ArrayNodeBox struct {
Comment *Comment `json:"comment,omitempty"`
BlockComment *BlockComment `json:"block_comment,omitempty"`
Substitution *Substitution `json:"substitution,omitempty"`
Import *Import `json:"import,omitempty"`
Null *Null `json:"null,omitempty"`
Boolean *Boolean `json:"boolean,omitempty"`
Number *Number `json:"number,omitempty"`
Expand All @@ -785,6 +820,8 @@ func MakeArrayNodeBox(an ArrayNode) ArrayNodeBox {
ab.BlockComment = an
case *Substitution:
ab.Substitution = an
case *Import:
ab.Import = an
case *Null:
ab.Null = an
case *Boolean:
Expand Down Expand Up @@ -815,6 +852,8 @@ func (ab ArrayNodeBox) Unbox() ArrayNode {
return ab.BlockComment
case ab.Substitution != nil:
return ab.Substitution
case ab.Import != nil:
return ab.Import
case ab.Null != nil:
return ab.Null
case ab.Boolean != nil:
Expand Down Expand Up @@ -849,6 +888,7 @@ type ValueBox struct {
BlockString *BlockString `json:"block_string,omitempty"`
Array *Array `json:"array,omitempty"`
Map *Map `json:"map,omitempty"`
Import *Import `json:"import,omitempty"`
}

func (vb ValueBox) Unbox() Value {
Expand All @@ -871,6 +911,8 @@ func (vb ValueBox) Unbox() Value {
return vb.Array
case vb.Map != nil:
return vb.Map
case vb.Import != nil:
return vb.Import
default:
return nil
}
Expand All @@ -897,6 +939,8 @@ func MakeValueBox(v Value) ValueBox {
vb.Array = v
case *Map:
vb.Map = v
case *Import:
vb.Import = v
}
return vb
}
Expand Down Expand Up @@ -990,8 +1034,8 @@ type InterpolationBox struct {
// & is only special if it begins a key.
// - is only special if followed by another - in a key.
// ' " and | are only special if they begin an unquoted key or value.
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')'})
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$'})
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@'})
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$', '@'})

// RawString returns s in a AST String node that can format s in the most aesthetically
// pleasing way.
Expand Down Expand Up @@ -1040,8 +1084,33 @@ func RawString(s string, inKey bool) String {
return FlatUnquotedString(s)
}

func RawStringBox(s string, inKey bool) *StringBox {
return MakeValueBox(RawString(s, inKey)).StringBox()
}

func hasSurroundingWhitespace(s string) bool {
r, _ := utf8.DecodeRuneInString(s)
r2, _ := utf8.DecodeLastRuneInString(s)
return unicode.IsSpace(r) || unicode.IsSpace(r2)
}

func (s *Substitution) IDA() (ida []string) {
for _, el := range s.Path {
ida = append(ida, el.Unbox().ScalarString())
}
return ida
}

func (i *Import) IDA() (ida []string) {
for _, el := range i.Path[1:] {
ida = append(ida, el.Unbox().ScalarString())
}
return ida
}

func (i *Import) PathWithPre() string {
if len(i.Path) == 0 {
return ""
}
return path.Join(i.Pre, i.Path[0].Unbox().ScalarString())
}
1 change: 1 addition & 0 deletions d2cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
Ruler: ruler,
ThemeID: renderOpts.ThemeID,
FontFamily: fontFamily,
InputPath: inputPath,
}
if renderOpts.Sketch {
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
Expand Down
47 changes: 32 additions & 15 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"encoding/xml"
"fmt"
"io"
"io/fs"
"net/url"
"os"
"strconv"
"strings"

Expand All @@ -21,21 +23,30 @@ import (

type CompileOptions struct {
UTF16 bool
// FS is the file system used for resolving imports in the d2 text.
// It should correspond to the root path.
FS fs.FS
}

func Compile(path string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, error) {
func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, error) {
if opts == nil {
opts = &CompileOptions{}
}
if opts.FS == nil {
opts.FS = os.DirFS("/")
}

ast, err := d2parser.Parse(path, r, &d2parser.ParseOptions{
ast, err := d2parser.Parse(p, r, &d2parser.ParseOptions{
UTF16: opts.UTF16,
})
if err != nil {
return nil, err
}

ir, err := d2ir.Compile(ast)
ir, err := d2ir.Compile(ast, &d2ir.CompileOptions{
UTF16: opts.UTF16,
FS: opts.FS,
})
if err != nil {
return nil, err
}
Expand All @@ -50,7 +61,9 @@ func Compile(path string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph
}

func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) {
c := &compiler{}
c := &compiler{
err: &d2parser.ParseError{},
}

g := d2graph.NewGraph()
g.AST = ast
Expand Down Expand Up @@ -116,7 +129,7 @@ func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName
}

type compiler struct {
err d2parser.ParseError
err *d2parser.ParseError
}

func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
Expand Down Expand Up @@ -264,17 +277,19 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
obj.Map = fr.Context.Key.Value.Map
}
}
scopeObjIDA := d2ir.BoardIDA(fr.Context.ScopeMap)
scopeObj := obj.Graph.Root.EnsureChildIDVal(scopeObjIDA)
obj.References = append(obj.References, d2graph.Reference{
r := d2graph.Reference{
Key: fr.KeyPath,
KeyPathIndex: fr.KeyPathIndex(),

MapKey: fr.Context.Key,
MapKeyEdgeIndex: fr.Context.EdgeIndex(),
Scope: fr.Context.Scope,
ScopeObj: scopeObj,
})
}
if fr.Context.ScopeMap != nil {
scopeObjIDA := d2ir.BoardIDA(fr.Context.ScopeMap)
r.ScopeObj = obj.Graph.Root.EnsureChildIDVal(scopeObjIDA)
}
obj.References = append(obj.References, r)
}
}

Expand Down Expand Up @@ -617,15 +632,17 @@ func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) {

edge.Label.MapKey = e.LastPrimaryKey()
for _, er := range e.References {
scopeObjIDA := d2ir.BoardIDA(er.Context.ScopeMap)
scopeObj := edge.Src.Graph.Root.EnsureChildIDVal(scopeObjIDA)
edge.References = append(edge.References, d2graph.EdgeReference{
r := d2graph.EdgeReference{
Edge: er.Context.Edge,
MapKey: er.Context.Key,
MapKeyEdgeIndex: er.Context.EdgeIndex(),
Scope: er.Context.Scope,
ScopeObj: scopeObj,
})
}
if er.Context.ScopeMap != nil {
scopeObjIDA := d2ir.BoardIDA(er.Context.ScopeMap)
r.ScopeObj = edge.Src.Graph.Root.EnsureChildIDVal(scopeObjIDA)
}
edge.References = append(edge.References, r)
}
}

Expand Down
Loading

0 comments on commit 202ffa2

Please sign in to comment.