2022-11-03 13:54:49 +00:00
package d2compiler
import (
"errors"
"fmt"
"io"
"net/url"
"strconv"
"strings"
2022-12-01 18:48:01 +00:00
"oss.terrastruct.com/util-go/go2"
2022-11-03 13:54:49 +00:00
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2target"
)
// TODO: should Parse even be exported? guess not. IR should contain list of files and
// their AST.
type CompileOptions struct {
UTF16 bool
}
func Compile ( path string , r io . RuneReader , opts * CompileOptions ) ( * d2graph . Graph , error ) {
if opts == nil {
opts = & CompileOptions { }
}
var pe d2parser . ParseError
ast , err := d2parser . Parse ( path , r , & d2parser . ParseOptions {
UTF16 : opts . UTF16 ,
} )
if err != nil {
if ! errors . As ( err , & pe ) {
return nil , err
}
}
return compileAST ( path , pe , ast )
}
func compileAST ( path string , pe d2parser . ParseError , ast * d2ast . Map ) ( * d2graph . Graph , error ) {
g := d2graph . NewGraph ( ast )
c := & compiler {
path : path ,
err : pe ,
}
c . compileKeys ( g . Root , ast )
if len ( c . err . Errors ) == 0 {
c . validateKeys ( g . Root , ast )
}
c . compileEdges ( g . Root , ast )
// TODO: simplify removeContainer by running before compileEdges
c . compileShapes ( g . Root )
c . validateNear ( g )
if len ( c . err . Errors ) > 0 {
return nil , c . err
}
return g , nil
}
type compiler struct {
path string
err d2parser . ParseError
}
func ( c * compiler ) errorf ( start d2ast . Position , end d2ast . Position , f string , v ... interface { } ) {
r := d2ast . Range {
Path : c . path ,
Start : start ,
End : end ,
}
f = "%v: " + f
v = append ( [ ] interface { } { r } , v ... )
c . err . Errors = append ( c . err . Errors , d2ast . Error {
Range : r ,
Message : fmt . Sprintf ( f , v ... ) ,
} )
}
func ( c * compiler ) compileKeys ( obj * d2graph . Object , m * d2ast . Map ) {
for _ , n := range m . Nodes {
if n . MapKey != nil && n . MapKey . Key != nil && len ( n . MapKey . Edges ) == 0 {
c . compileKey ( obj , m , n . MapKey )
}
}
}
func ( c * compiler ) compileEdges ( obj * d2graph . Object , m * d2ast . Map ) {
for _ , n := range m . Nodes {
if n . MapKey != nil {
if len ( n . MapKey . Edges ) > 0 {
obj := obj
if n . MapKey . Key != nil {
ida := d2graph . Key ( n . MapKey . Key )
parent , resolvedIDA , err := d2graph . ResolveUnderscoreKey ( ida , obj )
if err != nil {
c . errorf ( n . MapKey . Range . Start , n . MapKey . Range . End , err . Error ( ) )
return
}
unresolvedObj := obj
obj = parent . EnsureChild ( resolvedIDA )
parent . AppendReferences ( ida , d2graph . Reference {
Key : n . MapKey . Key ,
MapKey : n . MapKey ,
Scope : m ,
} , unresolvedObj )
}
c . compileEdgeMapKey ( obj , m , n . MapKey )
}
if n . MapKey . Key != nil && n . MapKey . Value . Map != nil {
c . compileEdges ( obj . EnsureChild ( d2graph . Key ( n . MapKey . Key ) ) , n . MapKey . Value . Map )
}
}
}
}
// compileArrowheads compiles keywords for edge arrowhead attributes by
// 1. creating a fake, detached parent
// 2. compiling the arrowhead field as a fake object onto that fake parent
// 3. transferring the relevant attributes onto the edge
func ( c * compiler ) compileArrowheads ( edge * d2graph . Edge , m * d2ast . Map , mk * d2ast . Key ) bool {
arrowheadKey := mk . Key
if mk . EdgeKey != nil {
arrowheadKey = mk . EdgeKey
}
if arrowheadKey == nil || len ( arrowheadKey . Path ) == 0 {
return false
}
key := arrowheadKey . Path [ 0 ] . Unbox ( ) . ScalarString ( )
var field * d2graph . Attributes
if key == "source-arrowhead" {
if edge . SrcArrowhead == nil {
edge . SrcArrowhead = & d2graph . Attributes { }
}
field = edge . SrcArrowhead
} else if key == "target-arrowhead" {
if edge . DstArrowhead == nil {
edge . DstArrowhead = & d2graph . Attributes { }
}
field = edge . DstArrowhead
} else {
return false
}
fakeParent := & d2graph . Object {
Children : make ( map [ string ] * d2graph . Object ) ,
}
detachedMK := & d2ast . Key {
Key : arrowheadKey ,
Primary : mk . Primary ,
Value : mk . Value ,
}
c . compileKey ( fakeParent , m , detachedMK )
fakeObj := fakeParent . ChildrenArray [ 0 ]
c . compileShapes ( fakeObj )
if fakeObj . Attributes . Shape . Value != "" {
field . Shape = fakeObj . Attributes . Shape
}
if fakeObj . Attributes . Label . Value != "" && fakeObj . Attributes . Label . Value != "source-arrowhead" && fakeObj . Attributes . Label . Value != "target-arrowhead" {
field . Label = fakeObj . Attributes . Label
}
if fakeObj . Attributes . Style . Filled != nil {
field . Style . Filled = fakeObj . Attributes . Style . Filled
}
return true
}
func ( c * compiler ) compileAttributes ( attrs * d2graph . Attributes , mk * d2ast . Key ) {
var reserved string
var ok bool
if mk . EdgeKey != nil {
_ , reserved , ok = c . compileFlatKey ( mk . EdgeKey )
} else if mk . Key != nil {
_ , reserved , ok = c . compileFlatKey ( mk . Key )
}
if ! ok {
return
}
if reserved == "" || reserved == "label" {
attrs . Label . MapKey = mk
} else if reserved == "shape" {
attrs . Shape . MapKey = mk
} else if reserved == "opacity" {
attrs . Style . Opacity = & d2graph . Scalar { MapKey : mk }
} else if reserved == "stroke" {
attrs . Style . Stroke = & d2graph . Scalar { MapKey : mk }
} else if reserved == "fill" {
attrs . Style . Fill = & d2graph . Scalar { MapKey : mk }
} else if reserved == "stroke-width" {
attrs . Style . StrokeWidth = & d2graph . Scalar { MapKey : mk }
} else if reserved == "stroke-dash" {
attrs . Style . StrokeDash = & d2graph . Scalar { MapKey : mk }
} else if reserved == "border-radius" {
attrs . Style . BorderRadius = & d2graph . Scalar { MapKey : mk }
} else if reserved == "shadow" {
attrs . Style . Shadow = & d2graph . Scalar { MapKey : mk }
} else if reserved == "3d" {
// TODO this should be movd to validateKeys, as shape may not be set yet
if attrs . Shape . Value != "" && ! strings . EqualFold ( attrs . Shape . Value , d2target . ShapeSquare ) && ! strings . EqualFold ( attrs . Shape . Value , d2target . ShapeRectangle ) {
c . errorf ( mk . Range . Start , mk . Range . End , ` key "3d" can only be applied to squares and rectangles ` )
return
}
attrs . Style . ThreeDee = & d2graph . Scalar { MapKey : mk }
} else if reserved == "multiple" {
attrs . Style . Multiple = & d2graph . Scalar { MapKey : mk }
} else if reserved == "font" {
attrs . Style . Font = & d2graph . Scalar { MapKey : mk }
} else if reserved == "font-size" {
attrs . Style . FontSize = & d2graph . Scalar { MapKey : mk }
} else if reserved == "font-color" {
attrs . Style . FontColor = & d2graph . Scalar { MapKey : mk }
} else if reserved == "animated" {
attrs . Style . Animated = & d2graph . Scalar { MapKey : mk }
} else if reserved == "bold" {
attrs . Style . Bold = & d2graph . Scalar { MapKey : mk }
} else if reserved == "italic" {
attrs . Style . Italic = & d2graph . Scalar { MapKey : mk }
} else if reserved == "underline" {
attrs . Style . Underline = & d2graph . Scalar { MapKey : mk }
} else if reserved == "filled" {
attrs . Style . Filled = & d2graph . Scalar { MapKey : mk }
} else if reserved == "width" {
attrs . Width = & d2graph . Scalar { MapKey : mk }
} else if reserved == "height" {
attrs . Height = & d2graph . Scalar { MapKey : mk }
}
}
func ( c * compiler ) compileKey ( obj * d2graph . Object , m * d2ast . Map , mk * d2ast . Key ) {
ida , reserved , ok := c . compileFlatKey ( mk . Key )
if ! ok {
return
}
if reserved == "desc" {
return
}
resolvedObj , resolvedIDA , err := d2graph . ResolveUnderscoreKey ( ida , obj )
if err != nil {
c . errorf ( mk . Range . Start , mk . Range . End , err . Error ( ) )
return
}
2022-12-04 20:47:55 +00:00
parent := resolvedObj
2022-11-03 13:54:49 +00:00
if len ( resolvedIDA ) > 0 {
unresolvedObj := obj
obj = parent . EnsureChild ( resolvedIDA )
parent . AppendReferences ( ida , d2graph . Reference {
Key : mk . Key ,
MapKey : mk ,
Scope : m ,
} , unresolvedObj )
} else if obj . Parent == nil {
// Top level reserved key set on root.
2022-11-28 22:10:24 +00:00
c . compileAttributes ( & obj . Attributes , mk )
c . applyScalar ( & obj . Attributes , reserved , mk . Value . ScalarBox ( ) )
2022-11-03 13:54:49 +00:00
return
}
if len ( mk . Edges ) > 0 {
return
}
c . compileAttributes ( & obj . Attributes , mk )
if obj . Attributes . Style . Animated != nil {
c . errorf ( mk . Range . Start , mk . Range . End , ` key "animated" can only be applied to edges ` )
return
}
c . applyScalar ( & obj . Attributes , reserved , mk . Value . ScalarBox ( ) )
if mk . Value . Map != nil {
if reserved != "" {
c . errorf ( mk . Range . Start , mk . Range . End , "cannot set reserved key %q to a map" , reserved )
return
}
obj . Map = mk . Value . Map
c . compileKeys ( obj , mk . Value . Map )
}
c . applyScalar ( & obj . Attributes , reserved , mk . Primary )
}
func ( c * compiler ) applyScalar ( attrs * d2graph . Attributes , reserved string , box d2ast . ScalarBox ) {
scalar := box . Unbox ( )
if scalar == nil {
return
}
switch reserved {
case "shape" :
in := d2target . IsShape ( scalar . ScalarString ( ) )
_ , isArrowhead := d2target . Arrowheads [ scalar . ScalarString ( ) ]
if ! in && ! isArrowhead {
c . errorf ( scalar . GetRange ( ) . Start , scalar . GetRange ( ) . End , "unknown shape %q" , scalar . ScalarString ( ) )
return
}
if box . Null != nil {
attrs . Shape . Value = ""
} else {
attrs . Shape . Value = scalar . ScalarString ( )
}
if attrs . Shape . Value == d2target . ShapeCode {
// Explicit code shape is plaintext.
attrs . Language = d2target . ShapeText
}
return
case "icon" :
iconURL , err := url . Parse ( scalar . ScalarString ( ) )
if err != nil {
c . errorf ( scalar . GetRange ( ) . Start , scalar . GetRange ( ) . End , "bad icon url %#v: %s" , scalar . ScalarString ( ) , err )
return
}
attrs . Icon = iconURL
return
case "near" :
nearKey , err := d2parser . ParseKey ( scalar . ScalarString ( ) )
if err != nil {
c . errorf ( scalar . GetRange ( ) . Start , scalar . GetRange ( ) . End , "bad near key %#v: %s" , scalar . ScalarString ( ) , err )
return
}
attrs . NearKey = nearKey
return
case "tooltip" :
attrs . Tooltip = scalar . ScalarString ( )
return
case "width" :
_ , err := strconv . Atoi ( scalar . ScalarString ( ) )
if err != nil {
c . errorf ( scalar . GetRange ( ) . Start , scalar . GetRange ( ) . End , "non-integer width %#v: %s" , scalar . ScalarString ( ) , err )
return
}
attrs . Width . Value = scalar . ScalarString ( )
return
case "height" :
_ , err := strconv . Atoi ( scalar . ScalarString ( ) )
if err != nil {
c . errorf ( scalar . GetRange ( ) . Start , scalar . GetRange ( ) . End , "non-integer height %#v: %s" , scalar . ScalarString ( ) , err )
return
}
attrs . Height . Value = scalar . ScalarString ( )
return
case "link" :
attrs . Link = scalar . ScalarString ( )
return
2022-11-30 00:02:37 +00:00
case "direction" :
dirs := [ ] string { "up" , "down" , "right" , "left" }
if ! go2 . Contains ( dirs , scalar . ScalarString ( ) ) {
c . errorf ( scalar . GetRange ( ) . Start , scalar . GetRange ( ) . End , ` direction must be one of %v, got %q ` , strings . Join ( dirs , ", " ) , scalar . ScalarString ( ) )
2022-11-29 05:39:36 +00:00
return
}
2022-11-30 00:02:37 +00:00
attrs . Direction . Value = scalar . ScalarString ( )
2022-11-29 05:39:36 +00:00
return
2022-12-12 00:46:09 +00:00
case "constraint" :
// Compilation for shape-specific keywords happens elsewhere
return
2022-11-03 13:54:49 +00:00
}
if _ , ok := d2graph . StyleKeywords [ reserved ] ; ok {
if err := attrs . Style . Apply ( reserved , scalar . ScalarString ( ) ) ; err != nil {
c . errorf ( scalar . GetRange ( ) . Start , scalar . GetRange ( ) . End , err . Error ( ) )
}
return
}
if box . Null != nil {
// TODO: delete obj
attrs . Label . Value = ""
} else {
attrs . Label . Value = scalar . ScalarString ( )
}
bs := box . BlockString
if bs != nil && reserved == "" {
attrs . Language = bs . Tag
fullTag , ok := ShortToFullLanguageAliases [ bs . Tag ]
if ok {
attrs . Language = fullTag
}
2022-11-27 21:54:41 +00:00
if attrs . Language == "markdown" || attrs . Language == "latex" {
2022-11-03 13:54:49 +00:00
attrs . Shape . Value = d2target . ShapeText
} else {
attrs . Shape . Value = d2target . ShapeCode
}
}
}
func ( c * compiler ) compileEdgeMapKey ( obj * d2graph . Object , m * d2ast . Map , mk * d2ast . Key ) {
if mk . EdgeIndex != nil {
edge , ok := obj . HasEdge ( mk )
if ok {
c . appendEdgeReferences ( obj , m , mk )
edge . References = append ( edge . References , d2graph . EdgeReference {
Edge : mk . Edges [ 0 ] ,
MapKey : mk ,
MapKeyEdgeIndex : 0 ,
Scope : m ,
ScopeObj : obj ,
} )
c . compileEdge ( edge , m , mk )
}
return
}
for i , e := range mk . Edges {
if e . Src == nil || e . Dst == nil {
continue
}
edge , err := obj . Connect ( d2graph . Key ( e . Src ) , d2graph . Key ( e . Dst ) , e . SrcArrow == "<" , e . DstArrow == ">" , "" )
if err != nil {
c . errorf ( e . Range . Start , e . Range . End , err . Error ( ) )
continue
}
edge . References = append ( edge . References , d2graph . EdgeReference {
Edge : e ,
MapKey : mk ,
MapKeyEdgeIndex : i ,
Scope : m ,
ScopeObj : obj ,
} )
c . compileEdge ( edge , m , mk )
}
c . appendEdgeReferences ( obj , m , mk )
}
func ( c * compiler ) compileEdge ( edge * d2graph . Edge , m * d2ast . Map , mk * d2ast . Key ) {
if mk . Key == nil && mk . EdgeKey == nil {
if len ( mk . Edges ) == 1 {
edge . Attributes . Label . MapKey = mk
}
c . applyScalar ( & edge . Attributes , "" , mk . Value . ScalarBox ( ) )
c . applyScalar ( & edge . Attributes , "" , mk . Primary )
} else {
c . compileEdgeKey ( edge , m , mk )
}
if mk . Value . Map != nil && mk . EdgeKey == nil {
for _ , n := range mk . Value . Map . Nodes {
if n . MapKey == nil {
continue
}
if len ( n . MapKey . Edges ) > 0 {
c . errorf ( mk . Range . Start , mk . Range . End , ` edges cannot be nested within another edge ` )
continue
}
if n . MapKey . Key == nil {
continue
}
for _ , p := range n . MapKey . Key . Path {
_ , ok := d2graph . ReservedKeywords [ strings . ToLower ( p . Unbox ( ) . ScalarString ( ) ) ]
if ! ok {
c . errorf ( mk . Range . Start , mk . Range . End , ` edge map keys must be reserved keywords ` )
return
}
}
c . compileEdgeKey ( edge , m , n . MapKey )
}
}
}
func ( c * compiler ) compileEdgeKey ( edge * d2graph . Edge , m * d2ast . Map , mk * d2ast . Key ) {
var r string
var ok bool
// Give precedence to EdgeKeys
// x.(a -> b)[0].style.opacity: 0.4
// We want to compile the style.opacity, not the x
if mk . EdgeKey != nil {
_ , r , ok = c . compileFlatKey ( mk . EdgeKey )
} else if mk . Key != nil {
_ , r , ok = c . compileFlatKey ( mk . Key )
}
if ! ok {
return
}
ok = c . compileArrowheads ( edge , m , mk )
if ok {
return
}
c . compileAttributes ( & edge . Attributes , mk )
c . applyScalar ( & edge . Attributes , r , mk . Value . ScalarBox ( ) )
if mk . Value . Map != nil {
for _ , n := range mk . Value . Map . Nodes {
if n . MapKey != nil {
c . compileEdgeKey ( edge , m , n . MapKey )
}
}
}
}
func ( c * compiler ) appendEdgeReferences ( obj * d2graph . Object , m * d2ast . Map , mk * d2ast . Key ) {
for i , e := range mk . Edges {
if e . Src != nil {
ida := d2graph . Key ( e . Src )
parent , _ , err := d2graph . ResolveUnderscoreKey ( ida , obj )
if err != nil {
c . errorf ( mk . Range . Start , mk . Range . End , err . Error ( ) )
return
}
parent . AppendReferences ( ida , d2graph . Reference {
Key : e . Src ,
MapKey : mk ,
MapKeyEdgeIndex : i ,
Scope : m ,
} , obj )
}
if e . Dst != nil {
ida := d2graph . Key ( e . Dst )
parent , _ , err := d2graph . ResolveUnderscoreKey ( ida , obj )
if err != nil {
c . errorf ( mk . Range . Start , mk . Range . End , err . Error ( ) )
return
}
parent . AppendReferences ( ida , d2graph . Reference {
Key : e . Dst ,
MapKey : mk ,
MapKeyEdgeIndex : i ,
Scope : m ,
} , obj )
}
}
}
func ( c * compiler ) compileFlatKey ( k * d2ast . KeyPath ) ( [ ] string , string , bool ) {
k2 := * k
var reserved string
for i , s := range k . Path {
keyword := strings . ToLower ( s . Unbox ( ) . ScalarString ( ) )
_ , isReserved := d2graph . ReservedKeywords [ keyword ]
_ , isReservedHolder := d2graph . ReservedKeywordHolders [ keyword ]
if isReserved && ! isReservedHolder {
reserved = keyword
k2 . Path = k2 . Path [ : i ]
break
}
}
if len ( k2 . Path ) < len ( k . Path ) - 1 {
c . errorf ( k . Range . Start , k . Range . End , "reserved key %q cannot have children" , reserved )
return nil , "" , false
}
return d2graph . Key ( & k2 ) , reserved , true
}
// TODO add more, e.g. C, bash
var ShortToFullLanguageAliases = map [ string ] string {
2022-11-27 21:54:41 +00:00
"md" : "markdown" ,
"tex" : "latex" ,
"js" : "javascript" ,
"go" : "golang" ,
"py" : "python" ,
"rb" : "ruby" ,
"ts" : "typescript" ,
2022-11-03 13:54:49 +00:00
}
var FullToShortLanguageAliases map [ string ] string
func ( c * compiler ) compileShapes ( obj * d2graph . Object ) {
for _ , obj := range obj . ChildrenArray {
switch obj . Attributes . Shape . Value {
case d2target . ShapeClass :
c . compileClass ( obj )
case d2target . ShapeSQLTable :
c . compileSQLTable ( obj )
case d2target . ShapeImage :
c . compileImage ( obj )
}
c . compileShapes ( obj )
}
2022-12-23 07:41:15 +00:00
for i := 0 ; i < len ( obj . ChildrenArray ) ; i ++ {
ch := obj . ChildrenArray [ i ]
switch ch . Attributes . Shape . Value {
2022-11-03 13:54:49 +00:00
case d2target . ShapeClass , d2target . ShapeSQLTable :
2022-12-23 07:41:15 +00:00
flattenContainer ( obj . Graph , ch )
2022-11-03 13:54:49 +00:00
}
2022-12-23 07:41:15 +00:00
if ch . IDVal == "style" {
obj . Attributes . Style = ch . Attributes . Style
2022-11-03 13:54:49 +00:00
if obj . Graph != nil {
2022-12-23 07:41:15 +00:00
flattenContainer ( obj . Graph , ch )
for i := 0 ; i < len ( obj . Graph . Objects ) ; i ++ {
if obj . Graph . Objects [ i ] == ch {
obj . Graph . Objects = append ( obj . Graph . Objects [ : i ] , obj . Graph . Objects [ i + 1 : ] ... )
break
}
}
delete ( obj . Children , ch . ID )
obj . ChildrenArray = append ( obj . ChildrenArray [ : i ] , obj . ChildrenArray [ i + 1 : ] ... )
i --
2022-11-03 13:54:49 +00:00
}
}
}
}
func ( c * compiler ) compileImage ( obj * d2graph . Object ) {
if obj . Attributes . Icon == nil {
c . errorf ( obj . Attributes . Shape . MapKey . Range . Start , obj . Attributes . Shape . MapKey . Range . End , ` image shape must include an "icon" field ` )
}
}
func ( c * compiler ) compileClass ( obj * d2graph . Object ) {
obj . Class = & d2target . Class { }
for _ , f := range obj . ChildrenArray {
if f . IDVal == "style" {
continue
}
visiblity := "public"
name := f . IDVal
// See https://www.uml-diagrams.org/visibility.html
if name != "" {
switch name [ 0 ] {
case '+' :
name = name [ 1 : ]
case '-' :
visiblity = "private"
name = name [ 1 : ]
case '#' :
visiblity = "protected"
name = name [ 1 : ]
}
}
if ! strings . Contains ( f . IDVal , "(" ) {
typ := f . Attributes . Label . Value
if typ == f . IDVal {
typ = ""
}
obj . Class . Fields = append ( obj . Class . Fields , d2target . ClassField {
Name : name ,
Type : typ ,
Visibility : visiblity ,
} )
} else {
// TODO: Not great, AST should easily allow specifying alternate primary field
// as an explicit label should change the name.
returnType := f . Attributes . Label . Value
if returnType == f . IDVal {
returnType = "void"
}
obj . Class . Methods = append ( obj . Class . Methods , d2target . ClassMethod {
Name : name ,
Return : returnType ,
Visibility : visiblity ,
} )
}
}
}
func ( c * compiler ) compileSQLTable ( obj * d2graph . Object ) {
obj . SQLTable = & d2target . SQLTable { }
parentID := obj . Parent . AbsID ( )
tableID := obj . AbsID ( )
for _ , col := range obj . ChildrenArray {
if col . IDVal == "style" {
continue
}
typ := col . Attributes . Label . Value
if typ == col . IDVal {
// Not great, AST should easily allow specifying alternate primary field
// as an explicit label should change the name.
typ = ""
}
d2Col := d2target . SQLColumn {
2022-12-20 01:14:39 +00:00
Name : d2target . Text { Label : col . IDVal } ,
Type : d2target . Text { Label : typ } ,
2022-11-03 13:54:49 +00:00
}
// The only map a sql table field could have is to specify constraint
if col . Map != nil {
for _ , n := range col . Map . Nodes {
if n . MapKey . Key == nil || len ( n . MapKey . Key . Path ) == 0 {
continue
}
if n . MapKey . Key . Path [ 0 ] . Unbox ( ) . ScalarString ( ) == "constraint" {
2022-12-24 21:11:45 +00:00
if n . MapKey . Value . StringBox ( ) . Unbox ( ) == nil {
c . errorf ( n . MapKey . GetRange ( ) . Start , n . MapKey . GetRange ( ) . End , "constraint value must be a string" )
return
}
2022-11-03 13:54:49 +00:00
d2Col . Constraint = n . MapKey . Value . StringBox ( ) . Unbox ( ) . ScalarString ( )
}
}
}
absID := col . AbsID ( )
for _ , e := range obj . Graph . Edges {
srcID := e . Src . AbsID ( )
dstID := e . Dst . AbsID ( )
// skip edges between columns of the same table
if strings . HasPrefix ( srcID , tableID ) && strings . HasPrefix ( dstID , tableID ) {
continue
}
if srcID == absID {
d2Col . Reference = strings . TrimPrefix ( dstID , parentID + "." )
2022-11-16 18:54:34 +00:00
e . SrcTableColumnIndex = new ( int )
* e . SrcTableColumnIndex = len ( obj . SQLTable . Columns )
2022-11-16 14:51:55 +00:00
} else if dstID == absID {
2022-11-16 18:54:34 +00:00
e . DstTableColumnIndex = new ( int )
* e . DstTableColumnIndex = len ( obj . SQLTable . Columns )
2022-11-03 13:54:49 +00:00
}
}
obj . SQLTable . Columns = append ( obj . SQLTable . Columns , d2Col )
}
}
func flattenContainer ( g * d2graph . Graph , obj * d2graph . Object ) {
absID := obj . AbsID ( )
toRemove := map [ * d2graph . Edge ] struct { } { }
toAdd := [ ] * d2graph . Edge { }
for i := 0 ; i < len ( g . Edges ) ; i ++ {
e := g . Edges [ i ]
srcID := e . Src . AbsID ( )
dstID := e . Dst . AbsID ( )
srcIsChild := strings . HasPrefix ( srcID , absID + "." )
dstIsChild := strings . HasPrefix ( dstID , absID + "." )
if srcIsChild && dstIsChild {
toRemove [ e ] = struct { } { }
} else if srcIsChild {
toRemove [ e ] = struct { } { }
if dstID == absID {
continue
}
toAdd = append ( toAdd , e )
} else if dstIsChild {
toRemove [ e ] = struct { } { }
if srcID == absID {
continue
}
toAdd = append ( toAdd , e )
}
}
for _ , e := range toAdd {
var newEdge * d2graph . Edge
if strings . HasPrefix ( e . Src . AbsID ( ) , absID + "." ) {
newEdge , _ = g . Root . Connect ( obj . AbsIDArray ( ) , e . Dst . AbsIDArray ( ) , e . SrcArrow , e . DstArrow , e . Attributes . Label . Value )
} else {
newEdge , _ = g . Root . Connect ( e . Src . AbsIDArray ( ) , obj . AbsIDArray ( ) , e . SrcArrow , e . DstArrow , e . Attributes . Label . Value )
}
// TODO more attributes
2022-11-16 18:54:34 +00:00
if e . SrcTableColumnIndex != nil {
newEdge . SrcTableColumnIndex = new ( int )
* newEdge . SrcTableColumnIndex = * e . SrcTableColumnIndex
2022-11-16 14:56:28 +00:00
}
2022-11-16 18:54:34 +00:00
if e . DstTableColumnIndex != nil {
newEdge . DstTableColumnIndex = new ( int )
* newEdge . DstTableColumnIndex = * e . DstTableColumnIndex
2022-11-16 14:56:28 +00:00
}
2022-11-03 13:54:49 +00:00
newEdge . Attributes . Label = e . Attributes . Label
newEdge . References = e . References
}
updatedEdges := [ ] * d2graph . Edge { }
for _ , e := range g . Edges {
if _ , is := toRemove [ e ] ; is {
continue
}
updatedEdges = append ( updatedEdges , e )
}
g . Edges = updatedEdges
for i := 0 ; i < len ( g . Objects ) ; i ++ {
child := g . Objects [ i ]
if strings . HasPrefix ( child . AbsID ( ) , absID + "." ) {
g . Objects = append ( g . Objects [ : i ] , g . Objects [ i + 1 : ] ... )
i --
delete ( obj . Children , child . ID )
for i , child2 := range obj . ChildrenArray {
if child == child2 {
obj . ChildrenArray = append ( obj . ChildrenArray [ : i ] , obj . ChildrenArray [ i + 1 : ] ... )
break
}
}
}
}
}
func ( c * compiler ) validateKey ( obj * d2graph . Object , m * d2ast . Map , mk * d2ast . Key ) {
ida , reserved , ok := c . compileFlatKey ( mk . Key )
if ! ok {
return
}
if reserved == "" && obj . Attributes . Shape . Value == d2target . ShapeImage {
c . errorf ( mk . Range . Start , mk . Range . End , "image shapes cannot have children." )
}
if reserved == "width" && obj . Attributes . Shape . Value != d2target . ShapeImage {
c . errorf ( mk . Range . Start , mk . Range . End , "width is only applicable to image shapes." )
}
if reserved == "height" && obj . Attributes . Shape . Value != d2target . ShapeImage {
c . errorf ( mk . Range . Start , mk . Range . End , "height is only applicable to image shapes." )
}
in := d2target . IsShape ( obj . Attributes . Shape . Value )
_ , arrowheadIn := d2target . Arrowheads [ obj . Attributes . Shape . Value ]
if ! in && arrowheadIn {
c . errorf ( mk . Range . Start , mk . Range . End , fmt . Sprintf ( ` invalid shape, can only set "%s" for arrowheads ` , obj . Attributes . Shape . Value ) )
}
resolvedObj , resolvedIDA , err := d2graph . ResolveUnderscoreKey ( ida , obj )
if err != nil {
c . errorf ( mk . Range . Start , mk . Range . End , err . Error ( ) )
return
}
if resolvedObj != obj {
obj = resolvedObj
}
parent := obj
if len ( resolvedIDA ) > 0 {
obj , _ = parent . HasChild ( resolvedIDA )
} else if obj . Parent == nil {
return
}
if len ( mk . Edges ) > 0 {
return
}
if mk . Value . Map != nil {
c . validateKeys ( obj , mk . Value . Map )
}
}
func ( c * compiler ) validateKeys ( obj * d2graph . Object , m * d2ast . Map ) {
for _ , n := range m . Nodes {
if n . MapKey != nil && n . MapKey . Key != nil && len ( n . MapKey . Edges ) == 0 {
c . validateKey ( obj , m , n . MapKey )
}
}
}
func ( c * compiler ) validateNear ( g * d2graph . Graph ) {
for _ , obj := range g . Objects {
if obj . Attributes . NearKey != nil {
_ , ok := g . Root . HasChild ( d2graph . Key ( obj . Attributes . NearKey ) )
if ! ok {
c . errorf ( obj . Attributes . NearKey . GetRange ( ) . Start , obj . Attributes . NearKey . GetRange ( ) . End , "near key %#v does not exist. It must be the absolute path to a shape." , d2format . Format ( obj . Attributes . NearKey ) )
continue
}
}
}
}
func init ( ) {
FullToShortLanguageAliases = make ( map [ string ] string , len ( ShortToFullLanguageAliases ) )
for k , v := range ShortToFullLanguageAliases {
FullToShortLanguageAliases [ v ] = k
}
}