diff --git a/go.mod b/go.mod index 6f32f01d927ac421bbf4cb6eac83b01261514c4d..265f014b4df155bf85307db8dabfec58eacb66c2 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect github.com/dgrijalva/jwt-go v3.2.1-0.20200107013213-dc14462fd587+incompatible github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/dop251/goja v0.0.0-20201212162034-be0895b77e07 + github.com/dop251/goja v0.0.0-20210106133455-27b0a7dc4c7f github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect @@ -52,7 +52,7 @@ require ( golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321 // indirect golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c // indirect - golang.org/x/text v0.3.4 // indirect + golang.org/x/text v0.3.5 // indirect google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 google.golang.org/grpc v1.33.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0 diff --git a/go.sum b/go.sum index 11765c81f89dd037e0b25c3fabee52c0d067b99f..8beffb9d4a31fb5e896e94a192446e855493917a 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/dgrijalva/jwt-go v3.2.1-0.20200107013213-dc14462fd587+incompatible h1 github.com/dgrijalva/jwt-go v3.2.1-0.20200107013213-dc14462fd587+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dop251/goja v0.0.0-20201212162034-be0895b77e07 h1:Fn066OGb3xiuFSljnjA5gq7zzNj/4Df2St727lGnhMI= -github.com/dop251/goja v0.0.0-20201212162034-be0895b77e07/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/dop251/goja v0.0.0-20210106133455-27b0a7dc4c7f h1:tHoCUlro23JdXvcvYXH1AseEyWJf+b8cBbVm16RYRkU= +github.com/dop251/goja v0.0.0-20210106133455-27b0a7dc4c7f/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -205,8 +205,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1/go.mod h1:oVMjMN64nzEcepv1kdZKg github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/heroiclabs/nakama-common v1.10.1-0.20201216142705-136e6c0d214f h1:RTKN7L8sWQjsyBEv4fW65MsHbLxnO/NbgR/l+M3uaF4= -github.com/heroiclabs/nakama-common v1.10.1-0.20201216142705-136e6c0d214f/go.mod h1:li7bMQwOYA0NjT3DM4NKQBNruULPa2hrqdiSaaTwui4= github.com/heroiclabs/nakama-common v1.10.1-0.20210112124918-da701a0f7020 h1:6jaVk2j7QSdSb4APKMQjDDa/s6FgxcZF+vAKszoCbkg= github.com/heroiclabs/nakama-common v1.10.1-0.20210112124918-da701a0f7020/go.mod h1:li7bMQwOYA0NjT3DM4NKQBNruULPa2hrqdiSaaTwui4= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -461,8 +459,8 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/server/core_file.go b/server/core_file.go new file mode 100644 index 0000000000000000000000000000000000000000..04c5eb257172dafdd6ba1c09b9aeb4fcea91bf16 --- /dev/null +++ b/server/core_file.go @@ -0,0 +1,32 @@ +// Copyright 2020 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "os" + "path/filepath" +) + +// Open a file relative to the runtime path. +func FileRead(rootPath, relPath string) (*os.File, error) { + path := filepath.Join(rootPath, relPath) + + f, err := os.Open(path) + if err != nil { + return nil, err + } + + return f, nil +} diff --git a/server/runtime_go_nakama.go b/server/runtime_go_nakama.go index 9345a7331f4210c2da3b2eedfe9b516dcff6223b..c2c0784d3ec0cea53bb8c3d8e1a92f143b555228 100644 --- a/server/runtime_go_nakama.go +++ b/server/runtime_go_nakama.go @@ -21,6 +21,7 @@ import ( "encoding/base64" "encoding/gob" "encoding/json" + "os" "strings" "sync" "time" @@ -559,6 +560,10 @@ func (n *RuntimeGoNakamaModule) LinkSteam(ctx context.Context, userID, token str return LinkSteam(ctx, n.logger, n.db, n.config, n.socialClient, id, token) } +func (n *RuntimeGoNakamaModule) ReadFile(relPath string) (*os.File, error) { + return FileRead(n.config.GetRuntime().Path, relPath) +} + func (n *RuntimeGoNakamaModule) UnlinkApple(ctx context.Context, userID, token string) error { id, err := uuid.FromString(userID) if err != nil { diff --git a/server/runtime_javascript.go b/server/runtime_javascript.go index cbb403bbb34eb37d0657ef079c0f4d7a71700412..18db7ebcc8ffc382bd5bce6f82bf753d666dfbc1 100644 --- a/server/runtime_javascript.go +++ b/server/runtime_javascript.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Nakama Authors +// Copyright 2020 The Nakama Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/server/runtime_javascript_context.go b/server/runtime_javascript_context.go index 6a1abdca48f60f9f5419a1321362743fd2749cbe..6c557bdd333459ca2e2d9eb185ce9aeda638c2a7 100644 --- a/server/runtime_javascript_context.go +++ b/server/runtime_javascript_context.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Nakama Authors +// Copyright 2020 The Nakama Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/server/runtime_javascript_init.go b/server/runtime_javascript_init.go index abe5093500f9b456e8d3432019faccf1e3153741..e4141f60a01e5ebe20e776cdd34b6acf2545ede7 100644 --- a/server/runtime_javascript_init.go +++ b/server/runtime_javascript_init.go @@ -1,3 +1,17 @@ +// Copyright 2020 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package server import ( diff --git a/server/runtime_javascript_logger.go b/server/runtime_javascript_logger.go index 022fe404990c05061f4e7716a9faa5a8271327ec..aef1f2d4d6c77b422db5527e48a69d2069228539 100644 --- a/server/runtime_javascript_logger.go +++ b/server/runtime_javascript_logger.go @@ -1,3 +1,17 @@ +// Copyright 2020 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package server import ( @@ -142,4 +156,3 @@ func freeze(o *goja.Object) { o.DefineDataProperty(key, o.Get(key), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_TRUE) } } - diff --git a/server/runtime_javascript_logger_test.go b/server/runtime_javascript_logger_test.go index ebbb20d953b06a72dfe5c629b560795896494923..fb5fb1f1117ba3fff054a09a327ab3fbef10cb6b 100644 --- a/server/runtime_javascript_logger_test.go +++ b/server/runtime_javascript_logger_test.go @@ -1,3 +1,17 @@ +// Copyright 2018 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package server import ( @@ -190,4 +204,3 @@ l2.info('logger two') {Key: "n", Integer: 1, Type: zapcore.Int64Type}, }, secondLog.Context) } - diff --git a/server/runtime_javascript_match_core.go b/server/runtime_javascript_match_core.go index c274001e121a3238db9f7eeb61ecdaf148c47fde..177e8c66a081ad713e34569de1dc25d7a5bad0ff 100644 --- a/server/runtime_javascript_match_core.go +++ b/server/runtime_javascript_match_core.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Nakama Authors +// Copyright 2020 The Nakama Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/server/runtime_javascript_nakama.go b/server/runtime_javascript_nakama.go index 98cbae17e13e8a6aa9276e06149a744683965f2e..906bd193737dd08785a4c3329544fd50782bc2e1 100644 --- a/server/runtime_javascript_nakama.go +++ b/server/runtime_javascript_nakama.go @@ -1,3 +1,17 @@ +// Copyright 2020 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package server import ( @@ -26,9 +40,8 @@ import ( "strings" "time" - "github.com/heroiclabs/nakama/v2/internal/cronexpr" - "github.com/heroiclabs/nakama-common/rtapi" + "github.com/heroiclabs/nakama/v2/internal/cronexpr" "github.com/dgrijalva/jwt-go" "github.com/dop251/goja" @@ -211,6 +224,7 @@ func (n *runtimeJavascriptNakamaModule) mappings(r *goja.Runtime) map[string]fun "groupUsersAdd": n.groupUsersAdd(r), "groupUsersPromote": n.groupUsersPromote(r), "groupUsersDemote": n.groupUsersDemote(r), + "fileRead": n.fileRead(r), } } @@ -5496,6 +5510,30 @@ func (n *runtimeJavascriptNakamaModule) groupUsersDemote(r *goja.Runtime) func(g } } +func (n *runtimeJavascriptNakamaModule) fileRead(r *goja.Runtime) func(goja.FunctionCall) goja.Value { + return func(f goja.FunctionCall) goja.Value { + relPath := getJsString(r, f.Argument(0)) + if relPath == "" { + panic(r.NewTypeError("expects relative path string")) + } + + rootPath := n.config.GetRuntime().Path + + file, err := FileRead(rootPath, relPath) + if err != nil { + panic(r.NewGoError(fmt.Errorf("failed to open file: %s", err.Error()))) + } + defer file.Close() + + fContent, err := ioutil.ReadAll(file) + if err != nil { + panic(r.NewGoError(fmt.Errorf("failed to read file: %s", err.Error()))) + } + + return r.ToValue(string(fContent)) + } +} + func getJsString(r *goja.Runtime, v goja.Value) string { s, ok := v.Export().(string) if !ok { diff --git a/server/runtime_lua_nakama.go b/server/runtime_lua_nakama.go index 088c188bd4db95f20ce24f04016b21039e32861d..d42e3ac792841b17d65a16bb7eceecdd4779f473 100644 --- a/server/runtime_lua_nakama.go +++ b/server/runtime_lua_nakama.go @@ -247,6 +247,7 @@ func (n *RuntimeLuaNakamaModule) Loader(l *lua.LState) int { "group_users_kick": n.groupUsersKick, "user_groups_list": n.userGroupsList, "friends_list": n.friendsList, + "fileRead": n.fileRead, } mod := l.SetFuncs(l.CreateTable(0, len(functions)), functions) @@ -6973,3 +6974,29 @@ func (n *RuntimeLuaNakamaModule) friendsList(l *lua.LState) int { } return 2 } + +func (n *RuntimeLuaNakamaModule) fileRead(l *lua.LState) int { + relPath := l.CheckString(1) + if relPath == "" { + l.ArgError(3, "expects relative path string") + return 0 + } + + rootPath := n.config.GetRuntime().Path + + f, err := FileRead(rootPath, relPath) + if err != nil { + l.RaiseError(fmt.Sprintf("failed to open file: %s", err.Error())) + return 0 + } + defer f.Close() + + fileContent, err := ioutil.ReadAll(f) + if err != nil { + l.RaiseError(fmt.Sprintf("failed to read file: %s", err.Error())) + return 0 + } + + l.Push(lua.LString(string(fileContent))) + return 1 +} diff --git a/vendor/github.com/dop251/goja/ast/node.go b/vendor/github.com/dop251/goja/ast/node.go index a0d2855944d91b518556d1232ba4b40e49774176..9472e4ac587b180180343a755975191c1e6a94c6 100644 --- a/vendor/github.com/dop251/goja/ast/node.go +++ b/vendor/github.com/dop251/goja/ast/node.go @@ -13,7 +13,6 @@ import ( "github.com/dop251/goja/file" "github.com/dop251/goja/token" "github.com/dop251/goja/unistring" - "github.com/go-sourcemap/sourcemap" ) // All nodes implement the Node interface. @@ -399,8 +398,6 @@ type Program struct { DeclarationList []Declaration File *file.File - - SourceMap *sourcemap.Consumer } // ==== // diff --git a/vendor/github.com/dop251/goja/compiler.go b/vendor/github.com/dop251/goja/compiler.go index 9375ef4d2c38d3f96954c47f9dc3f2ad9498acfe..3843a74e676f1eeeb0d13fa97e90dff693ab5114 100644 --- a/vendor/github.com/dop251/goja/compiler.go +++ b/vendor/github.com/dop251/goja/compiler.go @@ -21,7 +21,7 @@ const ( type CompilerError struct { Message string - File *SrcFile + File *file.File Offset int } @@ -43,7 +43,7 @@ type Program struct { values []Value funcName unistring.String - src *SrcFile + src *file.File srcMap []srcMapItem } @@ -250,7 +250,7 @@ func (c *compiler) markBlockStart() { } func (c *compiler) compile(in *ast.Program) { - c.p.src = NewSrcFile(in.File.Name(), in.File.Source(), in.SourceMap) + c.p.src = in.File if len(in.Body) > 0 { if !c.scope.strict { diff --git a/vendor/github.com/dop251/goja/file/file.go b/vendor/github.com/dop251/goja/file/file.go index 76524ac39e1c5daa205e9225f9969265e85dd41b..78ae1ad9011f572adce4933b5460cca05e58cb13 100644 --- a/vendor/github.com/dop251/goja/file/file.go +++ b/vendor/github.com/dop251/goja/file/file.go @@ -4,7 +4,11 @@ package file import ( "fmt" - "strings" + "path" + "sort" + "sync" + + "github.com/go-sourcemap/sourcemap" ) // Idx is a compact encoding of a source position within a file set. @@ -16,7 +20,6 @@ type Idx int // including the filename, line, and column location. type Position struct { Filename string // The filename where the error occurred, if any - Offset int // The src offset Line int // The line number, starting at 1 Column int // The column number, starting at 1 (The character count) @@ -35,7 +38,7 @@ func (self *Position) isValid() bool { // file An invalid position with filename // - An invalid position without filename // -func (self *Position) String() string { +func (self Position) String() string { str := self.Filename if self.isValid() { if str != "" { @@ -89,29 +92,23 @@ func (self *FileSet) File(idx Idx) *File { } // Position converts an Idx in the FileSet into a Position. -func (self *FileSet) Position(idx Idx) *Position { - position := &Position{} +func (self *FileSet) Position(idx Idx) Position { for _, file := range self.files { if idx <= Idx(file.base+len(file.src)) { - offset := int(idx) - file.base - src := file.src[:offset] - position.Filename = file.name - position.Offset = offset - position.Line = 1 + strings.Count(src, "\n") - if index := strings.LastIndex(src, "\n"); index >= 0 { - position.Column = offset - index - } else { - position.Column = 1 + len(src) - } + return file.Position(int(idx) - file.base) } } - return position + return Position{} } type File struct { - name string - src string - base int // This will always be 1 or greater + mu sync.Mutex + name string + src string + base int // This will always be 1 or greater + sourceMap *sourcemap.Consumer + lineOffsets []int + lastScannedOffset int } func NewFile(filename, src string, base int) *File { @@ -133,3 +130,86 @@ func (fl *File) Source() string { func (fl *File) Base() int { return fl.base } + +func (fl *File) SetSourceMap(m *sourcemap.Consumer) { + fl.sourceMap = m +} + +func (fl *File) Position(offset int) Position { + var line int + var lineOffsets []int + fl.mu.Lock() + if offset > fl.lastScannedOffset { + line = fl.scanTo(offset) + lineOffsets = fl.lineOffsets + fl.mu.Unlock() + } else { + lineOffsets = fl.lineOffsets + fl.mu.Unlock() + line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1 + } + + var lineStart int + if line >= 0 { + lineStart = lineOffsets[line] + } + + row := line + 2 + col := offset - lineStart + 1 + + if fl.sourceMap != nil { + if source, _, row, col, ok := fl.sourceMap.Source(row, col); ok { + if !path.IsAbs(source) { + source = path.Join(path.Dir(fl.name), source) + } + return Position{ + Filename: source, + Line: row, + Column: col, + } + } + } + + return Position{ + Filename: fl.name, + Line: row, + Column: col, + } +} + +func findNextLineStart(s string) int { + for pos, ch := range s { + switch ch { + case '\r': + if pos < len(s)-1 && s[pos+1] == '\n' { + return pos + 2 + } + return pos + 1 + case '\n': + return pos + 1 + case '\u2028', '\u2029': + return pos + 3 + } + } + return -1 +} + +func (fl *File) scanTo(offset int) int { + o := fl.lastScannedOffset + for o < offset { + p := findNextLineStart(fl.src[o:]) + if p == -1 { + fl.lastScannedOffset = len(fl.src) + return len(fl.lineOffsets) - 1 + } + o = o + p + fl.lineOffsets = append(fl.lineOffsets, o) + } + fl.lastScannedOffset = o + + if o == offset { + return len(fl.lineOffsets) - 1 + } + + return len(fl.lineOffsets) - 2 +} diff --git a/vendor/github.com/dop251/goja/parser/parser.go b/vendor/github.com/dop251/goja/parser/parser.go index a5784d33baeaafbae0204e6efb3e7d411ce75e55..b1bc74bb195e36a37341d8b028d571b6b83acbac 100644 --- a/vendor/github.com/dop251/goja/parser/parser.go +++ b/vendor/github.com/dop251/goja/parser/parser.go @@ -52,6 +52,32 @@ const ( IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking) ) +type options struct { + disableSourceMaps bool + sourceMapLoader func(path string) ([]byte, error) +} + +// Option represents one of the options for the parser to use in the Parse methods. Currently supported are: +// WithDisableSourceMaps and WithSourceMapLoader. +type Option func(*options) + +// WithDisableSourceMaps is an option to disable source maps support. May save a bit of time when source maps +// are not in use. +func WithDisableSourceMaps(opts *options) { + opts.disableSourceMaps = true +} + +// WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a +// URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name +// of the file being parsed. Any error returned by the loader will fail the parsing. +// Note that setting this to nil does not disable source map support, there is a default loader which reads +// from the filesystem. Use WithDisableSourceMaps to disable source map support. +func WithSourceMapLoader(loader func(path string) ([]byte, error)) Option { + return func(opts *options) { + opts.sourceMapLoader = loader + } +} + type _parser struct { str string length int @@ -79,18 +105,23 @@ type _parser struct { } mode Mode + opts options file *file.File } -func _newParser(filename, src string, base int) *_parser { - return &_parser{ +func _newParser(filename, src string, base int, opts ...Option) *_parser { + p := &_parser{ chr: ' ', // This is set so we can start scanning by skipping whitespace str: src, length: len(src), base: base, file: file.NewFile(filename, src, base), } + for _, opt := range opts { + opt(&p.opts) + } + return p } func newParser(filename, src string) *_parser { @@ -133,7 +164,7 @@ func ReadSource(filename string, src interface{}) ([]byte, error) { // // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList // program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0) // -func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) { +func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode, options ...Option) (*ast.Program, error) { str, err := ReadSource(filename, src) if err != nil { return nil, err @@ -146,7 +177,7 @@ func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mod base = fileSet.AddFile(filename, str) } - parser := _newParser(filename, str, base) + parser := _newParser(filename, str, base, options...) parser.mode = mode return parser.parse() } @@ -157,11 +188,11 @@ func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mod // // The parameter list, if any, should be a comma-separated list of identifiers. // -func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) { +func ParseFunction(parameterList, body string, options ...Option) (*ast.FunctionLiteral, error) { src := "(function(" + parameterList + ") {\n" + body + "\n})" - parser := _newParser("", src, 1) + parser := _newParser("", src, 1, options...) program, err := parser.parse() if err != nil { return nil, err @@ -233,42 +264,6 @@ func (self *_parser) expect(value token.Token) file.Idx { return idx } -func lineCount(str string) (int, int) { - line, last := 0, -1 - pair := false - for index, chr := range str { - switch chr { - case '\r': - line += 1 - last = index - pair = true - continue - case '\n': - if !pair { - line += 1 - } - last = index - case '\u2028', '\u2029': - line += 1 - last = index + 2 - } - pair = false - } - return line, last -} - func (self *_parser) position(idx file.Idx) file.Position { - position := file.Position{} - offset := int(idx) - self.base - str := self.str[:offset] - position.Filename = self.file.Name() - line, last := lineCount(str) - position.Line = 1 + line - if last >= 0 { - position.Column = offset - last - } else { - position.Column = 1 + len(str) - } - - return position + return self.file.Position(int(idx) - self.base) } diff --git a/vendor/github.com/dop251/goja/parser/statement.go b/vendor/github.com/dop251/goja/parser/statement.go index 45581cec50d3dfa174157b0ef3ecb54cb8c6171b..84ce7fd9f69452d3963b5fbc5b0f2a6cefe2e247 100644 --- a/vendor/github.com/dop251/goja/parser/statement.go +++ b/vendor/github.com/dop251/goja/parser/statement.go @@ -2,13 +2,14 @@ package parser import ( "encoding/base64" + "fmt" "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" "github.com/go-sourcemap/sourcemap" "io/ioutil" "net/url" - "os" + "path" "strings" ) @@ -578,48 +579,86 @@ func (self *_parser) parseSourceElements() []ast.Statement { func (self *_parser) parseProgram() *ast.Program { self.openScope() defer self.closeScope() - return &ast.Program{ + prg := &ast.Program{ Body: self.parseSourceElements(), DeclarationList: self.scope.declarationList, File: self.file, - SourceMap: self.parseSourceMap(), } + self.file.SetSourceMap(self.parseSourceMap()) + return prg +} + +func extractSourceMapLine(str string) string { + for { + p := strings.LastIndexByte(str, '\n') + line := str[p+1:] + if line != "" && line != "})" { + if strings.HasPrefix(line, "//# sourceMappingURL=") { + return line + } + break + } + if p >= 0 { + str = str[:p] + } else { + break + } + } + return "" } func (self *_parser) parseSourceMap() *sourcemap.Consumer { - lastLine := self.str[strings.LastIndexByte(self.str, '\n')+1:] - if strings.HasPrefix(lastLine, "//# sourceMappingURL") { - urlIndex := strings.Index(lastLine, "=") - urlStr := lastLine[urlIndex+1:] + if self.opts.disableSourceMaps { + return nil + } + if smLine := extractSourceMapLine(self.str); smLine != "" { + urlIndex := strings.Index(smLine, "=") + urlStr := smLine[urlIndex+1:] var data []byte + var err error if strings.HasPrefix(urlStr, "data:application/json") { b64Index := strings.Index(urlStr, ",") b64 := urlStr[b64Index+1:] - if d, err := base64.StdEncoding.DecodeString(b64); err == nil { - data = d - } + data, err = base64.StdEncoding.DecodeString(b64) } else { - if smUrl, err := url.Parse(urlStr); err == nil { - if smUrl.Scheme == "" || smUrl.Scheme == "file" { - if f, err := os.Open(smUrl.Path); err == nil { - if d, err := ioutil.ReadAll(f); err == nil { - data = d - } + var smUrl *url.URL + if smUrl, err = url.Parse(urlStr); err == nil { + p := smUrl.Path + if !path.IsAbs(p) { + baseName := self.file.Name() + baseUrl, err1 := url.Parse(baseName) + if err1 == nil && baseUrl.Scheme != "" { + baseUrl.Path = path.Join(path.Dir(baseUrl.Path), p) + p = baseUrl.String() + } else { + p = path.Join(path.Dir(baseName), p) } + } + if self.opts.sourceMapLoader != nil { + data, err = self.opts.sourceMapLoader(p) } else { - // Not implemented - compile error? - return nil + if smUrl.Scheme == "" || smUrl.Scheme == "file" { + data, err = ioutil.ReadFile(p) + } else { + err = fmt.Errorf("unsupported source map URL scheme: %s", smUrl.Scheme) + } } } } + if err != nil { + self.error(file.Idx(0), "Could not load source map: %v", err) + return nil + } if data == nil { return nil } if sm, err := sourcemap.Parse(self.file.Name(), data); err == nil { return sm + } else { + self.error(file.Idx(0), "Could not parse source map: %v", err) } } return nil diff --git a/vendor/github.com/dop251/goja/regexp.go b/vendor/github.com/dop251/goja/regexp.go index 2d020aa6821f134545a2c00b4dcc6dfcf4a251a8..426791cf8b508ef4b4c553c0dfb30b99e0b2b242 100644 --- a/vendor/github.com/dop251/goja/regexp.go +++ b/vendor/github.com/dop251/goja/regexp.go @@ -17,6 +17,7 @@ type regexp2MatchCache struct { posMap []int } +// Not goroutine-safe. Use regexp2Wrapper.clone() type regexp2Wrapper struct { rx *regexp2.Regexp cache *regexp2MatchCache @@ -56,6 +57,7 @@ func (rd *arrayRuneReader) ReadRune() (r rune, size int, err error) { return } +// Not goroutine-safe. Use regexpPattern.clone() type regexpPattern struct { src string @@ -165,6 +167,25 @@ func (p *regexpPattern) findAllSubmatchIndex(s valueString, start int, limit int return p.regexp2Wrapper.findAllSubmatchIndex(s, start, limit, sticky, p.unicode) } +// clone creates a copy of the regexpPattern which can be used concurrently. +func (p *regexpPattern) clone() *regexpPattern { + ret := ®expPattern{ + src: p.src, + global: p.global, + ignoreCase: p.ignoreCase, + multiline: p.multiline, + sticky: p.sticky, + unicode: p.unicode, + } + if p.regexpWrapper != nil { + ret.regexpWrapper = p.regexpWrapper.clone() + } + if p.regexp2Wrapper != nil { + ret.regexp2Wrapper = p.regexp2Wrapper.clone() + } + return ret +} + type regexpObject struct { baseObject pattern *regexpPattern @@ -428,6 +449,12 @@ func (r *regexp2Wrapper) findAllSubmatchIndex(s valueString, start, limit int, s } } +func (r *regexp2Wrapper) clone() *regexp2Wrapper { + return ®exp2Wrapper{ + rx: r.rx, + } +} + func (r *regexpWrapper) findAllSubmatchIndex(s string, limit int, sticky bool) (results [][]int) { wrapped := (*regexp.Regexp)(r) results = wrapped.FindAllStringSubmatchIndex(s, limit) @@ -476,6 +503,10 @@ func (r *regexpWrapper) findSubmatchIndexUnicode(s unicodeString, fullUnicode bo return wrapped.FindReaderSubmatchIndex(s.utf16Reader(0)) } +func (r *regexpWrapper) clone() *regexpWrapper { + return r +} + func (r *regexpObject) execResultToArray(target valueString, result []int) Value { captureCount := len(result) >> 1 valueArray := make([]Value, captureCount) diff --git a/vendor/github.com/dop251/goja/runtime.go b/vendor/github.com/dop251/goja/runtime.go index a29b6d4f8318a8fe795240cc9876caffd35373d7..1b498e37a2bac660bf2d9a96f1fe6565a80da74a 100644 --- a/vendor/github.com/dop251/goja/runtime.go +++ b/vendor/github.com/dop251/goja/runtime.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "github.com/dop251/goja/file" "go/ast" "hash/maphash" "math" @@ -161,6 +162,7 @@ type Runtime struct { rand RandSource now Now _collator *collate.Collator + parserOptions []parser.Option symbolRegistry map[unistring.String]*Symbol @@ -192,7 +194,7 @@ func (f *StackFrame) SrcName() string { if f.prg == nil { return "" } - return f.prg.src.name + return f.prg.src.Name() } func (f *StackFrame) FuncName() string { @@ -205,12 +207,9 @@ func (f *StackFrame) FuncName() string { return f.funcName.String() } -func (f *StackFrame) Position() Position { +func (f *StackFrame) Position() file.Position { if f.prg == nil || f.prg.src == nil { - return Position{ - 0, - 0, - } + return file.Position{} } return f.prg.src.Position(f.prg.sourceOffset(f.pc)) } @@ -221,13 +220,16 @@ func (f *StackFrame) Write(b *bytes.Buffer) { b.WriteString(n.String()) b.WriteString(" (") } - if n := f.prg.src.name; n != "" { - b.WriteString(n) + p := f.Position() + if p.Filename != "" { + b.WriteString(p.Filename) } else { b.WriteString("") } b.WriteByte(':') - b.WriteString(f.Position().String()) + b.WriteString(strconv.Itoa(p.Line)) + b.WriteByte(':') + b.WriteString(strconv.Itoa(p.Column)) b.WriteByte('(') b.WriteString(strconv.Itoa(f.pc)) b.WriteByte(')') @@ -1111,8 +1113,17 @@ func MustCompile(name, src string, strict bool) *Program { return prg } -func compile(name, src string, strict, eval bool) (p *Program, err error) { - prg, err1 := parser.ParseFile(nil, name, src, 0) +// Parse takes a source string and produces a parsed AST. Use this function if you want to pass options +// to the parser, e.g.: +// +// p, err := Parse("test.js", "var a = true", parser.WithDisableSourceMaps) +// if err != nil { /* ... */ } +// prg, err := CompileAST(p, true) +// // ... +// +// Otherwise use Compile which combines both steps. +func Parse(name, src string, options ...parser.Option) (prg *js_ast.Program, err error) { + prg, err1 := parser.ParseFile(nil, name, src, 0, options...) if err1 != nil { switch err1 := err1.(type) { case parser.ErrorList: @@ -1131,12 +1142,17 @@ func compile(name, src string, strict, eval bool) (p *Program, err error) { Message: err1.Error(), }, } - return } + return +} - p, err = compileAST(prg, strict, eval) +func compile(name, src string, strict, eval bool, parserOptions ...parser.Option) (p *Program, err error) { + prg, err := Parse(name, src, parserOptions...) + if err != nil { + return + } - return + return compileAST(prg, strict, eval) } func compileAST(prg *js_ast.Program, strict, eval bool) (p *Program, err error) { @@ -1162,7 +1178,7 @@ func compileAST(prg *js_ast.Program, strict, eval bool) (p *Program, err error) } func (r *Runtime) compile(name, src string, strict, eval bool) (p *Program, err error) { - p, err = compile(name, src, strict, eval) + p, err = compile(name, src, strict, eval, r.parserOptions...) if err != nil { switch x1 := err.(type) { case *CompilerSyntaxError: @@ -1185,7 +1201,7 @@ func (r *Runtime) RunString(str string) (Value, error) { // RunScript executes the given string in the global context. func (r *Runtime) RunScript(name, src string) (Value, error) { - p, err := Compile(name, src, false) + p, err := r.compile(name, src, false, false) if err != nil { return nil, err @@ -1984,6 +2000,11 @@ func (r *Runtime) SetTimeSource(now Now) { r.now = now } +// SetParserOptions sets parser options to be used by RunString, RunScript and eval() within the code. +func (r *Runtime) SetParserOptions(opts ...parser.Option) { + r.parserOptions = opts +} + // New is an equivalent of the 'new' operator allowing to call it directly from Go. func (r *Runtime) New(construct Value, args ...Value) (o *Object, err error) { err = tryFunc(func() { diff --git a/vendor/github.com/dop251/goja/srcfile.go b/vendor/github.com/dop251/goja/srcfile.go deleted file mode 100644 index 8031b863f52fd76d894044acfc561d1ac220d67a..0000000000000000000000000000000000000000 --- a/vendor/github.com/dop251/goja/srcfile.go +++ /dev/null @@ -1,92 +0,0 @@ -package goja - -import ( - "fmt" - "github.com/go-sourcemap/sourcemap" - "sort" - "strings" - "sync" -) - -type Position struct { - Line, Col int -} - -type SrcFile struct { - name string - src string - - lineOffsets []int - lineOffsetsLock sync.Mutex - lastScannedOffset int - sourceMap *sourcemap.Consumer -} - -func NewSrcFile(name, src string, sourceMap *sourcemap.Consumer) *SrcFile { - return &SrcFile{ - name: name, - src: src, - sourceMap: sourceMap, - } -} - -func (f *SrcFile) Position(offset int) Position { - var line int - var lineOffsets []int - f.lineOffsetsLock.Lock() - if offset > f.lastScannedOffset { - line = f.scanTo(offset) - lineOffsets = f.lineOffsets - f.lineOffsetsLock.Unlock() - } else { - lineOffsets = f.lineOffsets - f.lineOffsetsLock.Unlock() - line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1 - } - - var lineStart int - if line >= 0 { - lineStart = lineOffsets[line] - } - - row := line + 2 - col := offset - lineStart + 1 - - if f.sourceMap != nil { - if _, _, row, col, ok := f.sourceMap.Source(row, col); ok { - return Position{ - Line: row, - Col: col, - } - } - } - - return Position{ - Line: row, - Col: col, - } -} - -func (f *SrcFile) scanTo(offset int) int { - o := f.lastScannedOffset - for o < offset { - p := strings.Index(f.src[o:], "\n") - if p == -1 { - f.lastScannedOffset = len(f.src) - return len(f.lineOffsets) - 1 - } - o = o + p + 1 - f.lineOffsets = append(f.lineOffsets, o) - } - f.lastScannedOffset = o - - if o == offset { - return len(f.lineOffsets) - 1 - } - - return len(f.lineOffsets) - 2 -} - -func (p Position) String() string { - return fmt.Sprintf("%d:%d", p.Line, p.Col) -} diff --git a/vendor/github.com/dop251/goja/vm.go b/vendor/github.com/dop251/goja/vm.go index 713b4c499ae0d0c35e585c8e0405c0c4cef452c4..2622dfa138749591e4f2f69d971090056d94509d 100644 --- a/vendor/github.com/dop251/goja/vm.go +++ b/vendor/github.com/dop251/goja/vm.go @@ -1278,7 +1278,7 @@ type newRegexp struct { } func (n *newRegexp) exec(vm *vm) { - vm.push(vm.r.newRegExpp(n.pattern, n.src, vm.r.global.RegExpPrototype)) + vm.push(vm.r.newRegExpp(n.pattern.clone(), n.src, vm.r.global.RegExpPrototype)) vm.pc++ } @@ -1946,7 +1946,7 @@ func (n *newFunc) exec(vm *vm) { obj := vm.r.newFunc(n.name, int(n.length), n.strict) obj.prg = n.prg obj.stash = vm.stash - obj.src = n.prg.src.src[n.srcStart:n.srcEnd] + obj.src = n.prg.src.Source()[n.srcStart:n.srcEnd] vm.push(obj.val) vm.pc++ } diff --git a/vendor/golang.org/x/text/internal/language/parse.go b/vendor/golang.org/x/text/internal/language/parse.go index 2be83e1da54201eb6067ed6f8ee83f579761a41a..a2fdad89db43e739910b71dfca8f1a0814f7c096 100644 --- a/vendor/golang.org/x/text/internal/language/parse.go +++ b/vendor/golang.org/x/text/internal/language/parse.go @@ -133,14 +133,15 @@ func (s *scanner) resizeRange(oldStart, oldEnd, newSize int) { s.start = oldStart if end := oldStart + newSize; end != oldEnd { diff := end - oldEnd - if end < cap(s.b) { - b := make([]byte, len(s.b)+diff) + var b []byte + if n := len(s.b) + diff; n > cap(s.b) { + b = make([]byte, n) copy(b, s.b[:oldStart]) - copy(b[end:], s.b[oldEnd:]) - s.b = b } else { - s.b = append(s.b[end:], s.b[oldEnd:]...) + b = s.b[:n:n] } + copy(b[end:], s.b[oldEnd:]) + s.b = b s.next = end + (s.next - s.end) s.end = end } diff --git a/vendor/golang.org/x/text/unicode/bidi/bidi.go b/vendor/golang.org/x/text/unicode/bidi/bidi.go index e8edc54cc28d20d9a2bb02a6a7310f87e8fe39ed..fd057601bd9178c5def359f002b06ef0769bd6f3 100644 --- a/vendor/golang.org/x/text/unicode/bidi/bidi.go +++ b/vendor/golang.org/x/text/unicode/bidi/bidi.go @@ -12,15 +12,14 @@ // and without notice. package bidi // import "golang.org/x/text/unicode/bidi" -// TODO: -// The following functionality would not be hard to implement, but hinges on -// the definition of a Segmenter interface. For now this is up to the user. -// - Iterate over paragraphs -// - Segmenter to iterate over runs directly from a given text. -// Also: +// TODO // - Transformer for reordering? // - Transformer (validator, really) for Bidi Rule. +import ( + "bytes" +) + // This API tries to avoid dealing with embedding levels for now. Under the hood // these will be computed, but the question is to which extent the user should // know they exist. We should at some point allow the user to specify an @@ -49,7 +48,9 @@ const ( Neutral ) -type options struct{} +type options struct { + defaultDirection Direction +} // An Option is an option for Bidi processing. type Option func(*options) @@ -66,12 +67,62 @@ type Option func(*options) // DefaultDirection sets the default direction for a Paragraph. The direction is // overridden if the text contains directional characters. func DefaultDirection(d Direction) Option { - panic("unimplemented") + return func(opts *options) { + opts.defaultDirection = d + } } // A Paragraph holds a single Paragraph for Bidi processing. type Paragraph struct { - // buffers + p []byte + o Ordering + opts []Option + types []Class + pairTypes []bracketType + pairValues []rune + runes []rune + options options +} + +// Initialize the p.pairTypes, p.pairValues and p.types from the input previously +// set by p.SetBytes() or p.SetString(). Also limit the input up to (and including) a paragraph +// separator (bidi class B). +// +// The function p.Order() needs these values to be set, so this preparation could be postponed. +// But since the SetBytes and SetStrings functions return the length of the input up to the paragraph +// separator, the whole input needs to be processed anyway and should not be done twice. +// +// The function has the same return values as SetBytes() / SetString() +func (p *Paragraph) prepareInput() (n int, err error) { + p.runes = bytes.Runes(p.p) + bytecount := 0 + // clear slices from previous SetString or SetBytes + p.pairTypes = nil + p.pairValues = nil + p.types = nil + + for _, r := range p.runes { + props, i := LookupRune(r) + bytecount += i + cls := props.Class() + if cls == B { + return bytecount, nil + } + p.types = append(p.types, cls) + if props.IsOpeningBracket() { + p.pairTypes = append(p.pairTypes, bpOpen) + p.pairValues = append(p.pairValues, r) + } else if props.IsBracket() { + // this must be a closing bracket, + // since IsOpeningBracket is not true + p.pairTypes = append(p.pairTypes, bpClose) + p.pairValues = append(p.pairValues, r) + } else { + p.pairTypes = append(p.pairTypes, bpNone) + p.pairValues = append(p.pairValues, 0) + } + } + return bytecount, nil } // SetBytes configures p for the given paragraph text. It replaces text @@ -80,70 +131,150 @@ type Paragraph struct { // consumed from b including this separator. Error may be non-nil if options are // given. func (p *Paragraph) SetBytes(b []byte, opts ...Option) (n int, err error) { - panic("unimplemented") + p.p = b + p.opts = opts + return p.prepareInput() } -// SetString configures p for the given paragraph text. It replaces text -// previously set by SetBytes or SetString. If b contains a paragraph separator +// SetString configures s for the given paragraph text. It replaces text +// previously set by SetBytes or SetString. If s contains a paragraph separator // it will only process the first paragraph and report the number of bytes -// consumed from b including this separator. Error may be non-nil if options are +// consumed from s including this separator. Error may be non-nil if options are // given. func (p *Paragraph) SetString(s string, opts ...Option) (n int, err error) { - panic("unimplemented") + p.p = []byte(s) + p.opts = opts + return p.prepareInput() } // IsLeftToRight reports whether the principle direction of rendering for this // paragraphs is left-to-right. If this returns false, the principle direction // of rendering is right-to-left. func (p *Paragraph) IsLeftToRight() bool { - panic("unimplemented") + return p.Direction() == LeftToRight } // Direction returns the direction of the text of this paragraph. // // The direction may be LeftToRight, RightToLeft, Mixed, or Neutral. func (p *Paragraph) Direction() Direction { - panic("unimplemented") + return p.o.Direction() } +// TODO: what happens if the position is > len(input)? This should return an error. + // RunAt reports the Run at the given position of the input text. // // This method can be used for computing line breaks on paragraphs. func (p *Paragraph) RunAt(pos int) Run { - panic("unimplemented") + c := 0 + runNumber := 0 + for i, r := range p.o.runes { + c += len(r) + if pos < c { + runNumber = i + } + } + return p.o.Run(runNumber) +} + +func calculateOrdering(levels []level, runes []rune) Ordering { + var curDir Direction + + prevDir := Neutral + prevI := 0 + + o := Ordering{} + // lvl = 0,2,4,...: left to right + // lvl = 1,3,5,...: right to left + for i, lvl := range levels { + if lvl%2 == 0 { + curDir = LeftToRight + } else { + curDir = RightToLeft + } + if curDir != prevDir { + if i > 0 { + o.runes = append(o.runes, runes[prevI:i]) + o.directions = append(o.directions, prevDir) + o.startpos = append(o.startpos, prevI) + } + prevI = i + prevDir = curDir + } + } + o.runes = append(o.runes, runes[prevI:]) + o.directions = append(o.directions, prevDir) + o.startpos = append(o.startpos, prevI) + return o } // Order computes the visual ordering of all the runs in a Paragraph. func (p *Paragraph) Order() (Ordering, error) { - panic("unimplemented") + if len(p.types) == 0 { + return Ordering{}, nil + } + + for _, fn := range p.opts { + fn(&p.options) + } + lvl := level(-1) + if p.options.defaultDirection == RightToLeft { + lvl = 1 + } + para, err := newParagraph(p.types, p.pairTypes, p.pairValues, lvl) + if err != nil { + return Ordering{}, err + } + + levels := para.getLevels([]int{len(p.types)}) + + p.o = calculateOrdering(levels, p.runes) + return p.o, nil } // Line computes the visual ordering of runs for a single line starting and // ending at the given positions in the original text. func (p *Paragraph) Line(start, end int) (Ordering, error) { - panic("unimplemented") + lineTypes := p.types[start:end] + para, err := newParagraph(lineTypes, p.pairTypes[start:end], p.pairValues[start:end], -1) + if err != nil { + return Ordering{}, err + } + levels := para.getLevels([]int{len(lineTypes)}) + o := calculateOrdering(levels, p.runes[start:end]) + return o, nil } // An Ordering holds the computed visual order of runs of a Paragraph. Calling // SetBytes or SetString on the originating Paragraph invalidates an Ordering. // The methods of an Ordering should only be called by one goroutine at a time. -type Ordering struct{} +type Ordering struct { + runes [][]rune + directions []Direction + startpos []int +} // Direction reports the directionality of the runs. // // The direction may be LeftToRight, RightToLeft, Mixed, or Neutral. func (o *Ordering) Direction() Direction { - panic("unimplemented") + return o.directions[0] } // NumRuns returns the number of runs. func (o *Ordering) NumRuns() int { - panic("unimplemented") + return len(o.runes) } // Run returns the ith run within the ordering. func (o *Ordering) Run(i int) Run { - panic("unimplemented") + r := Run{ + runes: o.runes[i], + direction: o.directions[i], + startpos: o.startpos[i], + } + return r } // TODO: perhaps with options. @@ -155,16 +286,19 @@ func (o *Ordering) Run(i int) Run { // A Run is a continuous sequence of characters of a single direction. type Run struct { + runes []rune + direction Direction + startpos int } // String returns the text of the run in its original order. func (r *Run) String() string { - panic("unimplemented") + return string(r.runes) } // Bytes returns the text of the run in its original order. func (r *Run) Bytes() []byte { - panic("unimplemented") + return []byte(r.String()) } // TODO: methods for @@ -174,25 +308,52 @@ func (r *Run) Bytes() []byte { // Direction reports the direction of the run. func (r *Run) Direction() Direction { - panic("unimplemented") + return r.direction } -// Position of the Run within the text passed to SetBytes or SetString of the +// Pos returns the position of the Run within the text passed to SetBytes or SetString of the // originating Paragraph value. func (r *Run) Pos() (start, end int) { - panic("unimplemented") + return r.startpos, r.startpos + len(r.runes) - 1 } // AppendReverse reverses the order of characters of in, appends them to out, // and returns the result. Modifiers will still follow the runes they modify. // Brackets are replaced with their counterparts. func AppendReverse(out, in []byte) []byte { - panic("unimplemented") + ret := make([]byte, len(in)+len(out)) + copy(ret, out) + inRunes := bytes.Runes(in) + + for i, r := range inRunes { + prop, _ := LookupRune(r) + if prop.IsBracket() { + inRunes[i] = prop.reverseBracket(r) + } + } + + for i, j := 0, len(inRunes)-1; i < j; i, j = i+1, j-1 { + inRunes[i], inRunes[j] = inRunes[j], inRunes[i] + } + copy(ret[len(out):], string(inRunes)) + + return ret } // ReverseString reverses the order of characters in s and returns a new string. // Modifiers will still follow the runes they modify. Brackets are replaced with // their counterparts. func ReverseString(s string) string { - panic("unimplemented") + input := []rune(s) + li := len(input) + ret := make([]rune, li) + for i, r := range input { + prop, _ := LookupRune(r) + if prop.IsBracket() { + ret[li-i-1] = prop.reverseBracket(r) + } else { + ret[li-i-1] = r + } + } + return string(ret) } diff --git a/vendor/golang.org/x/text/unicode/bidi/core.go b/vendor/golang.org/x/text/unicode/bidi/core.go index 50deb6600a3c0c6b6149146bcb6c07703632c02e..e4c0811016c2acd3f54b0ec93d59f84f8cbba190 100644 --- a/vendor/golang.org/x/text/unicode/bidi/core.go +++ b/vendor/golang.org/x/text/unicode/bidi/core.go @@ -4,7 +4,10 @@ package bidi -import "log" +import ( + "fmt" + "log" +) // This implementation is a port based on the reference implementation found at: // https://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/ @@ -97,13 +100,20 @@ type paragraph struct { // rune (suggested is the rune of the open bracket for opening and matching // close brackets, after normalization). The embedding levels are optional, but // may be supplied to encode embedding levels of styled text. -// -// TODO: return an error. -func newParagraph(types []Class, pairTypes []bracketType, pairValues []rune, levels level) *paragraph { - validateTypes(types) - validatePbTypes(pairTypes) - validatePbValues(pairValues, pairTypes) - validateParagraphEmbeddingLevel(levels) +func newParagraph(types []Class, pairTypes []bracketType, pairValues []rune, levels level) (*paragraph, error) { + var err error + if err = validateTypes(types); err != nil { + return nil, err + } + if err = validatePbTypes(pairTypes); err != nil { + return nil, err + } + if err = validatePbValues(pairValues, pairTypes); err != nil { + return nil, err + } + if err = validateParagraphEmbeddingLevel(levels); err != nil { + return nil, err + } p := ¶graph{ initialTypes: append([]Class(nil), types...), @@ -115,7 +125,7 @@ func newParagraph(types []Class, pairTypes []bracketType, pairValues []rune, lev resultTypes: append([]Class(nil), types...), } p.run() - return p + return p, nil } func (p *paragraph) Len() int { return len(p.initialTypes) } @@ -1001,58 +1011,61 @@ func typeForLevel(level level) Class { return R } -// TODO: change validation to not panic - -func validateTypes(types []Class) { +func validateTypes(types []Class) error { if len(types) == 0 { - log.Panic("types is null") + return fmt.Errorf("types is null") } for i, t := range types[:len(types)-1] { if t == B { - log.Panicf("B type before end of paragraph at index: %d", i) + return fmt.Errorf("B type before end of paragraph at index: %d", i) } } + return nil } -func validateParagraphEmbeddingLevel(embeddingLevel level) { +func validateParagraphEmbeddingLevel(embeddingLevel level) error { if embeddingLevel != implicitLevel && embeddingLevel != 0 && embeddingLevel != 1 { - log.Panicf("illegal paragraph embedding level: %d", embeddingLevel) + return fmt.Errorf("illegal paragraph embedding level: %d", embeddingLevel) } + return nil } -func validateLineBreaks(linebreaks []int, textLength int) { +func validateLineBreaks(linebreaks []int, textLength int) error { prev := 0 for i, next := range linebreaks { if next <= prev { - log.Panicf("bad linebreak: %d at index: %d", next, i) + return fmt.Errorf("bad linebreak: %d at index: %d", next, i) } prev = next } if prev != textLength { - log.Panicf("last linebreak was %d, want %d", prev, textLength) + return fmt.Errorf("last linebreak was %d, want %d", prev, textLength) } + return nil } -func validatePbTypes(pairTypes []bracketType) { +func validatePbTypes(pairTypes []bracketType) error { if len(pairTypes) == 0 { - log.Panic("pairTypes is null") + return fmt.Errorf("pairTypes is null") } for i, pt := range pairTypes { switch pt { case bpNone, bpOpen, bpClose: default: - log.Panicf("illegal pairType value at %d: %v", i, pairTypes[i]) + return fmt.Errorf("illegal pairType value at %d: %v", i, pairTypes[i]) } } + return nil } -func validatePbValues(pairValues []rune, pairTypes []bracketType) { +func validatePbValues(pairValues []rune, pairTypes []bracketType) error { if pairValues == nil { - log.Panic("pairValues is null") + return fmt.Errorf("pairValues is null") } if len(pairTypes) != len(pairValues) { - log.Panic("pairTypes is different length from pairValues") + return fmt.Errorf("pairTypes is different length from pairValues") } + return nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 21c160b794dd1ccc197e66906e63e07c35b81d36..b12426b31ba471cc053e3388df4b1dea3bab8f43 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -71,7 +71,7 @@ github.com/dgrijalva/jwt-go # github.com/dlclark/regexp2 v1.4.0 github.com/dlclark/regexp2 github.com/dlclark/regexp2/syntax -# github.com/dop251/goja v0.0.0-20201212162034-be0895b77e07 +# github.com/dop251/goja v0.0.0-20210106133455-27b0a7dc4c7f github.com/dop251/goja github.com/dop251/goja/ast github.com/dop251/goja/file @@ -217,7 +217,7 @@ golang.org/x/net/trace golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/text v0.3.4 +# golang.org/x/text v0.3.5 golang.org/x/text/cases golang.org/x/text/collate golang.org/x/text/internal