2023-04-03 20:40:40 +00:00
|
|
|
//go:build wasm
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
2023-06-15 07:15:43 +00:00
|
|
|
"io"
|
2023-04-03 20:40:40 +00:00
|
|
|
"io/fs"
|
2023-06-15 07:15:43 +00:00
|
|
|
"os"
|
2023-04-03 20:40:40 +00:00
|
|
|
"strings"
|
|
|
|
|
"syscall/js"
|
|
|
|
|
|
|
|
|
|
"oss.terrastruct.com/d2/d2ast"
|
|
|
|
|
"oss.terrastruct.com/d2/d2compiler"
|
|
|
|
|
"oss.terrastruct.com/d2/d2format"
|
2024-10-11 02:29:39 +00:00
|
|
|
"oss.terrastruct.com/d2/d2lsp"
|
2023-04-03 20:40:40 +00:00
|
|
|
"oss.terrastruct.com/d2/d2oracle"
|
|
|
|
|
"oss.terrastruct.com/d2/d2parser"
|
|
|
|
|
"oss.terrastruct.com/d2/lib/urlenc"
|
2024-09-04 18:20:16 +00:00
|
|
|
"oss.terrastruct.com/d2/lib/version"
|
2023-04-03 20:40:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
2024-01-20 19:13:34 +00:00
|
|
|
js.Global().Set("d2GetParentID", js.FuncOf(jsGetParentID))
|
2023-04-03 20:40:40 +00:00
|
|
|
js.Global().Set("d2GetObjOrder", js.FuncOf(jsGetObjOrder))
|
|
|
|
|
js.Global().Set("d2GetRefRanges", js.FuncOf(jsGetRefRanges))
|
|
|
|
|
js.Global().Set("d2Compile", js.FuncOf(jsCompile))
|
2024-11-13 19:17:49 +00:00
|
|
|
js.Global().Set("d2GetBoardAtPosition", js.FuncOf(jsGetBoardAtPosition))
|
2023-04-03 20:40:40 +00:00
|
|
|
js.Global().Set("d2Parse", js.FuncOf(jsParse))
|
|
|
|
|
js.Global().Set("d2Encode", js.FuncOf(jsEncode))
|
|
|
|
|
js.Global().Set("d2Decode", js.FuncOf(jsDecode))
|
2024-09-04 18:20:16 +00:00
|
|
|
js.Global().Set("d2Version", js.FuncOf(jsVersion))
|
2023-09-25 18:29:33 +00:00
|
|
|
initCallback := js.Global().Get("onWasmInitialized")
|
|
|
|
|
if !initCallback.IsUndefined() {
|
|
|
|
|
initCallback.Invoke()
|
|
|
|
|
}
|
2023-04-03 20:40:40 +00:00
|
|
|
select {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type jsObjOrder struct {
|
|
|
|
|
Order []string `json:"order"`
|
|
|
|
|
Error string `json:"error"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jsGetObjOrder(this js.Value, args []js.Value) interface{} {
|
|
|
|
|
dsl := args[0].String()
|
|
|
|
|
|
2023-07-22 17:03:26 +00:00
|
|
|
g, _, err := d2compiler.Compile("", strings.NewReader(dsl), &d2compiler.CompileOptions{
|
2023-08-04 17:54:33 +00:00
|
|
|
UTF16Pos: true,
|
2023-04-03 20:40:40 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
ret := jsObjOrder{Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-22 17:03:26 +00:00
|
|
|
objOrder, err := d2oracle.GetObjOrder(g, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ret := jsObjOrder{Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
2023-04-03 20:40:40 +00:00
|
|
|
resp := jsObjOrder{
|
2023-07-22 17:03:26 +00:00
|
|
|
Order: objOrder,
|
2023-04-03 20:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
str, _ := json.Marshal(resp)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-20 19:13:34 +00:00
|
|
|
func jsGetParentID(this js.Value, args []js.Value) interface{} {
|
|
|
|
|
id := args[0].String()
|
|
|
|
|
|
|
|
|
|
mk, _ := d2parser.ParseMapKey(id)
|
|
|
|
|
|
|
|
|
|
if len(mk.Edges) > 0 {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mk.Key != nil {
|
|
|
|
|
if len(mk.Key.Path) == 1 {
|
|
|
|
|
return "root"
|
|
|
|
|
}
|
|
|
|
|
mk.Key.Path = mk.Key.Path[:len(mk.Key.Path)-1]
|
2024-12-12 22:27:28 +00:00
|
|
|
return strings.Join(mk.Key.StringIDA(), ".")
|
2024-01-20 19:13:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-03 20:40:40 +00:00
|
|
|
type jsRefRanges struct {
|
2024-10-19 06:09:04 +00:00
|
|
|
Ranges []d2ast.Range `json:"ranges"`
|
|
|
|
|
ImportRanges []d2ast.Range `json:"importRanges"`
|
|
|
|
|
ParseError string `json:"parseError"`
|
|
|
|
|
UserError string `json:"userError"`
|
|
|
|
|
D2Error string `json:"d2Error"`
|
2023-04-03 20:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jsGetRefRanges(this js.Value, args []js.Value) interface{} {
|
2024-10-11 02:29:39 +00:00
|
|
|
fsRaw := args[0].String()
|
2024-10-11 20:18:22 +00:00
|
|
|
file := args[1].String()
|
|
|
|
|
key := args[2].String()
|
2024-10-12 00:49:16 +00:00
|
|
|
boardPathRaw := args[3].String()
|
2023-04-03 20:40:40 +00:00
|
|
|
|
2024-10-11 02:29:39 +00:00
|
|
|
var fs map[string]string
|
|
|
|
|
err := json.Unmarshal([]byte(fsRaw), &fs)
|
2023-04-03 20:40:40 +00:00
|
|
|
if err != nil {
|
|
|
|
|
ret := jsRefRanges{D2Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 02:29:39 +00:00
|
|
|
_, err = d2parser.ParseMapKey(key)
|
2023-04-03 20:40:40 +00:00
|
|
|
if err != nil {
|
|
|
|
|
ret := jsRefRanges{D2Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-12 00:49:16 +00:00
|
|
|
var boardPath []string
|
|
|
|
|
err = json.Unmarshal([]byte(boardPathRaw), &boardPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ret := jsRefRanges{D2Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-19 06:09:04 +00:00
|
|
|
ranges, importRanges, err := d2lsp.GetRefRanges(file, fs, boardPath, key)
|
2024-10-17 15:04:11 +00:00
|
|
|
if err != nil {
|
|
|
|
|
ret := jsRefRanges{D2Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := jsRefRanges{
|
2024-10-19 06:09:04 +00:00
|
|
|
Ranges: ranges,
|
|
|
|
|
ImportRanges: importRanges,
|
2024-10-17 15:04:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
str, _ := json.Marshal(resp)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-03 20:40:40 +00:00
|
|
|
type jsObject struct {
|
|
|
|
|
Result string `json:"result"`
|
|
|
|
|
UserError string `json:"userError"`
|
|
|
|
|
D2Error string `json:"d2Error"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type jsParseResponse struct {
|
2023-06-14 21:57:43 +00:00
|
|
|
DSL string `json:"dsl"`
|
|
|
|
|
ParseError string `json:"parseError"`
|
|
|
|
|
UserError string `json:"userError"`
|
|
|
|
|
D2Error string `json:"d2Error"`
|
2023-04-03 20:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
2023-06-15 07:15:43 +00:00
|
|
|
type emptyFile struct{}
|
2023-04-03 20:40:40 +00:00
|
|
|
|
2023-06-15 07:15:43 +00:00
|
|
|
func (f *emptyFile) Stat() (os.FileInfo, error) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *emptyFile) Read(p []byte) (int, error) {
|
|
|
|
|
return 0, io.EOF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *emptyFile) Close() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type detectFS struct {
|
|
|
|
|
importUsed bool
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-04 17:54:33 +00:00
|
|
|
func (detectFS *detectFS) Open(name string) (fs.File, error) {
|
2023-06-15 07:15:43 +00:00
|
|
|
detectFS.importUsed = true
|
|
|
|
|
return &emptyFile{}, nil
|
2023-04-03 20:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jsParse(this js.Value, args []js.Value) interface{} {
|
|
|
|
|
dsl := args[0].String()
|
|
|
|
|
themeID := args[1].Int()
|
|
|
|
|
|
2023-06-15 07:15:43 +00:00
|
|
|
detectFS := detectFS{}
|
|
|
|
|
|
2023-07-22 17:03:26 +00:00
|
|
|
g, _, err := d2compiler.Compile("", strings.NewReader(dsl), &d2compiler.CompileOptions{
|
2023-08-04 17:54:33 +00:00
|
|
|
UTF16Pos: true,
|
|
|
|
|
FS: &detectFS,
|
2023-04-03 20:40:40 +00:00
|
|
|
})
|
2023-06-15 07:15:43 +00:00
|
|
|
// If an import was used, client side D2 cannot reliably compile
|
|
|
|
|
// Defer to backend compilation
|
|
|
|
|
if !detectFS.importUsed {
|
|
|
|
|
var pe *d2parser.ParseError
|
|
|
|
|
if err != nil {
|
|
|
|
|
if errors.As(err, &pe) {
|
|
|
|
|
serialized, _ := json.Marshal(err)
|
|
|
|
|
ret := jsParseResponse{ParseError: string(serialized)}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
ret := jsParseResponse{D2Error: err.Error()}
|
2023-04-03 20:40:40 +00:00
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-15 07:15:43 +00:00
|
|
|
for _, o := range g.Objects {
|
|
|
|
|
if (o.Attributes.Top == nil) != (o.Attributes.Left == nil) {
|
|
|
|
|
ret := jsParseResponse{UserError: `keywords "top" and "left" currently must be used together`}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = g.ApplyTheme(int64(themeID))
|
|
|
|
|
if err != nil {
|
|
|
|
|
ret := jsParseResponse{D2Error: err.Error()}
|
2023-04-03 20:40:40 +00:00
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-02 21:57:37 +00:00
|
|
|
m, err := d2parser.Parse("", strings.NewReader(dsl), &d2parser.ParseOptions{
|
|
|
|
|
UTF16Pos: true,
|
|
|
|
|
})
|
2023-06-14 21:57:43 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2023-04-03 20:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
2023-06-14 21:57:43 +00:00
|
|
|
resp := jsParseResponse{}
|
|
|
|
|
|
|
|
|
|
newDSL := d2format.Format(m)
|
2023-04-03 20:40:40 +00:00
|
|
|
if dsl != newDSL {
|
|
|
|
|
resp.DSL = newDSL
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
str, _ := json.Marshal(resp)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO error passing
|
|
|
|
|
// TODO recover panics
|
|
|
|
|
func jsCompile(this js.Value, args []js.Value) interface{} {
|
|
|
|
|
script := args[0].String()
|
|
|
|
|
|
2023-07-22 17:03:26 +00:00
|
|
|
g, _, err := d2compiler.Compile("", strings.NewReader(script), &d2compiler.CompileOptions{
|
2023-08-04 17:54:33 +00:00
|
|
|
UTF16Pos: true,
|
2023-04-03 20:40:40 +00:00
|
|
|
})
|
|
|
|
|
var pe *d2parser.ParseError
|
|
|
|
|
if err != nil {
|
|
|
|
|
if errors.As(err, &pe) {
|
|
|
|
|
serialized, _ := json.Marshal(err)
|
|
|
|
|
ret := jsObject{UserError: string(serialized)}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
ret := jsObject{D2Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newScript := d2format.Format(g.AST)
|
|
|
|
|
if script != newScript {
|
|
|
|
|
ret := jsObject{Result: newScript}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jsEncode(this js.Value, args []js.Value) interface{} {
|
|
|
|
|
script := args[0].String()
|
|
|
|
|
|
|
|
|
|
encoded, err := urlenc.Encode(script)
|
|
|
|
|
// should never happen
|
|
|
|
|
if err != nil {
|
|
|
|
|
ret := jsObject{D2Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret := jsObject{Result: encoded}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jsDecode(this js.Value, args []js.Value) interface{} {
|
|
|
|
|
script := args[0].String()
|
|
|
|
|
|
|
|
|
|
script, err := urlenc.Decode(script)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ret := jsObject{UserError: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret := jsObject{Result: script}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
2024-09-04 18:20:16 +00:00
|
|
|
|
|
|
|
|
func jsVersion(this js.Value, args []js.Value) interface{} {
|
|
|
|
|
return version.Version
|
|
|
|
|
}
|
2024-11-13 19:17:49 +00:00
|
|
|
|
|
|
|
|
type jsBoardAtPosition struct {
|
|
|
|
|
BoardPath []string `json:"boardPath"`
|
|
|
|
|
Error string `json:"error"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jsGetBoardAtPosition(this js.Value, args []js.Value) interface{} {
|
|
|
|
|
dsl := args[0].String()
|
|
|
|
|
line := args[1].Int()
|
|
|
|
|
column := args[2].Int()
|
|
|
|
|
|
|
|
|
|
boardPath, err := d2lsp.GetBoardAtPosition(dsl, d2ast.Position{
|
|
|
|
|
Line: line,
|
|
|
|
|
Column: column,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
ret := jsBoardAtPosition{Error: err.Error()}
|
|
|
|
|
str, _ := json.Marshal(ret)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := jsBoardAtPosition{
|
|
|
|
|
BoardPath: boardPath,
|
|
|
|
|
}
|
|
|
|
|
str, _ := json.Marshal(resp)
|
|
|
|
|
return string(str)
|
|
|
|
|
}
|