mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-26 22:19:26 +00:00
Compare commits
35 Commits
v1.14.0-rc
...
v1.14.2
Author | SHA1 | Date | |
---|---|---|---|
e4d73b29a1 | |||
8a875f292e | |||
60a85621ea | |||
115d20373c | |||
cdf33e5748 | |||
01d0a9f412 | |||
8cc2d3b4fe | |||
aba9e4f3be | |||
4d575ba13a | |||
7f0e4ad448 | |||
17cc14a9d2 | |||
1f8016182c | |||
caf9ef2c4b | |||
64b57f2da3 | |||
efd2c99862 | |||
cc05ba8907 | |||
16763b715a | |||
ffaa598796 | |||
858e16d34f | |||
a60e62efb1 | |||
97f9d4be67 | |||
fa4eec41f7 | |||
77516c97db | |||
cba01f0865 | |||
8b754017ca | |||
a27600046e | |||
fb2667631d | |||
b638f7037a | |||
74699a8262 | |||
eabf2a4582 | |||
325d62b41c | |||
e955a056e2 | |||
723f8c5fd5 | |||
a16137f53f | |||
d60b8b97f9 |
@ -7,7 +7,7 @@ run:
|
||||
# concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 1m
|
||||
deadline: 2m
|
||||
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
@ -105,10 +105,6 @@ linters-settings:
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
unparam:
|
||||
# call graph construction algorithm (cha, rta). In general, use cha for libraries,
|
||||
# and rta for programs with main packages. Default is cha.
|
||||
algo: rta
|
||||
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
@ -158,7 +154,6 @@ linters-settings:
|
||||
- regexpMust
|
||||
- singleCaseSwitch
|
||||
- sloppyLen
|
||||
- sloppyReassign
|
||||
- switchTrue
|
||||
- typeSwitchVar
|
||||
- typeUnparen
|
||||
|
76
.travis.yml
76
.travis.yml
@ -1,63 +1,55 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.11.x
|
||||
go_import_path: github.com/42wim/matterbridge
|
||||
|
||||
# we have everything vendored
|
||||
# We have everything vendored so this helps TravisCI not run `go get ...`.
|
||||
install: true
|
||||
|
||||
git:
|
||||
depth: 200
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOOS=linux GOARCH=amd64
|
||||
- GOLANGCI_VERSION="v1.14.0"
|
||||
|
||||
matrix:
|
||||
# It's ok if our code fails on unstable development versions of Go.
|
||||
allow_failures:
|
||||
- go: tip
|
||||
# Don't wait for tip tests to finish. Mark the test run green if the
|
||||
# tests pass on the stable versions of Go.
|
||||
fast_finish: true
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
email: false
|
||||
|
||||
before_script:
|
||||
# Get version info from tags.
|
||||
- MY_VERSION="$(git describe --tags)"
|
||||
# Retrieve the golangci-lint linter binary.
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b ${GOPATH}/bin ${GOLANGCI_VERSION}
|
||||
# Retrieve and prepare CodeClimate's test coverage reporter.
|
||||
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||
- chmod +x ./cc-test-reporter
|
||||
- ./cc-test-reporter before-build
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
script:
|
||||
# Run the linter.
|
||||
- golangci-lint run
|
||||
# Run all the tests with the race detector and generate coverage.
|
||||
- go test -v -race -coverprofile c.out ./...
|
||||
# Run the build script to generate the necessary binaries and images.
|
||||
- /bin/bash ci/bintray.sh
|
||||
jobs:
|
||||
include:
|
||||
- stage: lint
|
||||
# Run linting in one Go environment only.
|
||||
script: ./ci/lint.sh
|
||||
go: 1.12.x
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
- GOLANGCI_VERSION="v1.16.0"
|
||||
- stage: test
|
||||
# Run tests in a combination of Go environments.
|
||||
script: ./ci/test.sh
|
||||
go: 1.11.x
|
||||
env:
|
||||
- GO111MODULE=off
|
||||
- script: ./ci/test.sh
|
||||
go: 1.11.x
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
- script: ./ci/test.sh
|
||||
go: 1.12.x
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
- REPORT_COVERAGE=1
|
||||
- BINDEPLOY=1
|
||||
|
||||
after_script:
|
||||
# Upload test coverage to CodeClimate.
|
||||
- ./cc-test-reporter after-build --exit-code ${TRAVIS_TEST_RESULT}
|
||||
before_deploy: /bin/bash ci/bintray.sh
|
||||
|
||||
deploy:
|
||||
on:
|
||||
all_branches: true
|
||||
provider: bintray
|
||||
on:
|
||||
all_branches: true
|
||||
condition: $BINDEPLOY = 1
|
||||
provider: bintray
|
||||
edge:
|
||||
branch: v1.8.47
|
||||
file: ci/deploy.json
|
||||
user: 42wim
|
||||
on:
|
||||
all_branches: true
|
||||
key:
|
||||
secure: "CeXXe6JOmt7HYR81MdWLua0ltQHhDdkIeRGBFbgd7hkb1wi8eF9DgpAcQrTso8NIlHNZmSAP46uhFgsRvkuezzX0ygalZ7DCJyAyn3sAMEh+UQSHV1WGThRehTtidqRGjetzsIGSwdrJOWil+XTfbO1Z8DGzfakhSuAZka8CM4BAoe3YeP9rYK8h+84x0GHfczvsLtXZ3mWLvQuwe4pK6+ItBCUg0ae7O7ZUpWHy0xQQkkWztY/6RAzXfaG7DuGjIw+20fhx3WOXRNpHCtZ6Bc3qERCpk0s1HhlQWlrN9wDaFTBWYwlvSnNgvxxMbNXJ6RrRJ0l0bA7FUswYwyroxhzrGLdzWDg8dHaQkypocngdalfhpsnoO9j3ApJhomUFJ3UoEq5nOGRUrKn8MPi+dP0zE4kNQ3e4VNa1ufNrvfpWolMg3xh8OXuhQdD5wIM5zFAbRJLqWSCVAjPq4DDPecmvXBOlIial7oa312lN5qnBnUjvAcxszZ+FUyDHT1Grxzna4tMwxY9obPzZUzm7359AOCCwIQFVB8GLqD2nwIstcXS0zGRz+fhviPipHuBa02q5bGUZwmkvrSNab0s8Jo7pCrel2Rz3nWPKaiCfq2WjbW1CLheSMkOQrjsdUd1hhbqNWFPUjJPInTc77NAKCfm5runv5uyowRLh4NNd0sI="
|
||||
secure: "CeXXe6JOmt7HYR81MdWLua0ltQHhDdkIeRGBFbgd7hkb1wi8eF9DgpAcQrTso8NIlHNZmSAP46uhFgsRvkuezzX0ygalZ7DCJyAyn3sAMEh+UQSHV1WGThRehTtidqRGjetzsIGSwdrJOWil+XTfbO1Z8DGzfakhSuAZka8CM4BAoe3YeP9rYK8h+84x0GHfczvsLtXZ3mWLvQuwe4pK6+ItBCUg0ae7O7ZUpWHy0xQQkkWztY/6RAzXfaG7DuGjIw+20fhx3WOXRNpHCtZ6Bc3qERCpk0s1HhlQWlrN9wDaFTBWYwlvSnNgvxxMbNXJ6RrRJ0l0bA7FUswYwyroxhzrGLdzWDg8dHaQkypocngdalfhpsnoO9j3ApJhomUFJ3UoEq5nOGRUrKn8MPi+dP0zE4kNQ3e4VNa1ufNrvfpWolMg3xh8OXuhQdD5wIM5zFAbRJLqWSCVAjPq4DDPecmvXBOlIial7oa312lN5qnBnUjvAcxszZ+FUyDHT1Grxzna4tMwxY9obPzZUzm7359AOCCwIQFVB8GLqD2nwIstcXS0zGRz+fhviPipHuBa02q5bGUZwmkvrSNab0s8Jo7pCrel2Rz3nWPKaiCfq2WjbW1CLheSMkOQrjsdUd1hhbqNWFPUjJPInTc77NAKCfm5runv5uyowRLh4NNd0sI="
|
||||
|
20
README.md
20
README.md
@ -35,6 +35,12 @@
|
||||
|
||||
**Note:** Matter<em>most</em> isn't required to run matter<em>bridge</em>.</sup></div>
|
||||
|
||||
<p>
|
||||
<a href="https://www.digitalocean.com/">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="201px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### Table of Contents
|
||||
* [Features](https://github.com/42wim/matterbridge/wiki/Features)
|
||||
* [Natively supported](#natively-supported)
|
||||
@ -88,6 +94,7 @@
|
||||
* [Minecraft](https://github.com/elytra/MatterLink)
|
||||
* [Reddit](https://github.com/bonehurtingjuice/mattereddit)
|
||||
* [Facebook messenger](https://github.com/VictorNine/fbridge)
|
||||
* [Discourse](https://github.com/DeclanHoare/matterbabble)
|
||||
|
||||
### API
|
||||
The API is very basic at the moment.
|
||||
@ -99,6 +106,7 @@ Used by the projects below. Feel free to make a PR to add your project to this l
|
||||
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
|
||||
* [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
|
||||
* [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
|
||||
* [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
|
||||
|
||||
## Chat with us
|
||||
|
||||
@ -121,7 +129,7 @@ See https://github.com/42wim/matterbridge/wiki
|
||||
|
||||
## Installing
|
||||
### Binaries
|
||||
* Latest stable release [v1.13.1](https://github.com/42wim/matterbridge/releases/latest)
|
||||
* Latest stable release [v1.14.2](https://github.com/42wim/matterbridge/releases/latest)
|
||||
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
||||
|
||||
### Packages
|
||||
@ -247,6 +255,8 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
||||
* [mattermost-plugin](https://github.com/matterbridge/mattermost-plugin) - Run matterbridge as a plugin in mattermost
|
||||
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
|
||||
* [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
|
||||
* [isla](https://github.com/alphachung/isla) (Bot for Discord-Telegram groups used alongside matterbridge)
|
||||
* [matterbabble](https://github.com/DeclanHoare/matterbabble) (Connect Discourse threads to Matterbridge)
|
||||
|
||||
## Articles
|
||||
* [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)
|
||||
@ -260,7 +270,13 @@ See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
|
||||
* https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/
|
||||
|
||||
## Thanks
|
||||
[](https://www.digitalocean.com/) for sponsoring demo/testing droplets.
|
||||
|
||||
<p>This project is supported by:</p>
|
||||
<p>
|
||||
<a href="https://www.digitalocean.com/">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Matterbridge wouldn't exist without these libraries:
|
||||
* discord - https://github.com/bwmarrin/discordgo
|
||||
|
@ -178,7 +178,10 @@ func ClipMessage(text string, length int) string {
|
||||
|
||||
func ParseMarkdown(input string) string {
|
||||
md := markdown.New(markdown.XHTMLOutput(true), markdown.Breaks(true))
|
||||
return (md.RenderToString([]byte(input)))
|
||||
res := md.RenderToString([]byte(input))
|
||||
res = strings.TrimPrefix(res, "<p>")
|
||||
res = strings.TrimSuffix(res, "</p>\n")
|
||||
return res
|
||||
}
|
||||
|
||||
// ConvertWebPToPNG convert input data (which should be WebP format to PNG format)
|
||||
|
@ -92,10 +92,6 @@ func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) {
|
||||
return
|
||||
}
|
||||
b.Log.Debugf("<= Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||
// QUIT isn't channel bound, happens for all channels on the bridge
|
||||
if event.Command == "QUIT" {
|
||||
channel = ""
|
||||
}
|
||||
msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EventJoinLeave}
|
||||
b.Log.Debugf("<= Message is %#v", msg)
|
||||
b.Remote <- msg
|
||||
|
@ -137,6 +137,7 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
||||
// we can be in between reconnects #385
|
||||
if !b.i.IsConnected() {
|
||||
b.Log.Error("Not connected to server, dropping message")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Execute a command
|
||||
@ -231,7 +232,7 @@ func (b *Birc) getClient() (*girc.Client, error) {
|
||||
// fix strict user handling of girc
|
||||
user := b.GetString("Nick")
|
||||
for !girc.IsValidUser(user) {
|
||||
if len(user) == 1 {
|
||||
if len(user) == 1 || len(user) == 0 {
|
||||
user = "matterbridge"
|
||||
break
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ func (b *Brocketchat) getChannelID(name string) string {
|
||||
b.RLock()
|
||||
defer b.RUnlock()
|
||||
for k, v := range b.channelMap {
|
||||
if v == name {
|
||||
if v == name || v == "#"+name {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package brocketchat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
@ -85,14 +86,14 @@ func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
|
||||
if b.c == nil {
|
||||
return nil
|
||||
}
|
||||
id, err := b.c.GetChannelId(channel.Name)
|
||||
id, err := b.c.GetChannelId(strings.TrimPrefix(channel.Name, "#"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Lock()
|
||||
b.channelMap[id] = channel.Name
|
||||
b.Unlock()
|
||||
mychannel := &models.Channel{ID: id, Name: channel.Name}
|
||||
mychannel := &models.Channel{ID: id, Name: strings.TrimPrefix(channel.Name, "#")}
|
||||
if err := b.c.JoinChannel(id); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -103,6 +104,8 @@ func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Send(msg config.Message) (string, error) {
|
||||
// strip the # if people has set this
|
||||
msg.Channel = strings.TrimPrefix(msg.Channel, "#")
|
||||
channel := &models.Channel{ID: b.getChannelID(msg.Channel), Name: msg.Channel}
|
||||
|
||||
// Delete message
|
||||
@ -131,6 +134,8 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) {
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
// strip the # if people has set this
|
||||
rmsg.Channel = strings.TrimPrefix(rmsg.Channel, "#")
|
||||
smsg := &models.Message{
|
||||
RoomID: b.getChannelID(rmsg.Channel),
|
||||
Msg: rmsg.Username + rmsg.Text,
|
||||
|
@ -34,7 +34,7 @@ func (b *Bslack) handleSlack() {
|
||||
message.Text = html.UnescapeString(message.Text)
|
||||
|
||||
// Add the avatar
|
||||
message.Avatar = b.getAvatar(message.UserID)
|
||||
message.Avatar = b.users.getAvatar(message.UserID)
|
||||
|
||||
b.Log.Debugf("<= Message is %#v", message)
|
||||
b.Remote <- *message
|
||||
@ -75,20 +75,17 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
||||
// When we join a channel we update the full list of users as
|
||||
// well as the information for the channel that we joined as this
|
||||
// should now tell that we are a member of it.
|
||||
b.channelsMutex.Lock()
|
||||
b.channelsByID[ev.Channel.ID] = &ev.Channel
|
||||
b.channelsByName[ev.Channel.Name] = &ev.Channel
|
||||
b.channelsMutex.Unlock()
|
||||
b.channels.registerChannel(ev.Channel)
|
||||
case *slack.ConnectedEvent:
|
||||
b.si = ev.Info
|
||||
b.populateChannels(true)
|
||||
b.populateUsers(true)
|
||||
b.channels.populateChannels(true)
|
||||
b.users.populateUsers(true)
|
||||
case *slack.InvalidAuthEvent:
|
||||
b.Log.Fatalf("Invalid Token %#v", ev)
|
||||
case *slack.ConnectionErrorEvent:
|
||||
b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
|
||||
case *slack.MemberJoinedChannelEvent:
|
||||
b.populateUser(ev.User)
|
||||
b.users.populateUser(ev.User)
|
||||
case *slack.LatencyReport:
|
||||
continue
|
||||
default:
|
||||
@ -133,12 +130,18 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// It seems ev.SubMessage.Edited == nil when slack unfurls.
|
||||
// Do not forward these messages. See Github issue #266.
|
||||
if ev.SubMessage != nil &&
|
||||
ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp &&
|
||||
ev.SubMessage.Edited == nil {
|
||||
return true
|
||||
if ev.SubMessage != nil {
|
||||
// It seems ev.SubMessage.Edited == nil when slack unfurls.
|
||||
// Do not forward these messages. See Github issue #266.
|
||||
if ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp &&
|
||||
ev.SubMessage.Edited == nil {
|
||||
return true
|
||||
}
|
||||
// see hidden subtypes at https://api.slack.com/events/message
|
||||
// these messages are sent when we add a message to a thread #709
|
||||
if ev.SubType == "message_replied" && ev.Hidden {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if len(ev.Files) > 0 {
|
||||
@ -210,7 +213,7 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
|
||||
rmsg.Username = sSystemUser
|
||||
rmsg.Event = config.EventJoinLeave
|
||||
case sChannelTopic, sChannelPurpose:
|
||||
b.populateChannels(false)
|
||||
b.channels.populateChannels(false)
|
||||
rmsg.Event = config.EventTopicChange
|
||||
case sMessageChanged:
|
||||
rmsg.Text = ev.SubMessage.Text
|
||||
@ -266,7 +269,7 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
|
||||
}
|
||||
|
||||
func (b *Bslack) handleTypingEvent(ev *slack.UserTypingEvent) (*config.Message, error) {
|
||||
channelInfo, err := b.getChannelByID(ev.Channel)
|
||||
channelInfo, err := b.channels.getChannelByID(ev.Channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -316,36 +319,7 @@ func (b *Bslack) handleGetChannelMembers(rmsg *config.Message) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
cMembers := config.ChannelMembers{}
|
||||
|
||||
b.channelMembersMutex.RLock()
|
||||
|
||||
for channelID, members := range b.channelMembers {
|
||||
for _, member := range members {
|
||||
channelName := ""
|
||||
userName := ""
|
||||
userNick := ""
|
||||
user := b.getUser(member)
|
||||
if user != nil {
|
||||
userName = user.Name
|
||||
userNick = user.Profile.DisplayName
|
||||
}
|
||||
channel, _ := b.getChannelByID(channelID)
|
||||
if channel != nil {
|
||||
channelName = channel.Name
|
||||
}
|
||||
cMember := config.ChannelMember{
|
||||
Username: userName,
|
||||
Nick: userNick,
|
||||
UserID: member,
|
||||
ChannelID: channelID,
|
||||
ChannelName: channelName,
|
||||
}
|
||||
cMembers = append(cMembers, cMember)
|
||||
}
|
||||
}
|
||||
|
||||
b.channelMembersMutex.RUnlock()
|
||||
cMembers := b.channels.getChannelMembers(b.users)
|
||||
|
||||
extra := make(map[string][]interface{})
|
||||
extra[config.EventGetChannelMembers] = append(extra[config.EventGetChannelMembers], cMembers)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package bslack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -9,225 +8,14 @@ import (
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/nlopes/slack"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (b *Bslack) getUser(id string) *slack.User {
|
||||
b.usersMutex.RLock()
|
||||
user, ok := b.users[id]
|
||||
b.usersMutex.RUnlock()
|
||||
if ok {
|
||||
return user
|
||||
}
|
||||
b.populateUser(id)
|
||||
b.usersMutex.RLock()
|
||||
defer b.usersMutex.RUnlock()
|
||||
|
||||
return b.users[id]
|
||||
}
|
||||
|
||||
func (b *Bslack) getUsername(id string) string {
|
||||
if user := b.getUser(id); user != nil {
|
||||
if user.Profile.DisplayName != "" {
|
||||
return user.Profile.DisplayName
|
||||
}
|
||||
return user.Name
|
||||
}
|
||||
b.Log.Warnf("Could not find user with ID '%s'", id)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Bslack) getAvatar(id string) string {
|
||||
if user := b.getUser(id); user != nil {
|
||||
return user.Profile.Image48
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Bslack) getChannel(channel string) (*slack.Channel, error) {
|
||||
if strings.HasPrefix(channel, "ID:") {
|
||||
return b.getChannelByID(strings.TrimPrefix(channel, "ID:"))
|
||||
}
|
||||
return b.getChannelByName(channel)
|
||||
}
|
||||
|
||||
func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) {
|
||||
return b.getChannelBy(name, b.channelsByName)
|
||||
}
|
||||
|
||||
func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) {
|
||||
return b.getChannelBy(ID, b.channelsByID)
|
||||
}
|
||||
|
||||
func (b *Bslack) getChannelBy(lookupKey string, lookupMap map[string]*slack.Channel) (*slack.Channel, error) {
|
||||
b.channelsMutex.RLock()
|
||||
defer b.channelsMutex.RUnlock()
|
||||
|
||||
if channel, ok := lookupMap[lookupKey]; ok {
|
||||
return channel, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%s: channel %s not found", b.Account, lookupKey)
|
||||
}
|
||||
|
||||
const minimumRefreshInterval = 10 * time.Second
|
||||
|
||||
func (b *Bslack) populateUser(userID string) {
|
||||
b.usersMutex.RLock()
|
||||
_, exists := b.users[userID]
|
||||
b.usersMutex.RUnlock()
|
||||
if exists {
|
||||
// already in cache
|
||||
return
|
||||
}
|
||||
|
||||
user, err := b.sc.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
b.Log.Debugf("GetUserInfo failed for %v: %v", userID, err)
|
||||
return
|
||||
}
|
||||
|
||||
b.usersMutex.Lock()
|
||||
b.users[userID] = user
|
||||
b.usersMutex.Unlock()
|
||||
}
|
||||
|
||||
func (b *Bslack) populateUsers(wait bool) {
|
||||
b.refreshMutex.Lock()
|
||||
if !wait && (time.Now().Before(b.earliestUserRefresh) || b.refreshInProgress) {
|
||||
b.Log.Debugf("Not refreshing user list as it was done less than %v ago.",
|
||||
minimumRefreshInterval)
|
||||
b.refreshMutex.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
for b.refreshInProgress {
|
||||
b.refreshMutex.Unlock()
|
||||
time.Sleep(time.Second)
|
||||
b.refreshMutex.Lock()
|
||||
}
|
||||
b.refreshInProgress = true
|
||||
b.refreshMutex.Unlock()
|
||||
|
||||
newUsers := map[string]*slack.User{}
|
||||
pagination := b.sc.GetUsersPaginated(slack.GetUsersOptionLimit(200))
|
||||
count := 0
|
||||
for {
|
||||
var err error
|
||||
pagination, err = pagination.Next(context.Background())
|
||||
time.Sleep(time.Second)
|
||||
if err != nil {
|
||||
if pagination.Done(err) {
|
||||
break
|
||||
}
|
||||
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
b.Log.Errorf("Could not retrieve users: %#v", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range pagination.Users {
|
||||
newUsers[pagination.Users[i].ID] = &pagination.Users[i]
|
||||
}
|
||||
b.Log.Debugf("getting %d users", len(pagination.Users))
|
||||
count++
|
||||
// more > 2000 users, slack will complain and ratelimit. break
|
||||
if count > 10 {
|
||||
b.Log.Info("Large slack detected > 2000 users, skipping loading complete userlist.")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.usersMutex.Lock()
|
||||
defer b.usersMutex.Unlock()
|
||||
b.users = newUsers
|
||||
|
||||
b.refreshMutex.Lock()
|
||||
defer b.refreshMutex.Unlock()
|
||||
b.earliestUserRefresh = time.Now().Add(minimumRefreshInterval)
|
||||
b.refreshInProgress = false
|
||||
}
|
||||
|
||||
func (b *Bslack) populateChannels(wait bool) {
|
||||
b.refreshMutex.Lock()
|
||||
if !wait && (time.Now().Before(b.earliestChannelRefresh) || b.refreshInProgress) {
|
||||
b.Log.Debugf("Not refreshing channel list as it was done less than %v seconds ago.",
|
||||
minimumRefreshInterval)
|
||||
b.refreshMutex.Unlock()
|
||||
return
|
||||
}
|
||||
for b.refreshInProgress {
|
||||
b.refreshMutex.Unlock()
|
||||
time.Sleep(time.Second)
|
||||
b.refreshMutex.Lock()
|
||||
}
|
||||
b.refreshInProgress = true
|
||||
b.refreshMutex.Unlock()
|
||||
|
||||
newChannelsByID := map[string]*slack.Channel{}
|
||||
newChannelsByName := map[string]*slack.Channel{}
|
||||
newChannelMembers := make(map[string][]string)
|
||||
|
||||
// We only retrieve public and private channels, not IMs
|
||||
// and MPIMs as those do not have a channel name.
|
||||
queryParams := &slack.GetConversationsParameters{
|
||||
ExcludeArchived: "true",
|
||||
Types: []string{"public_channel,private_channel"},
|
||||
}
|
||||
for {
|
||||
channels, nextCursor, err := b.sc.GetConversations(queryParams)
|
||||
if err != nil {
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
b.Log.Errorf("Could not retrieve channels: %#v", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range channels {
|
||||
newChannelsByID[channels[i].ID] = &channels[i]
|
||||
newChannelsByName[channels[i].Name] = &channels[i]
|
||||
// also find all the members in every channel
|
||||
// comment for now, issues on big slacks
|
||||
/*
|
||||
members, err := b.getUsersInConversation(channels[i].ID)
|
||||
if err != nil {
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
b.Log.Errorf("Could not retrieve channel members: %#v", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
newChannelMembers[channels[i].ID] = members
|
||||
*/
|
||||
}
|
||||
|
||||
if nextCursor == "" {
|
||||
break
|
||||
}
|
||||
queryParams.Cursor = nextCursor
|
||||
}
|
||||
|
||||
b.channelsMutex.Lock()
|
||||
defer b.channelsMutex.Unlock()
|
||||
b.channelsByID = newChannelsByID
|
||||
b.channelsByName = newChannelsByName
|
||||
|
||||
b.channelMembersMutex.Lock()
|
||||
defer b.channelMembersMutex.Unlock()
|
||||
b.channelMembers = newChannelMembers
|
||||
|
||||
b.refreshMutex.Lock()
|
||||
defer b.refreshMutex.Unlock()
|
||||
b.earliestChannelRefresh = time.Now().Add(minimumRefreshInterval)
|
||||
b.refreshInProgress = false
|
||||
}
|
||||
|
||||
// populateReceivedMessage shapes the initial Matterbridge message that we will forward to the
|
||||
// router before we apply message-dependent modifications.
|
||||
func (b *Bslack) populateReceivedMessage(ev *slack.MessageEvent) (*config.Message, error) {
|
||||
// Use our own func because rtm.GetChannelInfo doesn't work for private channels.
|
||||
channel, err := b.getChannelByID(ev.Channel)
|
||||
channel, err := b.channels.getChannelByID(ev.Channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -289,7 +77,7 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi
|
||||
return nil
|
||||
}
|
||||
|
||||
user := b.getUser(userID)
|
||||
user := b.users.getUser(userID)
|
||||
if user == nil {
|
||||
return fmt.Errorf("could not find information for user with id %s", ev.User)
|
||||
}
|
||||
@ -315,7 +103,7 @@ func (b *Bslack) populateMessageWithBotInfo(ev *slack.MessageEvent, rmsg *config
|
||||
break
|
||||
}
|
||||
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
if err = handleRateLimit(b.Log, err); err != nil {
|
||||
b.Log.Errorf("Could not retrieve bot information: %#v", err)
|
||||
return err
|
||||
}
|
||||
@ -360,7 +148,7 @@ func (b *Bslack) extractTopicOrPurpose(text string) (string, string) {
|
||||
func (b *Bslack) replaceMention(text string) string {
|
||||
replaceFunc := func(match string) string {
|
||||
userID := strings.Trim(match, "@<>")
|
||||
if username := b.getUsername(userID); userID != "" {
|
||||
if username := b.users.getUsername(userID); userID != "" {
|
||||
return "@" + username
|
||||
}
|
||||
return match
|
||||
@ -404,16 +192,6 @@ func (b *Bslack) replaceCodeFence(text string) string {
|
||||
return codeFenceRE.ReplaceAllString(text, "```")
|
||||
}
|
||||
|
||||
func (b *Bslack) handleRateLimit(err error) error {
|
||||
rateLimit, ok := err.(*slack.RateLimitedError)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
b.Log.Infof("Rate-limited by Slack. Sleeping for %v", rateLimit.RetryAfter)
|
||||
time.Sleep(rateLimit.RetryAfter)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUsersInConversation returns an array of userIDs that are members of channelID
|
||||
func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) {
|
||||
channelMembers := []string{}
|
||||
@ -424,7 +202,7 @@ func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) {
|
||||
|
||||
members, nextCursor, err := b.sc.GetUsersInConversation(queryParams)
|
||||
if err != nil {
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
if err = handleRateLimit(b.Log, err); err != nil {
|
||||
return channelMembers, fmt.Errorf("Could not retrieve users in channels: %#v", err)
|
||||
}
|
||||
continue
|
||||
@ -439,3 +217,13 @@ func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) {
|
||||
}
|
||||
return channelMembers, nil
|
||||
}
|
||||
|
||||
func handleRateLimit(log *logrus.Entry, err error) error {
|
||||
rateLimit, ok := err.(*slack.RateLimitedError)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
log.Infof("Rate-limited by Slack. Sleeping for %v", rateLimit.RetryAfter)
|
||||
time.Sleep(rateLimit.RetryAfter)
|
||||
return nil
|
||||
}
|
||||
|
@ -55,14 +55,18 @@ func (b *BLegacy) Connect() error {
|
||||
})
|
||||
if b.GetString(tokenConfig) != "" {
|
||||
b.Log.Info("Connecting using token (receiving)")
|
||||
b.sc = slack.New(b.GetString(tokenConfig))
|
||||
b.sc = slack.New(b.GetString(tokenConfig), slack.OptionDebug(b.GetBool("debug")))
|
||||
b.channels = newChannelManager(b.Log, b.sc)
|
||||
b.users = newUserManager(b.Log, b.sc)
|
||||
b.rtm = b.sc.NewRTM()
|
||||
go b.rtm.ManageConnection()
|
||||
go b.handleSlack()
|
||||
}
|
||||
} else if b.GetString(tokenConfig) != "" {
|
||||
b.Log.Info("Connecting using token (sending and receiving)")
|
||||
b.sc = slack.New(b.GetString(tokenConfig))
|
||||
b.sc = slack.New(b.GetString(tokenConfig), slack.OptionDebug(b.GetBool("debug")))
|
||||
b.channels = newChannelManager(b.Log, b.sc)
|
||||
b.users = newUserManager(b.Log, b.sc)
|
||||
b.rtm = b.sc.NewRTM()
|
||||
go b.rtm.ManageConnection()
|
||||
go b.handleSlack()
|
||||
|
@ -30,20 +30,8 @@ type Bslack struct {
|
||||
uuid string
|
||||
useChannelID bool
|
||||
|
||||
users map[string]*slack.User
|
||||
usersMutex sync.RWMutex
|
||||
|
||||
channelsByID map[string]*slack.Channel
|
||||
channelsByName map[string]*slack.Channel
|
||||
channelsMutex sync.RWMutex
|
||||
|
||||
channelMembers map[string][]string
|
||||
channelMembersMutex sync.RWMutex
|
||||
|
||||
refreshInProgress bool
|
||||
earliestChannelRefresh time.Time
|
||||
earliestUserRefresh time.Time
|
||||
refreshMutex sync.Mutex
|
||||
channels *channels
|
||||
users *users
|
||||
}
|
||||
|
||||
const (
|
||||
@ -94,14 +82,9 @@ func newBridge(cfg *bridge.Config) *Bslack {
|
||||
cfg.Log.Fatalf("Could not create LRU cache for Slack bridge: %v", err)
|
||||
}
|
||||
b := &Bslack{
|
||||
Config: cfg,
|
||||
uuid: xid.New().String(),
|
||||
cache: newCache,
|
||||
users: map[string]*slack.User{},
|
||||
channelsByID: map[string]*slack.Channel{},
|
||||
channelsByName: map[string]*slack.Channel{},
|
||||
earliestChannelRefresh: time.Now(),
|
||||
earliestUserRefresh: time.Now(),
|
||||
Config: cfg,
|
||||
uuid: xid.New().String(),
|
||||
cache: newCache,
|
||||
}
|
||||
return b
|
||||
}
|
||||
@ -121,7 +104,12 @@ func (b *Bslack) Connect() error {
|
||||
// If we have a token we use the Slack websocket-based RTM for both sending and receiving.
|
||||
if token := b.GetString(tokenConfig); token != "" {
|
||||
b.Log.Info("Connecting using token")
|
||||
|
||||
b.sc = slack.New(token, slack.OptionDebug(b.GetBool("Debug")))
|
||||
|
||||
b.channels = newChannelManager(b.Log, b.sc)
|
||||
b.users = newUserManager(b.Log, b.sc)
|
||||
|
||||
b.rtm = b.sc.NewRTM()
|
||||
go b.rtm.ManageConnection()
|
||||
go b.handleSlack()
|
||||
@ -163,9 +151,9 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.populateChannels(false)
|
||||
b.channels.populateChannels(false)
|
||||
|
||||
channelInfo, err := b.getChannel(channel.Name)
|
||||
channelInfo, err := b.channels.getChannel(channel.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not join channel: %#v", err)
|
||||
}
|
||||
@ -275,7 +263,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
channelInfo, err := b.getChannel(msg.Channel)
|
||||
channelInfo, err := b.channels.getChannel(msg.Channel)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not send message: %v", err)
|
||||
}
|
||||
@ -351,7 +339,7 @@ func (b *Bslack) updateTopicOrPurpose(msg *config.Message, channelInfo *slack.Ch
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
if err = handleRateLimit(b.Log, err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -392,7 +380,7 @@ func (b *Bslack) deleteMessage(msg *config.Message, channelInfo *slack.Channel)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
if err = handleRateLimit(b.Log, err); err != nil {
|
||||
b.Log.Errorf("Failed to delete user message from Slack: %#v", err)
|
||||
return true, err
|
||||
}
|
||||
@ -411,7 +399,7 @@ func (b *Bslack) editMessage(msg *config.Message, channelInfo *slack.Channel) (b
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
if err = handleRateLimit(b.Log, err); err != nil {
|
||||
b.Log.Errorf("Failed to edit user message on Slack: %#v", err)
|
||||
return true, err
|
||||
}
|
||||
@ -424,14 +412,18 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
|
||||
return "", nil
|
||||
}
|
||||
messageOptions := b.prepareMessageOptions(msg)
|
||||
messageOptions = append(messageOptions, slack.MsgOptionText(msg.Text, false))
|
||||
messageOptions = append(
|
||||
messageOptions,
|
||||
slack.MsgOptionText(msg.Text, false),
|
||||
slack.MsgOptionEnableLinkUnfurl(),
|
||||
)
|
||||
for {
|
||||
_, id, err := b.rtm.PostMessage(channelInfo.ID, messageOptions...)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
if err = handleRateLimit(b.Log, err); err != nil {
|
||||
b.Log.Errorf("Failed to sent user message to Slack: %#v", err)
|
||||
return "", err
|
||||
}
|
||||
|
335
bridge/slack/users_channels.go
Normal file
335
bridge/slack/users_channels.go
Normal file
@ -0,0 +1,335 @@
|
||||
package bslack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/nlopes/slack"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const minimumRefreshInterval = 10 * time.Second
|
||||
|
||||
type users struct {
|
||||
log *logrus.Entry
|
||||
sc *slack.Client
|
||||
|
||||
users map[string]*slack.User
|
||||
usersMutex sync.RWMutex
|
||||
usersSyncPoints map[string]chan struct{}
|
||||
|
||||
refreshInProgress bool
|
||||
earliestRefresh time.Time
|
||||
refreshMutex sync.Mutex
|
||||
}
|
||||
|
||||
func newUserManager(log *logrus.Entry, sc *slack.Client) *users {
|
||||
return &users{
|
||||
log: log,
|
||||
sc: sc,
|
||||
users: make(map[string]*slack.User),
|
||||
usersSyncPoints: make(map[string]chan struct{}),
|
||||
earliestRefresh: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *users) getUser(id string) *slack.User {
|
||||
b.usersMutex.RLock()
|
||||
user, ok := b.users[id]
|
||||
b.usersMutex.RUnlock()
|
||||
if ok {
|
||||
return user
|
||||
}
|
||||
b.populateUser(id)
|
||||
b.usersMutex.RLock()
|
||||
defer b.usersMutex.RUnlock()
|
||||
|
||||
return b.users[id]
|
||||
}
|
||||
|
||||
func (b *users) getUsername(id string) string {
|
||||
if user := b.getUser(id); user != nil {
|
||||
if user.Profile.DisplayName != "" {
|
||||
return user.Profile.DisplayName
|
||||
}
|
||||
return user.Name
|
||||
}
|
||||
b.log.Warnf("Could not find user with ID '%s'", id)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *users) getAvatar(id string) string {
|
||||
if user := b.getUser(id); user != nil {
|
||||
return user.Profile.Image48
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *users) populateUser(userID string) {
|
||||
for {
|
||||
b.usersMutex.Lock()
|
||||
_, exists := b.users[userID]
|
||||
if exists {
|
||||
// already in cache
|
||||
b.usersMutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if syncPoint, ok := b.usersSyncPoints[userID]; ok {
|
||||
// Another goroutine is already populating this user for us so wait on it to finish.
|
||||
b.usersMutex.Unlock()
|
||||
<-syncPoint
|
||||
// We do not return and iterate again to check that the entry does indeed exist
|
||||
// in case the previous query failed for some reason.
|
||||
} else {
|
||||
b.usersSyncPoints[userID] = make(chan struct{})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Do not hold the lock while fetching information from Slack
|
||||
// as this might take an unbounded amount of time.
|
||||
b.usersMutex.Unlock()
|
||||
|
||||
user, err := b.sc.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
b.log.Debugf("GetUserInfo failed for %v: %v", userID, err)
|
||||
return
|
||||
}
|
||||
|
||||
b.usersMutex.Lock()
|
||||
defer b.usersMutex.Unlock()
|
||||
|
||||
// Register user information.
|
||||
b.users[userID] = user
|
||||
|
||||
// Wake up any waiting goroutines and remove the synchronization point.
|
||||
close(b.usersSyncPoints[userID])
|
||||
delete(b.usersSyncPoints, userID)
|
||||
}
|
||||
|
||||
func (b *users) populateUsers(wait bool) {
|
||||
b.refreshMutex.Lock()
|
||||
if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
|
||||
b.log.Debugf("Not refreshing user list as it was done less than %v ago.", minimumRefreshInterval)
|
||||
b.refreshMutex.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
for b.refreshInProgress {
|
||||
b.refreshMutex.Unlock()
|
||||
time.Sleep(time.Second)
|
||||
b.refreshMutex.Lock()
|
||||
}
|
||||
b.refreshInProgress = true
|
||||
b.refreshMutex.Unlock()
|
||||
|
||||
newUsers := map[string]*slack.User{}
|
||||
pagination := b.sc.GetUsersPaginated(slack.GetUsersOptionLimit(200))
|
||||
count := 0
|
||||
for {
|
||||
var err error
|
||||
pagination, err = pagination.Next(context.Background())
|
||||
time.Sleep(time.Second)
|
||||
if err != nil {
|
||||
if pagination.Done(err) {
|
||||
break
|
||||
}
|
||||
|
||||
if err = handleRateLimit(b.log, err); err != nil {
|
||||
b.log.Errorf("Could not retrieve users: %#v", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range pagination.Users {
|
||||
newUsers[pagination.Users[i].ID] = &pagination.Users[i]
|
||||
}
|
||||
b.log.Debugf("getting %d users", len(pagination.Users))
|
||||
count++
|
||||
// more > 2000 users, slack will complain and ratelimit. break
|
||||
if count > 10 {
|
||||
b.log.Info("Large slack detected > 2000 users, skipping loading complete userlist.")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.usersMutex.Lock()
|
||||
defer b.usersMutex.Unlock()
|
||||
b.users = newUsers
|
||||
|
||||
b.refreshMutex.Lock()
|
||||
defer b.refreshMutex.Unlock()
|
||||
b.earliestRefresh = time.Now().Add(minimumRefreshInterval)
|
||||
b.refreshInProgress = false
|
||||
}
|
||||
|
||||
type channels struct {
|
||||
log *logrus.Entry
|
||||
sc *slack.Client
|
||||
|
||||
channelsByID map[string]*slack.Channel
|
||||
channelsByName map[string]*slack.Channel
|
||||
channelsMutex sync.RWMutex
|
||||
|
||||
channelMembers map[string][]string
|
||||
channelMembersMutex sync.RWMutex
|
||||
|
||||
refreshInProgress bool
|
||||
earliestRefresh time.Time
|
||||
refreshMutex sync.Mutex
|
||||
}
|
||||
|
||||
func newChannelManager(log *logrus.Entry, sc *slack.Client) *channels {
|
||||
return &channels{
|
||||
log: log,
|
||||
sc: sc,
|
||||
channelsByID: make(map[string]*slack.Channel),
|
||||
channelsByName: make(map[string]*slack.Channel),
|
||||
earliestRefresh: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *channels) getChannel(channel string) (*slack.Channel, error) {
|
||||
if strings.HasPrefix(channel, "ID:") {
|
||||
return b.getChannelByID(strings.TrimPrefix(channel, "ID:"))
|
||||
}
|
||||
return b.getChannelByName(channel)
|
||||
}
|
||||
|
||||
func (b *channels) getChannelByName(name string) (*slack.Channel, error) {
|
||||
return b.getChannelBy(name, b.channelsByName)
|
||||
}
|
||||
|
||||
func (b *channels) getChannelByID(id string) (*slack.Channel, error) {
|
||||
return b.getChannelBy(id, b.channelsByID)
|
||||
}
|
||||
|
||||
func (b *channels) getChannelBy(lookupKey string, lookupMap map[string]*slack.Channel) (*slack.Channel, error) {
|
||||
b.channelsMutex.RLock()
|
||||
defer b.channelsMutex.RUnlock()
|
||||
|
||||
if channel, ok := lookupMap[lookupKey]; ok {
|
||||
return channel, nil
|
||||
}
|
||||
return nil, fmt.Errorf("channel %s not found", lookupKey)
|
||||
}
|
||||
|
||||
func (b *channels) getChannelMembers(users *users) config.ChannelMembers {
|
||||
b.channelMembersMutex.RLock()
|
||||
defer b.channelMembersMutex.RUnlock()
|
||||
|
||||
membersInfo := config.ChannelMembers{}
|
||||
for channelID, members := range b.channelMembers {
|
||||
for _, member := range members {
|
||||
channelName := ""
|
||||
userName := ""
|
||||
userNick := ""
|
||||
user := users.getUser(member)
|
||||
if user != nil {
|
||||
userName = user.Name
|
||||
userNick = user.Profile.DisplayName
|
||||
}
|
||||
channel, _ := b.getChannelByID(channelID)
|
||||
if channel != nil {
|
||||
channelName = channel.Name
|
||||
}
|
||||
memberInfo := config.ChannelMember{
|
||||
Username: userName,
|
||||
Nick: userNick,
|
||||
UserID: member,
|
||||
ChannelID: channelID,
|
||||
ChannelName: channelName,
|
||||
}
|
||||
membersInfo = append(membersInfo, memberInfo)
|
||||
}
|
||||
}
|
||||
return membersInfo
|
||||
}
|
||||
|
||||
func (b *channels) registerChannel(channel slack.Channel) {
|
||||
b.channelsMutex.Lock()
|
||||
defer b.channelsMutex.Unlock()
|
||||
|
||||
b.channelsByID[channel.ID] = &channel
|
||||
b.channelsByName[channel.Name] = &channel
|
||||
}
|
||||
|
||||
func (b *channels) populateChannels(wait bool) {
|
||||
b.refreshMutex.Lock()
|
||||
if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
|
||||
b.log.Debugf("Not refreshing channel list as it was done less than %v seconds ago.", minimumRefreshInterval)
|
||||
b.refreshMutex.Unlock()
|
||||
return
|
||||
}
|
||||
for b.refreshInProgress {
|
||||
b.refreshMutex.Unlock()
|
||||
time.Sleep(time.Second)
|
||||
b.refreshMutex.Lock()
|
||||
}
|
||||
b.refreshInProgress = true
|
||||
b.refreshMutex.Unlock()
|
||||
|
||||
newChannelsByID := map[string]*slack.Channel{}
|
||||
newChannelsByName := map[string]*slack.Channel{}
|
||||
newChannelMembers := make(map[string][]string)
|
||||
|
||||
// We only retrieve public and private channels, not IMs
|
||||
// and MPIMs as those do not have a channel name.
|
||||
queryParams := &slack.GetConversationsParameters{
|
||||
ExcludeArchived: "true",
|
||||
Types: []string{"public_channel,private_channel"},
|
||||
}
|
||||
for {
|
||||
channels, nextCursor, err := b.sc.GetConversations(queryParams)
|
||||
if err != nil {
|
||||
if err = handleRateLimit(b.log, err); err != nil {
|
||||
b.log.Errorf("Could not retrieve channels: %#v", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range channels {
|
||||
newChannelsByID[channels[i].ID] = &channels[i]
|
||||
newChannelsByName[channels[i].Name] = &channels[i]
|
||||
// also find all the members in every channel
|
||||
// comment for now, issues on big slacks
|
||||
/*
|
||||
members, err := b.getUsersInConversation(channels[i].ID)
|
||||
if err != nil {
|
||||
if err = b.handleRateLimit(err); err != nil {
|
||||
b.Log.Errorf("Could not retrieve channel members: %#v", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
newChannelMembers[channels[i].ID] = members
|
||||
*/
|
||||
}
|
||||
|
||||
if nextCursor == "" {
|
||||
break
|
||||
}
|
||||
queryParams.Cursor = nextCursor
|
||||
}
|
||||
|
||||
b.channelsMutex.Lock()
|
||||
defer b.channelsMutex.Unlock()
|
||||
b.channelsByID = newChannelsByID
|
||||
b.channelsByName = newChannelsByName
|
||||
|
||||
b.channelMembersMutex.Lock()
|
||||
defer b.channelMembersMutex.Unlock()
|
||||
b.channelMembers = newChannelMembers
|
||||
|
||||
b.refreshMutex.Lock()
|
||||
defer b.refreshMutex.Unlock()
|
||||
b.earliestRefresh = time.Now().Add(minimumRefreshInterval)
|
||||
b.refreshInProgress = false
|
||||
}
|
@ -125,6 +125,11 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
// handle groups
|
||||
message = b.handleGroups(&rmsg, message, update)
|
||||
|
||||
if message == nil {
|
||||
b.Log.Error("message is nil, this shouldn't happen.")
|
||||
continue
|
||||
}
|
||||
|
||||
// set the ID's from the channel or group message
|
||||
rmsg.ID = strconv.Itoa(message.MessageID)
|
||||
rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
|
||||
|
25
changelog.md
25
changelog.md
@ -1,3 +1,22 @@
|
||||
# v1.14.2
|
||||
|
||||
## Bugfix
|
||||
* general: Update tengo vendor and load the stdlib. Fixes #789 (#792)
|
||||
* rocketchat: Look up #channel too (rocketchat). Fix #773 (#775)
|
||||
* slack: Ignore messagereplied and hidden messages (slack). Fixes #709 (#779)
|
||||
* telegram: Handle nil message (telegram). Fixes #777
|
||||
* irc: Use default nick if none specified (irc). Fixes #785
|
||||
* irc: Return when not connected and drop a message (irc). Fixes #786
|
||||
* irc: Revert fix for #722 (Support quits from irc correctly). Closes #781
|
||||
|
||||
## Contributors
|
||||
This release couldn't exist without the following contributors:
|
||||
@42wim, @Helcaraxan, @dajohi
|
||||
|
||||
# v1.14.1
|
||||
## Bugfix
|
||||
* slack: Fix crash double unlock (slack) (#771)
|
||||
|
||||
# v1.14.0
|
||||
|
||||
## Breaking
|
||||
@ -19,6 +38,8 @@
|
||||
|
||||
## Enhancements
|
||||
* general: Fail gracefully on incorrect human input. Fixes #739 (#740)
|
||||
* matrix: Detect html nicks in RemoteNickFormat (matrix). Fixes #696 (#719)
|
||||
* matrix: Send notices on join/parts (matrix). Fixes #712 (#716)
|
||||
|
||||
## Bugfix
|
||||
* general: Handle file upload/download only once for each message (#742)
|
||||
@ -27,9 +48,9 @@
|
||||
* irc: add support for (older) unrealircd versions. #708
|
||||
* irc: Support quits from irc correctly. Fixes #722 (#724)
|
||||
* matrix: Send username when uploading video/images (matrix). Fixes #715 (#717)
|
||||
* matrix: Send notices on join/parts (matrix). Fixes #712 (#716)
|
||||
* matrix: Detect html nicks in RemoteNickFormat (matrix). Fixes #696 (#719)
|
||||
* matrix: Trim <p> and </p> tags (matrix). Closes #686 (#753)
|
||||
* slack: Hint at thread replies when messages are unthreaded (slack) (#684)
|
||||
* slack: Fix race-condition in populateUser() (#767)
|
||||
* xmpp: Do not send topic changes on connect (xmpp). Fixes #732 (#733)
|
||||
* telegram: Fix regression in HTML handling (telegram). Closes #734
|
||||
* discord: Do not relay any bot messages (discord) (#743)
|
||||
|
@ -1,5 +1,8 @@
|
||||
#!/bin/bash
|
||||
go version | grep go1.11 || exit
|
||||
#!/usr/bin/env bash
|
||||
set -u -e -x -o pipefail
|
||||
|
||||
go version | grep go1.12 || exit
|
||||
|
||||
VERSION=$(git describe --tags)
|
||||
mkdir ci/binaries
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe
|
||||
|
17
ci/lint.sh
Executable file
17
ci/lint.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -e -x -o pipefail
|
||||
|
||||
if [[ -n "${GOLANGCI_VERSION-}" ]]; then
|
||||
# Retrieve the golangci-lint linter binary.
|
||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b ${GOPATH}/bin ${GOLANGCI_VERSION}
|
||||
fi
|
||||
|
||||
# Run the linter.
|
||||
golangci-lint run
|
||||
|
||||
if [[ "${GO111MODULE-off}" == "on" ]]; then
|
||||
# If Go modules are active then check that dependencies are correctly maintained.
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
git diff --exit-code --quiet || (echo "Please run 'go mod tidy' to clean up the 'go.mod' and 'go.sum' files."; false)
|
||||
fi
|
17
ci/test.sh
Executable file
17
ci/test.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -e -x -o pipefail
|
||||
|
||||
if [[ -n "${REPORT_COVERAGE+cover}" ]]; then
|
||||
# Retrieve and prepare CodeClimate's test coverage reporter.
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||
chmod +x ./cc-test-reporter
|
||||
./cc-test-reporter before-build
|
||||
fi
|
||||
|
||||
# Run all the tests with the race detector and generate coverage.
|
||||
go test -v -race -coverprofile c.out ./...
|
||||
|
||||
if [[ -n "${REPORT_COVERAGE+cover}" && "${TRAVIS_SECURE_ENV_VARS}" == "true" ]]; then
|
||||
# Upload test coverage to CodeClimate.
|
||||
./cc-test-reporter after-build
|
||||
fi
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/d5/tengo/script"
|
||||
"github.com/d5/tengo/stdlib"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/peterhellberg/emojilib"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -211,23 +212,6 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
|
||||
return channels
|
||||
}
|
||||
|
||||
// irc quit is for the whole bridge, isn't a per channel quit.
|
||||
// channel is empty when we quit
|
||||
if msg.Event == config.EventJoinLeave && getProtocol(msg) == "irc" && msg.Channel == "" {
|
||||
// if we only have one channel on this irc bridge it's got to be the sending one.
|
||||
// don't send it back
|
||||
if dest.Account == msg.Account && len(dest.Channels) == 1 && dest.Protocol == "irc" {
|
||||
return channels
|
||||
}
|
||||
for _, channel := range gw.Channels {
|
||||
if channel.Account == dest.Account && strings.Contains(channel.Direction, "out") &&
|
||||
gw.validGatewayDest(msg) {
|
||||
channels = append(channels, *channel)
|
||||
}
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
// if source channel is in only, do nothing
|
||||
for _, channel := range gw.Channels {
|
||||
// lookup the channel from the message
|
||||
@ -503,6 +487,7 @@ func modifyMessageTengo(filename string, msg *config.Message) error {
|
||||
return err
|
||||
}
|
||||
s := script.New(res)
|
||||
s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||||
_ = s.Add("msgText", msg.Text)
|
||||
_ = s.Add("msgUsername", msg.Username)
|
||||
_ = s.Add("msgAccount", msg.Account)
|
||||
|
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ require (
|
||||
github.com/Jeffail/gabs v1.1.1 // indirect
|
||||
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
|
||||
github.com/bwmarrin/discordgo v0.19.0
|
||||
github.com/d5/tengo v1.9.2
|
||||
github.com/d5/tengo v1.20.0
|
||||
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible
|
||||
|
4
go.sum
4
go.sum
@ -15,8 +15,8 @@ github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVO
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/d5/tengo v1.9.2 h1:UE/X8PYl7bLS4Ww2zGeh91nq5PTnkhe8ncgNeA5PK7k=
|
||||
github.com/d5/tengo v1.9.2/go.mod h1:gsbjo7lBXzBIWBd6NQp1lRKqqiDDANqBOyhW8rTlFsY=
|
||||
github.com/d5/tengo v1.20.0 h1:lFmktzEGR6khlZu2MHUWJ5oDWS4l3jNRV/OhclZgcYc=
|
||||
github.com/d5/tengo v1.20.0/go.mod h1:gsbjo7lBXzBIWBd6NQp1lRKqqiDDANqBOyhW8rTlFsY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "1.14.0-rc2"
|
||||
version = "1.14.2"
|
||||
githash string
|
||||
|
||||
flagConfig = flag.String("conf", "matterbridge.toml", "config file")
|
||||
|
@ -1007,9 +1007,10 @@ ShowTopicChange=false
|
||||
Server="https://yourrocketchatserver.domain.com:443"
|
||||
|
||||
#login/pass of your bot.
|
||||
#login needs to be the login with email address! user@domain.com
|
||||
#Use a dedicated user for this and not your own!
|
||||
#REQUIRED (when not using webhooks)
|
||||
Login="yourlogin"
|
||||
Login="yourlogin@domain.com"
|
||||
Password="yourpass"
|
||||
|
||||
#### Settings for webhook matterbridge.
|
||||
@ -1050,6 +1051,8 @@ SkipTLSVerify=true
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
|
||||
#from bridge to rocketchat will by default be prefixed by the RemoteNickFormat setting. i
|
||||
#if you're using login/pass you can better enable because of this bug:
|
||||
#https://github.com/RocketChat/Rocket.Chat/issues/7549
|
||||
#OPTIONAL (default false)
|
||||
PrefixMessagesWithNick=false
|
||||
|
||||
|
@ -51,8 +51,9 @@ func (m *MMClient) GetChannelId(name string, teamId string) string { //nolint:go
|
||||
if res == name {
|
||||
return channel.Id
|
||||
}
|
||||
} else if channel.Name == name {
|
||||
return channel.Id
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
@ -216,9 +216,14 @@ func (m *MMClient) WsReceiver() {
|
||||
if msg.Post != nil {
|
||||
if msg.Text != "" || len(msg.Post.FileIds) > 0 || msg.Post.Type == "slack_attachment" {
|
||||
m.MessageChan <- msg
|
||||
continue
|
||||
}
|
||||
}
|
||||
continue
|
||||
switch msg.Raw.Event {
|
||||
case model.WEBSOCKET_EVENT_USER_ADDED, model.WEBSOCKET_EVENT_USER_REMOVED:
|
||||
m.MessageChan <- msg
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var response model.WebSocketResponse
|
||||
|
1
vendor/github.com/d5/tengo/.gitignore
generated
vendored
Normal file
1
vendor/github.com/d5/tengo/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist/
|
23
vendor/github.com/d5/tengo/.goreleaser.yml
generated
vendored
Normal file
23
vendor/github.com/d5/tengo/.goreleaser.yml
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
main: ./cmd/tengo/main.go
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
main: ./cmd/tengomin/main.go
|
||||
binary: tengomin
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
archive:
|
||||
files:
|
||||
- none*
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
changelog:
|
||||
sort: asc
|
17
vendor/github.com/d5/tengo/.travis.yml
generated
vendored
Normal file
17
vendor/github.com/d5/tengo/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.9
|
||||
|
||||
install:
|
||||
- go get -u golang.org/x/lint/golint
|
||||
|
||||
script:
|
||||
- make test
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: curl -sL https://git.io/goreleaser | bash
|
||||
on:
|
||||
tags: true
|
14
vendor/github.com/d5/tengo/Makefile
generated
vendored
Normal file
14
vendor/github.com/d5/tengo/Makefile
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
generate:
|
||||
go generate ./...
|
||||
|
||||
lint:
|
||||
golint -set_exit_status ./...
|
||||
|
||||
test: generate vet lint
|
||||
go test -race -cover ./...
|
||||
|
||||
fmt:
|
||||
go fmt ./...
|
76
vendor/github.com/d5/tengo/README.md
generated
vendored
Normal file
76
vendor/github.com/d5/tengo/README.md
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/d5/tengolang.com/master/logo_400.png" width="200" height="200">
|
||||
</p>
|
||||
|
||||
# The Tengo Language
|
||||
|
||||
[](https://godoc.org/github.com/d5/tengo/script)
|
||||
[](https://goreportcard.com/report/github.com/d5/tengo)
|
||||
[](https://travis-ci.org/d5/tengo)
|
||||
|
||||
**Tengo is a small, dynamic, fast, secure script language for Go.**
|
||||
|
||||
Tengo is **[fast](#benchmark)** and secure because it's compiled/executed as bytecode on stack-based VM that's written in native Go.
|
||||
|
||||
```golang
|
||||
/* The Tengo Language */
|
||||
|
||||
fmt := import("fmt")
|
||||
|
||||
each := func(seq, fn) {
|
||||
for x in seq { fn(x) }
|
||||
}
|
||||
|
||||
sum := func(init, seq) {
|
||||
each(seq, func(x) { init += x })
|
||||
return init
|
||||
}
|
||||
|
||||
fmt.println(sum(0, [1, 2, 3])) // "6"
|
||||
fmt.println(sum("", [1, 2, 3])) // "123"
|
||||
```
|
||||
|
||||
> Run this code in the [Playground](https://tengolang.com/?s=0c8d5d0d88f2795a7093d7f35ae12c3afa17bea3)
|
||||
|
||||
## Features
|
||||
|
||||
- Simple and highly readable [Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
|
||||
- Dynamic typing with type coercion
|
||||
- Higher-order functions and closures
|
||||
- Immutable values
|
||||
- Garbage collection
|
||||
- [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md) and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.md)
|
||||
- Compiler/runtime written in native Go _(no external deps or cgo)_
|
||||
- Executable as a [standalone](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) language / REPL
|
||||
- Use cases: rules engine, [state machine](https://github.com/d5/go-fsm), [gaming](https://github.com/d5/pbr), data pipeline, [transpiler](https://github.com/d5/tengo2lua)
|
||||
|
||||
## Benchmark
|
||||
|
||||
| | fib(35) | fibt(35) | Type |
|
||||
| :--- | ---: | ---: | :---: |
|
||||
| Go | `48ms` | `3ms` | Go (native) |
|
||||
| [**Tengo**](https://github.com/d5/tengo) | `2,349ms` | `5ms` | VM on Go |
|
||||
| Lua | `1,416ms` | `3ms` | Lua (native) |
|
||||
| [go-lua](https://github.com/Shopify/go-lua) | `4,402ms` | `5ms` | Lua VM on Go |
|
||||
| [GopherLua](https://github.com/yuin/gopher-lua) | `4,023ms` | `5ms` | Lua VM on Go |
|
||||
| Python | `2,588ms` | `26ms` | Python (native) |
|
||||
| [starlark-go](https://github.com/google/starlark-go) | `11,126ms` | `6ms` | Python-like Interpreter on Go |
|
||||
| [gpython](https://github.com/go-python/gpython) | `15,035ms` | `4ms` | Python Interpreter on Go |
|
||||
| [goja](https://github.com/dop251/goja) | `5,089ms` | `5ms` | JS VM on Go |
|
||||
| [otto](https://github.com/robertkrimen/otto) | `68,377ms` | `11ms` | JS Interpreter on Go |
|
||||
| [Anko](https://github.com/mattn/anko) | `92,579ms` | `18ms` | Interpreter on Go |
|
||||
|
||||
_* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo): Fibonacci(35)_
|
||||
_* [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo): [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of Fibonacci(35)_
|
||||
_* **Go** does not read the source code from file, while all other cases do_
|
||||
_* See [here](https://github.com/d5/tengobench) for commands/codes used_
|
||||
|
||||
## References
|
||||
|
||||
- [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
|
||||
- [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md)
|
||||
- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) and [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md)
|
||||
- [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md)
|
||||
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
|
||||
- [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md)
|
||||
- [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md)
|
66
vendor/github.com/d5/tengo/compiler/bytecode.go
generated
vendored
66
vendor/github.com/d5/tengo/compiler/bytecode.go
generated
vendored
@ -17,32 +17,6 @@ type Bytecode struct {
|
||||
Constants []objects.Object
|
||||
}
|
||||
|
||||
// Decode reads Bytecode data from the reader.
|
||||
func (b *Bytecode) Decode(r io.Reader) error {
|
||||
dec := gob.NewDecoder(r)
|
||||
|
||||
if err := dec.Decode(&b.FileSet); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet
|
||||
// as it's private field and not serialized by gob encoder/decoder.
|
||||
|
||||
if err := dec.Decode(&b.MainFunction); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dec.Decode(&b.Constants); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// replace Bool and Undefined with known value
|
||||
for i, v := range b.Constants {
|
||||
b.Constants[i] = cleanupObjects(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode writes Bytecode data to the writer.
|
||||
func (b *Bytecode) Encode(w io.Writer) error {
|
||||
enc := gob.NewEncoder(w)
|
||||
@ -59,6 +33,17 @@ func (b *Bytecode) Encode(w io.Writer) error {
|
||||
return enc.Encode(b.Constants)
|
||||
}
|
||||
|
||||
// CountObjects returns the number of objects found in Constants.
|
||||
func (b *Bytecode) CountObjects() int {
|
||||
n := 0
|
||||
|
||||
for _, c := range b.Constants {
|
||||
n += objects.CountObjects(c)
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// FormatInstructions returns human readable string representations of
|
||||
// compiled instructions.
|
||||
func (b *Bytecode) FormatInstructions() []string {
|
||||
@ -83,51 +68,22 @@ func (b *Bytecode) FormatConstants() (output []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func cleanupObjects(o objects.Object) objects.Object {
|
||||
switch o := o.(type) {
|
||||
case *objects.Bool:
|
||||
if o.IsFalsy() {
|
||||
return objects.FalseValue
|
||||
}
|
||||
return objects.TrueValue
|
||||
case *objects.Undefined:
|
||||
return objects.UndefinedValue
|
||||
case *objects.Array:
|
||||
for i, v := range o.Value {
|
||||
o.Value[i] = cleanupObjects(v)
|
||||
}
|
||||
case *objects.Map:
|
||||
for k, v := range o.Value {
|
||||
o.Value[k] = cleanupObjects(v)
|
||||
}
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(&source.FileSet{})
|
||||
gob.Register(&source.File{})
|
||||
gob.Register(&objects.Array{})
|
||||
gob.Register(&objects.ArrayIterator{})
|
||||
gob.Register(&objects.Bool{})
|
||||
gob.Register(&objects.Break{})
|
||||
gob.Register(&objects.BuiltinFunction{})
|
||||
gob.Register(&objects.Bytes{})
|
||||
gob.Register(&objects.Char{})
|
||||
gob.Register(&objects.Closure{})
|
||||
gob.Register(&objects.CompiledFunction{})
|
||||
gob.Register(&objects.Continue{})
|
||||
gob.Register(&objects.Error{})
|
||||
gob.Register(&objects.Float{})
|
||||
gob.Register(&objects.ImmutableArray{})
|
||||
gob.Register(&objects.ImmutableMap{})
|
||||
gob.Register(&objects.Int{})
|
||||
gob.Register(&objects.Map{})
|
||||
gob.Register(&objects.MapIterator{})
|
||||
gob.Register(&objects.ReturnValue{})
|
||||
gob.Register(&objects.String{})
|
||||
gob.Register(&objects.StringIterator{})
|
||||
gob.Register(&objects.Time{})
|
||||
gob.Register(&objects.Undefined{})
|
||||
gob.Register(&objects.UserFunction{})
|
||||
|
97
vendor/github.com/d5/tengo/compiler/bytecode_decode.go
generated
vendored
Normal file
97
vendor/github.com/d5/tengo/compiler/bytecode_decode.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
// Decode reads Bytecode data from the reader.
|
||||
func (b *Bytecode) Decode(r io.Reader, modules *objects.ModuleMap) error {
|
||||
if modules == nil {
|
||||
modules = objects.NewModuleMap()
|
||||
}
|
||||
|
||||
dec := gob.NewDecoder(r)
|
||||
|
||||
if err := dec.Decode(&b.FileSet); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet
|
||||
// as it's private field and not serialized by gob encoder/decoder.
|
||||
|
||||
if err := dec.Decode(&b.MainFunction); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dec.Decode(&b.Constants); err != nil {
|
||||
return err
|
||||
}
|
||||
for i, v := range b.Constants {
|
||||
fv, err := fixDecoded(v, modules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Constants[i] = fv
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fixDecoded(o objects.Object, modules *objects.ModuleMap) (objects.Object, error) {
|
||||
switch o := o.(type) {
|
||||
case *objects.Bool:
|
||||
if o.IsFalsy() {
|
||||
return objects.FalseValue, nil
|
||||
}
|
||||
return objects.TrueValue, nil
|
||||
case *objects.Undefined:
|
||||
return objects.UndefinedValue, nil
|
||||
case *objects.Array:
|
||||
for i, v := range o.Value {
|
||||
fv, err := fixDecoded(v, modules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Value[i] = fv
|
||||
}
|
||||
case *objects.ImmutableArray:
|
||||
for i, v := range o.Value {
|
||||
fv, err := fixDecoded(v, modules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Value[i] = fv
|
||||
}
|
||||
case *objects.Map:
|
||||
for k, v := range o.Value {
|
||||
fv, err := fixDecoded(v, modules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Value[k] = fv
|
||||
}
|
||||
case *objects.ImmutableMap:
|
||||
modName := moduleName(o)
|
||||
if mod := modules.GetBuiltinModule(modName); mod != nil {
|
||||
return mod.AsImmutableMap(modName), nil
|
||||
}
|
||||
|
||||
for k, v := range o.Value {
|
||||
// encoding of user function not supported
|
||||
if _, isUserFunction := v.(*objects.UserFunction); isUserFunction {
|
||||
return nil, fmt.Errorf("user function not decodable")
|
||||
}
|
||||
|
||||
fv, err := fixDecoded(v, modules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Value[k] = fv
|
||||
}
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
129
vendor/github.com/d5/tengo/compiler/bytecode_optimize.go
generated
vendored
Normal file
129
vendor/github.com/d5/tengo/compiler/bytecode_optimize.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
// RemoveDuplicates finds and remove the duplicate values in Constants.
|
||||
// Note this function mutates Bytecode.
|
||||
func (b *Bytecode) RemoveDuplicates() {
|
||||
var deduped []objects.Object
|
||||
|
||||
indexMap := make(map[int]int) // mapping from old constant index to new index
|
||||
ints := make(map[int64]int)
|
||||
strings := make(map[string]int)
|
||||
floats := make(map[float64]int)
|
||||
chars := make(map[rune]int)
|
||||
immutableMaps := make(map[string]int) // for modules
|
||||
|
||||
for curIdx, c := range b.Constants {
|
||||
switch c := c.(type) {
|
||||
case *objects.CompiledFunction:
|
||||
// add to deduped list
|
||||
indexMap[curIdx] = len(deduped)
|
||||
deduped = append(deduped, c)
|
||||
case *objects.ImmutableMap:
|
||||
modName := moduleName(c)
|
||||
newIdx, ok := immutableMaps[modName]
|
||||
if modName != "" && ok {
|
||||
indexMap[curIdx] = newIdx
|
||||
} else {
|
||||
newIdx = len(deduped)
|
||||
immutableMaps[modName] = newIdx
|
||||
indexMap[curIdx] = newIdx
|
||||
deduped = append(deduped, c)
|
||||
}
|
||||
case *objects.Int:
|
||||
if newIdx, ok := ints[c.Value]; ok {
|
||||
indexMap[curIdx] = newIdx
|
||||
} else {
|
||||
newIdx = len(deduped)
|
||||
ints[c.Value] = newIdx
|
||||
indexMap[curIdx] = newIdx
|
||||
deduped = append(deduped, c)
|
||||
}
|
||||
case *objects.String:
|
||||
if newIdx, ok := strings[c.Value]; ok {
|
||||
indexMap[curIdx] = newIdx
|
||||
} else {
|
||||
newIdx = len(deduped)
|
||||
strings[c.Value] = newIdx
|
||||
indexMap[curIdx] = newIdx
|
||||
deduped = append(deduped, c)
|
||||
}
|
||||
case *objects.Float:
|
||||
if newIdx, ok := floats[c.Value]; ok {
|
||||
indexMap[curIdx] = newIdx
|
||||
} else {
|
||||
newIdx = len(deduped)
|
||||
floats[c.Value] = newIdx
|
||||
indexMap[curIdx] = newIdx
|
||||
deduped = append(deduped, c)
|
||||
}
|
||||
case *objects.Char:
|
||||
if newIdx, ok := chars[c.Value]; ok {
|
||||
indexMap[curIdx] = newIdx
|
||||
} else {
|
||||
newIdx = len(deduped)
|
||||
chars[c.Value] = newIdx
|
||||
indexMap[curIdx] = newIdx
|
||||
deduped = append(deduped, c)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported top-level constant type: %s", c.TypeName()))
|
||||
}
|
||||
}
|
||||
|
||||
// replace with de-duplicated constants
|
||||
b.Constants = deduped
|
||||
|
||||
// update CONST instructions with new indexes
|
||||
// main function
|
||||
updateConstIndexes(b.MainFunction.Instructions, indexMap)
|
||||
// other compiled functions in constants
|
||||
for _, c := range b.Constants {
|
||||
switch c := c.(type) {
|
||||
case *objects.CompiledFunction:
|
||||
updateConstIndexes(c.Instructions, indexMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateConstIndexes(insts []byte, indexMap map[int]int) {
|
||||
i := 0
|
||||
for i < len(insts) {
|
||||
op := insts[i]
|
||||
numOperands := OpcodeOperands[op]
|
||||
_, read := ReadOperands(numOperands, insts[i+1:])
|
||||
|
||||
switch op {
|
||||
case OpConstant:
|
||||
curIdx := int(insts[i+2]) | int(insts[i+1])<<8
|
||||
newIdx, ok := indexMap[curIdx]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("constant index not found: %d", curIdx))
|
||||
}
|
||||
copy(insts[i:], MakeInstruction(op, newIdx))
|
||||
case OpClosure:
|
||||
curIdx := int(insts[i+2]) | int(insts[i+1])<<8
|
||||
numFree := int(insts[i+3])
|
||||
newIdx, ok := indexMap[curIdx]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("constant index not found: %d", curIdx))
|
||||
}
|
||||
copy(insts[i:], MakeInstruction(op, newIdx, numFree))
|
||||
}
|
||||
|
||||
i += 1 + read
|
||||
}
|
||||
}
|
||||
|
||||
func moduleName(mod *objects.ImmutableMap) string {
|
||||
if modName, ok := mod.Value["__module_name__"].(*objects.String); ok {
|
||||
return modName.Value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
7
vendor/github.com/d5/tengo/compiler/compilation_scope.go
generated
vendored
7
vendor/github.com/d5/tengo/compiler/compilation_scope.go
generated
vendored
@ -5,8 +5,7 @@ import "github.com/d5/tengo/compiler/source"
|
||||
// CompilationScope represents a compiled instructions
|
||||
// and the last two instructions that were emitted.
|
||||
type CompilationScope struct {
|
||||
instructions []byte
|
||||
lastInstructions [2]EmittedInstruction
|
||||
symbolInit map[string]bool
|
||||
sourceMap map[int]source.Pos
|
||||
instructions []byte
|
||||
symbolInit map[string]bool
|
||||
sourceMap map[int]source.Pos
|
||||
}
|
||||
|
288
vendor/github.com/d5/tengo/compiler/compiler.go
generated
vendored
288
vendor/github.com/d5/tengo/compiler/compiler.go
generated
vendored
@ -3,27 +3,30 @@ package compiler
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/compiler/ast"
|
||||
"github.com/d5/tengo/compiler/source"
|
||||
"github.com/d5/tengo/compiler/token"
|
||||
"github.com/d5/tengo/objects"
|
||||
"github.com/d5/tengo/stdlib"
|
||||
)
|
||||
|
||||
// Compiler compiles the AST into a bytecode.
|
||||
type Compiler struct {
|
||||
file *source.File
|
||||
parent *Compiler
|
||||
moduleName string
|
||||
modulePath string
|
||||
constants []objects.Object
|
||||
symbolTable *SymbolTable
|
||||
scopes []CompilationScope
|
||||
scopeIndex int
|
||||
moduleLoader ModuleLoader
|
||||
builtinModules map[string]bool
|
||||
modules *objects.ModuleMap
|
||||
compiledModules map[string]*objects.CompiledFunction
|
||||
allowFileImport bool
|
||||
loops []*Loop
|
||||
loopIndex int
|
||||
trace io.Writer
|
||||
@ -31,12 +34,7 @@ type Compiler struct {
|
||||
}
|
||||
|
||||
// NewCompiler creates a Compiler.
|
||||
// User can optionally provide the symbol table if one wants to add or remove
|
||||
// some global- or builtin- scope symbols. If not (nil), Compile will create
|
||||
// a new symbol table and use the default builtin functions. Likewise, standard
|
||||
// modules can be explicitly provided if user wants to add or remove some modules.
|
||||
// By default, Compile will use all the standard modules otherwise.
|
||||
func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, builtinModules map[string]bool, trace io.Writer) *Compiler {
|
||||
func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, modules *objects.ModuleMap, trace io.Writer) *Compiler {
|
||||
mainScope := CompilationScope{
|
||||
symbolInit: make(map[string]bool),
|
||||
sourceMap: make(map[int]source.Pos),
|
||||
@ -45,18 +43,16 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object
|
||||
// symbol table
|
||||
if symbolTable == nil {
|
||||
symbolTable = NewSymbolTable()
|
||||
}
|
||||
|
||||
for idx, fn := range objects.Builtins {
|
||||
symbolTable.DefineBuiltin(idx, fn.Name)
|
||||
}
|
||||
// add builtin functions to the symbol table
|
||||
for idx, fn := range objects.Builtins {
|
||||
symbolTable.DefineBuiltin(idx, fn.Name)
|
||||
}
|
||||
|
||||
// builtin modules
|
||||
if builtinModules == nil {
|
||||
builtinModules = make(map[string]bool)
|
||||
for name := range stdlib.Modules {
|
||||
builtinModules[name] = true
|
||||
}
|
||||
if modules == nil {
|
||||
modules = objects.NewModuleMap()
|
||||
}
|
||||
|
||||
return &Compiler{
|
||||
@ -67,7 +63,7 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object
|
||||
scopeIndex: 0,
|
||||
loopIndex: -1,
|
||||
trace: trace,
|
||||
builtinModules: builtinModules,
|
||||
modules: modules,
|
||||
compiledModules: make(map[string]*objects.CompiledFunction),
|
||||
}
|
||||
}
|
||||
@ -123,7 +119,7 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.emit(node, OpGreaterThan)
|
||||
c.emit(node, OpBinaryOp, int(token.Greater))
|
||||
|
||||
return nil
|
||||
} else if node.Token == token.LessEq {
|
||||
@ -134,7 +130,7 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.emit(node, OpGreaterThanEqual)
|
||||
c.emit(node, OpBinaryOp, int(token.GreaterEq))
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -148,35 +144,35 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
|
||||
switch node.Token {
|
||||
case token.Add:
|
||||
c.emit(node, OpAdd)
|
||||
c.emit(node, OpBinaryOp, int(token.Add))
|
||||
case token.Sub:
|
||||
c.emit(node, OpSub)
|
||||
c.emit(node, OpBinaryOp, int(token.Sub))
|
||||
case token.Mul:
|
||||
c.emit(node, OpMul)
|
||||
c.emit(node, OpBinaryOp, int(token.Mul))
|
||||
case token.Quo:
|
||||
c.emit(node, OpDiv)
|
||||
c.emit(node, OpBinaryOp, int(token.Quo))
|
||||
case token.Rem:
|
||||
c.emit(node, OpRem)
|
||||
c.emit(node, OpBinaryOp, int(token.Rem))
|
||||
case token.Greater:
|
||||
c.emit(node, OpGreaterThan)
|
||||
c.emit(node, OpBinaryOp, int(token.Greater))
|
||||
case token.GreaterEq:
|
||||
c.emit(node, OpGreaterThanEqual)
|
||||
c.emit(node, OpBinaryOp, int(token.GreaterEq))
|
||||
case token.Equal:
|
||||
c.emit(node, OpEqual)
|
||||
case token.NotEqual:
|
||||
c.emit(node, OpNotEqual)
|
||||
case token.And:
|
||||
c.emit(node, OpBAnd)
|
||||
c.emit(node, OpBinaryOp, int(token.And))
|
||||
case token.Or:
|
||||
c.emit(node, OpBOr)
|
||||
c.emit(node, OpBinaryOp, int(token.Or))
|
||||
case token.Xor:
|
||||
c.emit(node, OpBXor)
|
||||
c.emit(node, OpBinaryOp, int(token.Xor))
|
||||
case token.AndNot:
|
||||
c.emit(node, OpBAndNot)
|
||||
c.emit(node, OpBinaryOp, int(token.AndNot))
|
||||
case token.Shl:
|
||||
c.emit(node, OpBShiftLeft)
|
||||
c.emit(node, OpBinaryOp, int(token.Shl))
|
||||
case token.Shr:
|
||||
c.emit(node, OpBShiftRight)
|
||||
c.emit(node, OpBinaryOp, int(token.Shr))
|
||||
default:
|
||||
return c.errorf(node, "invalid binary operator: %s", node.Token.String())
|
||||
}
|
||||
@ -195,6 +191,10 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
}
|
||||
|
||||
case *ast.StringLit:
|
||||
if len(node.Value) > tengo.MaxStringLen {
|
||||
return c.error(node, objects.ErrStringLimit)
|
||||
}
|
||||
|
||||
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value}))
|
||||
|
||||
case *ast.CharLit:
|
||||
@ -292,6 +292,15 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
}
|
||||
|
||||
case *ast.BlockStmt:
|
||||
if len(node.Stmts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.symbolTable = c.symbolTable.Fork(true)
|
||||
defer func() {
|
||||
c.symbolTable = c.symbolTable.Parent(false)
|
||||
}()
|
||||
|
||||
for _, stmt := range node.Stmts {
|
||||
if err := c.Compile(stmt); err != nil {
|
||||
return err
|
||||
@ -332,6 +341,9 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
case *ast.MapLit:
|
||||
for _, elt := range node.Elements {
|
||||
// key
|
||||
if len(elt.Key) > tengo.MaxStringLen {
|
||||
return c.error(node, objects.ErrStringLimit)
|
||||
}
|
||||
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key}))
|
||||
|
||||
// value
|
||||
@ -401,10 +413,8 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// add OpReturn if function returns nothing
|
||||
if !c.lastInstructionIs(OpReturnValue) && !c.lastInstructionIs(OpReturn) {
|
||||
c.emit(node, OpReturn)
|
||||
}
|
||||
// code optimization
|
||||
c.optimizeFunc(node)
|
||||
|
||||
freeSymbols := c.symbolTable.FreeSymbols()
|
||||
numLocals := c.symbolTable.MaxSymbols()
|
||||
@ -457,9 +467,9 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
s.LocalAssigned = true
|
||||
}
|
||||
|
||||
c.emit(node, OpGetLocal, s.Index)
|
||||
c.emit(node, OpGetLocalPtr, s.Index)
|
||||
case ScopeFree:
|
||||
c.emit(node, OpGetFree, s.Index)
|
||||
c.emit(node, OpGetFreePtr, s.Index)
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,13 +493,13 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
}
|
||||
|
||||
if node.Result == nil {
|
||||
c.emit(node, OpReturn)
|
||||
c.emit(node, OpReturn, 0)
|
||||
} else {
|
||||
if err := c.Compile(node.Result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.emit(node, OpReturnValue)
|
||||
c.emit(node, OpReturn, 1)
|
||||
}
|
||||
|
||||
case *ast.CallExpr:
|
||||
@ -506,17 +516,57 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
c.emit(node, OpCall, len(node.Args))
|
||||
|
||||
case *ast.ImportExpr:
|
||||
if c.builtinModules[node.ModuleName] {
|
||||
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName}))
|
||||
c.emit(node, OpGetBuiltinModule)
|
||||
} else {
|
||||
userMod, err := c.compileModule(node)
|
||||
if node.ModuleName == "" {
|
||||
return c.errorf(node, "empty module name")
|
||||
}
|
||||
|
||||
if mod := c.modules.Get(node.ModuleName); mod != nil {
|
||||
v, err := mod.Import(node.ModuleName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.emit(node, OpConstant, c.addConstant(userMod))
|
||||
switch v := v.(type) {
|
||||
case []byte: // module written in Tengo
|
||||
compiled, err := c.compileModule(node, node.ModuleName, node.ModuleName, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(node, OpConstant, c.addConstant(compiled))
|
||||
c.emit(node, OpCall, 0)
|
||||
case objects.Object: // builtin module
|
||||
c.emit(node, OpConstant, c.addConstant(v))
|
||||
default:
|
||||
panic(fmt.Errorf("invalid import value type: %T", v))
|
||||
}
|
||||
} else if c.allowFileImport {
|
||||
moduleName := node.ModuleName
|
||||
if !strings.HasSuffix(moduleName, ".tengo") {
|
||||
moduleName += ".tengo"
|
||||
}
|
||||
|
||||
modulePath, err := filepath.Abs(moduleName)
|
||||
if err != nil {
|
||||
return c.errorf(node, "module file path error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := c.checkCyclicImports(node, modulePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
moduleSrc, err := ioutil.ReadFile(moduleName)
|
||||
if err != nil {
|
||||
return c.errorf(node, "module file read error: %s", err.Error())
|
||||
}
|
||||
|
||||
compiled, err := c.compileModule(node, moduleName, modulePath, moduleSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(node, OpConstant, c.addConstant(compiled))
|
||||
c.emit(node, OpCall, 0)
|
||||
} else {
|
||||
return c.errorf(node, "module '%s' not found", node.ModuleName)
|
||||
}
|
||||
|
||||
case *ast.ExportStmt:
|
||||
@ -535,7 +585,7 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
}
|
||||
|
||||
c.emit(node, OpImmutable)
|
||||
c.emit(node, OpReturnValue)
|
||||
c.emit(node, OpReturn, 1)
|
||||
|
||||
case *ast.ErrorExpr:
|
||||
if err := c.Compile(node.Expr); err != nil {
|
||||
@ -594,22 +644,28 @@ func (c *Compiler) Bytecode() *Bytecode {
|
||||
}
|
||||
}
|
||||
|
||||
// SetModuleLoader sets or replaces the current module loader.
|
||||
// Note that the module loader is used for user modules,
|
||||
// not for the standard modules.
|
||||
func (c *Compiler) SetModuleLoader(moduleLoader ModuleLoader) {
|
||||
c.moduleLoader = moduleLoader
|
||||
// EnableFileImport enables or disables module loading from local files.
|
||||
// Local file modules are disabled by default.
|
||||
func (c *Compiler) EnableFileImport(enable bool) {
|
||||
c.allowFileImport = enable
|
||||
}
|
||||
|
||||
func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *SymbolTable) *Compiler {
|
||||
child := NewCompiler(file, symbolTable, nil, c.builtinModules, c.trace)
|
||||
child.moduleName = moduleName // name of the module to compile
|
||||
child.parent = c // parent to set to current compiler
|
||||
child.moduleLoader = c.moduleLoader // share module loader
|
||||
func (c *Compiler) fork(file *source.File, modulePath string, symbolTable *SymbolTable) *Compiler {
|
||||
child := NewCompiler(file, symbolTable, nil, c.modules, c.trace)
|
||||
child.modulePath = modulePath // module file path
|
||||
child.parent = c // parent to set to current compiler
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
func (c *Compiler) error(node ast.Node, err error) error {
|
||||
return &Error{
|
||||
fileSet: c.file.Set(),
|
||||
node: node,
|
||||
error: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
fileSet: c.file.Set(),
|
||||
@ -641,33 +697,6 @@ func (c *Compiler) addInstruction(b []byte) int {
|
||||
return posNewIns
|
||||
}
|
||||
|
||||
func (c *Compiler) setLastInstruction(op Opcode, pos int) {
|
||||
c.scopes[c.scopeIndex].lastInstructions[1] = c.scopes[c.scopeIndex].lastInstructions[0]
|
||||
|
||||
c.scopes[c.scopeIndex].lastInstructions[0].Opcode = op
|
||||
c.scopes[c.scopeIndex].lastInstructions[0].Position = pos
|
||||
}
|
||||
|
||||
func (c *Compiler) lastInstructionIs(op Opcode) bool {
|
||||
if len(c.currentInstructions()) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.scopes[c.scopeIndex].lastInstructions[0].Opcode == op
|
||||
}
|
||||
|
||||
func (c *Compiler) removeLastInstruction() {
|
||||
lastPos := c.scopes[c.scopeIndex].lastInstructions[0].Position
|
||||
|
||||
if c.trace != nil {
|
||||
c.printTrace(fmt.Sprintf("DELET %s",
|
||||
FormatInstructions(c.scopes[c.scopeIndex].instructions[lastPos:], lastPos)[0]))
|
||||
}
|
||||
|
||||
c.scopes[c.scopeIndex].instructions = c.currentInstructions()[:lastPos]
|
||||
c.scopes[c.scopeIndex].lastInstructions[0] = c.scopes[c.scopeIndex].lastInstructions[1]
|
||||
}
|
||||
|
||||
func (c *Compiler) replaceInstruction(pos int, inst []byte) {
|
||||
copy(c.currentInstructions()[pos:], inst)
|
||||
|
||||
@ -684,6 +713,92 @@ func (c *Compiler) changeOperand(opPos int, operand ...int) {
|
||||
c.replaceInstruction(opPos, inst)
|
||||
}
|
||||
|
||||
// optimizeFunc performs some code-level optimization for the current function instructions
|
||||
// it removes unreachable (dead code) instructions and adds "returns" instruction if needed.
|
||||
func (c *Compiler) optimizeFunc(node ast.Node) {
|
||||
// any instructions between RETURN and the function end
|
||||
// or instructions between RETURN and jump target position
|
||||
// are considered as unreachable.
|
||||
|
||||
// pass 1. identify all jump destinations
|
||||
dsts := make(map[int]bool)
|
||||
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
|
||||
switch opcode {
|
||||
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
||||
dsts[operands[0]] = true
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
var newInsts []byte
|
||||
|
||||
// pass 2. eliminate dead code
|
||||
posMap := make(map[int]int) // old position to new position
|
||||
var dstIdx int
|
||||
var deadCode bool
|
||||
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
|
||||
switch {
|
||||
case opcode == OpReturn:
|
||||
if deadCode {
|
||||
return true
|
||||
}
|
||||
deadCode = true
|
||||
case dsts[pos]:
|
||||
dstIdx++
|
||||
deadCode = false
|
||||
case deadCode:
|
||||
return true
|
||||
}
|
||||
|
||||
posMap[pos] = len(newInsts)
|
||||
newInsts = append(newInsts, MakeInstruction(opcode, operands...)...)
|
||||
return true
|
||||
})
|
||||
|
||||
// pass 3. update jump positions
|
||||
var lastOp Opcode
|
||||
var appendReturn bool
|
||||
endPos := len(c.scopes[c.scopeIndex].instructions)
|
||||
iterateInstructions(newInsts, func(pos int, opcode Opcode, operands []int) bool {
|
||||
switch opcode {
|
||||
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
||||
newDst, ok := posMap[operands[0]]
|
||||
if ok {
|
||||
copy(newInsts[pos:], MakeInstruction(opcode, newDst))
|
||||
} else if endPos == operands[0] {
|
||||
// there's a jump instruction that jumps to the end of function
|
||||
// compiler should append "return".
|
||||
appendReturn = true
|
||||
} else {
|
||||
panic(fmt.Errorf("invalid jump position: %d", newDst))
|
||||
}
|
||||
}
|
||||
lastOp = opcode
|
||||
return true
|
||||
})
|
||||
if lastOp != OpReturn {
|
||||
appendReturn = true
|
||||
}
|
||||
|
||||
// pass 4. update source map
|
||||
newSourceMap := make(map[int]source.Pos)
|
||||
for pos, srcPos := range c.scopes[c.scopeIndex].sourceMap {
|
||||
newPos, ok := posMap[pos]
|
||||
if ok {
|
||||
newSourceMap[newPos] = srcPos
|
||||
}
|
||||
}
|
||||
|
||||
c.scopes[c.scopeIndex].instructions = newInsts
|
||||
c.scopes[c.scopeIndex].sourceMap = newSourceMap
|
||||
|
||||
// append "return"
|
||||
if appendReturn {
|
||||
c.emit(node, OpReturn, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int {
|
||||
filePos := source.NoPos
|
||||
if node != nil {
|
||||
@ -693,7 +808,6 @@ func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int {
|
||||
inst := MakeInstruction(opcode, operands...)
|
||||
pos := c.addInstruction(inst)
|
||||
c.scopes[c.scopeIndex].sourceMap[pos] = filePos
|
||||
c.setLastInstruction(opcode, pos)
|
||||
|
||||
if c.trace != nil {
|
||||
c.printTrace(fmt.Sprintf("EMIT %s",
|
||||
|
22
vendor/github.com/d5/tengo/compiler/compiler_assign.go
generated
vendored
22
vendor/github.com/d5/tengo/compiler/compiler_assign.go
generated
vendored
@ -51,27 +51,27 @@ func (c *Compiler) compileAssign(node ast.Node, lhs, rhs []ast.Expr, op token.To
|
||||
|
||||
switch op {
|
||||
case token.AddAssign:
|
||||
c.emit(node, OpAdd)
|
||||
c.emit(node, OpBinaryOp, int(token.Add))
|
||||
case token.SubAssign:
|
||||
c.emit(node, OpSub)
|
||||
c.emit(node, OpBinaryOp, int(token.Sub))
|
||||
case token.MulAssign:
|
||||
c.emit(node, OpMul)
|
||||
c.emit(node, OpBinaryOp, int(token.Mul))
|
||||
case token.QuoAssign:
|
||||
c.emit(node, OpDiv)
|
||||
c.emit(node, OpBinaryOp, int(token.Quo))
|
||||
case token.RemAssign:
|
||||
c.emit(node, OpRem)
|
||||
c.emit(node, OpBinaryOp, int(token.Rem))
|
||||
case token.AndAssign:
|
||||
c.emit(node, OpBAnd)
|
||||
c.emit(node, OpBinaryOp, int(token.And))
|
||||
case token.OrAssign:
|
||||
c.emit(node, OpBOr)
|
||||
c.emit(node, OpBinaryOp, int(token.Or))
|
||||
case token.AndNotAssign:
|
||||
c.emit(node, OpBAndNot)
|
||||
c.emit(node, OpBinaryOp, int(token.AndNot))
|
||||
case token.XorAssign:
|
||||
c.emit(node, OpBXor)
|
||||
c.emit(node, OpBinaryOp, int(token.Xor))
|
||||
case token.ShlAssign:
|
||||
c.emit(node, OpBShiftLeft)
|
||||
c.emit(node, OpBinaryOp, int(token.Shl))
|
||||
case token.ShrAssign:
|
||||
c.emit(node, OpBShiftRight)
|
||||
c.emit(node, OpBinaryOp, int(token.Shr))
|
||||
}
|
||||
|
||||
// compile selector expressions (right to left)
|
||||
|
98
vendor/github.com/d5/tengo/compiler/compiler_module.go
generated
vendored
98
vendor/github.com/d5/tengo/compiler/compiler_module.go
generated
vendored
@ -1,72 +1,31 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/d5/tengo/compiler/ast"
|
||||
"github.com/d5/tengo/compiler/parser"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) {
|
||||
compiledModule, exists := c.loadCompiledModule(expr.ModuleName)
|
||||
if exists {
|
||||
return compiledModule, nil
|
||||
}
|
||||
|
||||
moduleName := expr.ModuleName
|
||||
|
||||
// read module source from loader
|
||||
var moduleSrc []byte
|
||||
if c.moduleLoader == nil {
|
||||
// default loader: read from local file
|
||||
if !strings.HasSuffix(moduleName, ".tengo") {
|
||||
moduleName += ".tengo"
|
||||
}
|
||||
|
||||
if err := c.checkCyclicImports(expr, moduleName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
moduleSrc, err = ioutil.ReadFile(moduleName)
|
||||
if err != nil {
|
||||
return nil, c.errorf(expr, "module file read error: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
if err := c.checkCyclicImports(expr, moduleName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
moduleSrc, err = c.moduleLoader(moduleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
compiledModule, err := c.doCompileModule(moduleName, moduleSrc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.storeCompiledModule(moduleName, compiledModule)
|
||||
|
||||
return compiledModule, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) checkCyclicImports(node ast.Node, moduleName string) error {
|
||||
if c.moduleName == moduleName {
|
||||
return c.errorf(node, "cyclic module import: %s", moduleName)
|
||||
func (c *Compiler) checkCyclicImports(node ast.Node, modulePath string) error {
|
||||
if c.modulePath == modulePath {
|
||||
return c.errorf(node, "cyclic module import: %s", modulePath)
|
||||
} else if c.parent != nil {
|
||||
return c.parent.checkCyclicImports(node, moduleName)
|
||||
return c.parent.checkCyclicImports(node, modulePath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.CompiledFunction, error) {
|
||||
func (c *Compiler) compileModule(node ast.Node, moduleName, modulePath string, src []byte) (*objects.CompiledFunction, error) {
|
||||
if err := c.checkCyclicImports(node, modulePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compiledModule, exists := c.loadCompiledModule(modulePath)
|
||||
if exists {
|
||||
return compiledModule, nil
|
||||
}
|
||||
|
||||
modFile := c.file.Set().AddFile(moduleName, -1, len(src))
|
||||
p := parser.NewParser(modFile, src, nil)
|
||||
file, err := p.ParseFile()
|
||||
@ -77,47 +36,44 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp
|
||||
symbolTable := NewSymbolTable()
|
||||
|
||||
// inherit builtin functions
|
||||
for idx, fn := range objects.Builtins {
|
||||
s, _, ok := c.symbolTable.Resolve(fn.Name)
|
||||
if ok && s.Scope == ScopeBuiltin {
|
||||
symbolTable.DefineBuiltin(idx, fn.Name)
|
||||
}
|
||||
for _, sym := range c.symbolTable.BuiltinSymbols() {
|
||||
symbolTable.DefineBuiltin(sym.Index, sym.Name)
|
||||
}
|
||||
|
||||
// no global scope for the module
|
||||
symbolTable = symbolTable.Fork(false)
|
||||
|
||||
// compile module
|
||||
moduleCompiler := c.fork(modFile, moduleName, symbolTable)
|
||||
moduleCompiler := c.fork(modFile, modulePath, symbolTable)
|
||||
if err := moduleCompiler.Compile(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// add OpReturn (== export undefined) if export is missing
|
||||
if !moduleCompiler.lastInstructionIs(OpReturnValue) {
|
||||
moduleCompiler.emit(nil, OpReturn)
|
||||
}
|
||||
// code optimization
|
||||
moduleCompiler.optimizeFunc(node)
|
||||
|
||||
compiledFunc := moduleCompiler.Bytecode().MainFunction
|
||||
compiledFunc.NumLocals = symbolTable.MaxSymbols()
|
||||
|
||||
c.storeCompiledModule(modulePath, compiledFunc)
|
||||
|
||||
return compiledFunc, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) loadCompiledModule(moduleName string) (mod *objects.CompiledFunction, ok bool) {
|
||||
func (c *Compiler) loadCompiledModule(modulePath string) (mod *objects.CompiledFunction, ok bool) {
|
||||
if c.parent != nil {
|
||||
return c.parent.loadCompiledModule(moduleName)
|
||||
return c.parent.loadCompiledModule(modulePath)
|
||||
}
|
||||
|
||||
mod, ok = c.compiledModules[moduleName]
|
||||
mod, ok = c.compiledModules[modulePath]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Compiler) storeCompiledModule(moduleName string, module *objects.CompiledFunction) {
|
||||
func (c *Compiler) storeCompiledModule(modulePath string, module *objects.CompiledFunction) {
|
||||
if c.parent != nil {
|
||||
c.parent.storeCompiledModule(moduleName, module)
|
||||
c.parent.storeCompiledModule(modulePath, module)
|
||||
}
|
||||
|
||||
c.compiledModules[moduleName] = module
|
||||
c.compiledModules[modulePath] = module
|
||||
}
|
||||
|
13
vendor/github.com/d5/tengo/compiler/instructions.go
generated
vendored
13
vendor/github.com/d5/tengo/compiler/instructions.go
generated
vendored
@ -57,3 +57,16 @@ func FormatInstructions(b []byte, posOffset int) []string {
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func iterateInstructions(b []byte, fn func(pos int, opcode Opcode, operands []int) bool) {
|
||||
for i := 0; i < len(b); i++ {
|
||||
numOperands := OpcodeOperands[Opcode(b[i])]
|
||||
operands, read := ReadOperands(numOperands, b[i+1:])
|
||||
|
||||
if !fn(i, b[i], operands) {
|
||||
break
|
||||
}
|
||||
|
||||
i += read
|
||||
}
|
||||
}
|
||||
|
282
vendor/github.com/d5/tengo/compiler/opcodes.go
generated
vendored
282
vendor/github.com/d5/tengo/compiler/opcodes.go
generated
vendored
@ -5,173 +5,137 @@ type Opcode = byte
|
||||
|
||||
// List of opcodes
|
||||
const (
|
||||
OpConstant Opcode = iota // Load constant
|
||||
OpAdd // Add
|
||||
OpSub // Sub
|
||||
OpMul // Multiply
|
||||
OpDiv // Divide
|
||||
OpRem // Remainder
|
||||
OpBAnd // bitwise AND
|
||||
OpBOr // bitwise OR
|
||||
OpBXor // bitwise XOR
|
||||
OpBShiftLeft // bitwise shift left
|
||||
OpBShiftRight // bitwise shift right
|
||||
OpBAndNot // bitwise AND NOT
|
||||
OpBComplement // bitwise complement
|
||||
OpPop // Pop
|
||||
OpTrue // Push true
|
||||
OpFalse // Push false
|
||||
OpEqual // Equal ==
|
||||
OpNotEqual // Not equal !=
|
||||
OpGreaterThan // Greater than >=
|
||||
OpGreaterThanEqual // Greater than or equal to >=
|
||||
OpMinus // Minus -
|
||||
OpLNot // Logical not !
|
||||
OpJumpFalsy // Jump if falsy
|
||||
OpAndJump // Logical AND jump
|
||||
OpOrJump // Logical OR jump
|
||||
OpJump // Jump
|
||||
OpNull // Push null
|
||||
OpArray // Array object
|
||||
OpMap // Map object
|
||||
OpError // Error object
|
||||
OpImmutable // Immutable object
|
||||
OpIndex // Index operation
|
||||
OpSliceIndex // Slice operation
|
||||
OpCall // Call function
|
||||
OpReturn // Return
|
||||
OpReturnValue // Return value
|
||||
OpGetGlobal // Get global variable
|
||||
OpSetGlobal // Set global variable
|
||||
OpSetSelGlobal // Set global variable using selectors
|
||||
OpGetLocal // Get local variable
|
||||
OpSetLocal // Set local variable
|
||||
OpDefineLocal // Define local variable
|
||||
OpSetSelLocal // Set local variable using selectors
|
||||
OpGetFree // Get free variables
|
||||
OpSetFree // Set free variables
|
||||
OpSetSelFree // Set free variables using selectors
|
||||
OpGetBuiltin // Get builtin function
|
||||
OpGetBuiltinModule // Get builtin module
|
||||
OpClosure // Push closure
|
||||
OpIteratorInit // Iterator init
|
||||
OpIteratorNext // Iterator next
|
||||
OpIteratorKey // Iterator key
|
||||
OpIteratorValue // Iterator value
|
||||
OpConstant Opcode = iota // Load constant
|
||||
OpBComplement // bitwise complement
|
||||
OpPop // Pop
|
||||
OpTrue // Push true
|
||||
OpFalse // Push false
|
||||
OpEqual // Equal ==
|
||||
OpNotEqual // Not equal !=
|
||||
OpMinus // Minus -
|
||||
OpLNot // Logical not !
|
||||
OpJumpFalsy // Jump if falsy
|
||||
OpAndJump // Logical AND jump
|
||||
OpOrJump // Logical OR jump
|
||||
OpJump // Jump
|
||||
OpNull // Push null
|
||||
OpArray // Array object
|
||||
OpMap // Map object
|
||||
OpError // Error object
|
||||
OpImmutable // Immutable object
|
||||
OpIndex // Index operation
|
||||
OpSliceIndex // Slice operation
|
||||
OpCall // Call function
|
||||
OpReturn // Return
|
||||
OpGetGlobal // Get global variable
|
||||
OpSetGlobal // Set global variable
|
||||
OpSetSelGlobal // Set global variable using selectors
|
||||
OpGetLocal // Get local variable
|
||||
OpSetLocal // Set local variable
|
||||
OpDefineLocal // Define local variable
|
||||
OpSetSelLocal // Set local variable using selectors
|
||||
OpGetFreePtr // Get free variable pointer object
|
||||
OpGetFree // Get free variables
|
||||
OpSetFree // Set free variables
|
||||
OpGetLocalPtr // Get local variable as a pointer
|
||||
OpSetSelFree // Set free variables using selectors
|
||||
OpGetBuiltin // Get builtin function
|
||||
OpClosure // Push closure
|
||||
OpIteratorInit // Iterator init
|
||||
OpIteratorNext // Iterator next
|
||||
OpIteratorKey // Iterator key
|
||||
OpIteratorValue // Iterator value
|
||||
OpBinaryOp // Binary Operation
|
||||
)
|
||||
|
||||
// OpcodeNames is opcode names.
|
||||
var OpcodeNames = [...]string{
|
||||
OpConstant: "CONST",
|
||||
OpPop: "POP",
|
||||
OpTrue: "TRUE",
|
||||
OpFalse: "FALSE",
|
||||
OpAdd: "ADD",
|
||||
OpSub: "SUB",
|
||||
OpMul: "MUL",
|
||||
OpDiv: "DIV",
|
||||
OpRem: "REM",
|
||||
OpBAnd: "AND",
|
||||
OpBOr: "OR",
|
||||
OpBXor: "XOR",
|
||||
OpBAndNot: "ANDN",
|
||||
OpBShiftLeft: "SHL",
|
||||
OpBShiftRight: "SHR",
|
||||
OpBComplement: "NEG",
|
||||
OpEqual: "EQL",
|
||||
OpNotEqual: "NEQ",
|
||||
OpGreaterThan: "GTR",
|
||||
OpGreaterThanEqual: "GEQ",
|
||||
OpMinus: "NEG",
|
||||
OpLNot: "NOT",
|
||||
OpJumpFalsy: "JMPF",
|
||||
OpAndJump: "ANDJMP",
|
||||
OpOrJump: "ORJMP",
|
||||
OpJump: "JMP",
|
||||
OpNull: "NULL",
|
||||
OpGetGlobal: "GETG",
|
||||
OpSetGlobal: "SETG",
|
||||
OpSetSelGlobal: "SETSG",
|
||||
OpArray: "ARR",
|
||||
OpMap: "MAP",
|
||||
OpError: "ERROR",
|
||||
OpImmutable: "IMMUT",
|
||||
OpIndex: "INDEX",
|
||||
OpSliceIndex: "SLICE",
|
||||
OpCall: "CALL",
|
||||
OpReturn: "RET",
|
||||
OpReturnValue: "RETVAL",
|
||||
OpGetLocal: "GETL",
|
||||
OpSetLocal: "SETL",
|
||||
OpDefineLocal: "DEFL",
|
||||
OpSetSelLocal: "SETSL",
|
||||
OpGetBuiltin: "BUILTIN",
|
||||
OpGetBuiltinModule: "BLTMOD",
|
||||
OpClosure: "CLOSURE",
|
||||
OpGetFree: "GETF",
|
||||
OpSetFree: "SETF",
|
||||
OpSetSelFree: "SETSF",
|
||||
OpIteratorInit: "ITER",
|
||||
OpIteratorNext: "ITNXT",
|
||||
OpIteratorKey: "ITKEY",
|
||||
OpIteratorValue: "ITVAL",
|
||||
OpConstant: "CONST",
|
||||
OpPop: "POP",
|
||||
OpTrue: "TRUE",
|
||||
OpFalse: "FALSE",
|
||||
OpBComplement: "NEG",
|
||||
OpEqual: "EQL",
|
||||
OpNotEqual: "NEQ",
|
||||
OpMinus: "NEG",
|
||||
OpLNot: "NOT",
|
||||
OpJumpFalsy: "JMPF",
|
||||
OpAndJump: "ANDJMP",
|
||||
OpOrJump: "ORJMP",
|
||||
OpJump: "JMP",
|
||||
OpNull: "NULL",
|
||||
OpGetGlobal: "GETG",
|
||||
OpSetGlobal: "SETG",
|
||||
OpSetSelGlobal: "SETSG",
|
||||
OpArray: "ARR",
|
||||
OpMap: "MAP",
|
||||
OpError: "ERROR",
|
||||
OpImmutable: "IMMUT",
|
||||
OpIndex: "INDEX",
|
||||
OpSliceIndex: "SLICE",
|
||||
OpCall: "CALL",
|
||||
OpReturn: "RET",
|
||||
OpGetLocal: "GETL",
|
||||
OpSetLocal: "SETL",
|
||||
OpDefineLocal: "DEFL",
|
||||
OpSetSelLocal: "SETSL",
|
||||
OpGetBuiltin: "BUILTIN",
|
||||
OpClosure: "CLOSURE",
|
||||
OpGetFreePtr: "GETFP",
|
||||
OpGetFree: "GETF",
|
||||
OpSetFree: "SETF",
|
||||
OpGetLocalPtr: "GETLP",
|
||||
OpSetSelFree: "SETSF",
|
||||
OpIteratorInit: "ITER",
|
||||
OpIteratorNext: "ITNXT",
|
||||
OpIteratorKey: "ITKEY",
|
||||
OpIteratorValue: "ITVAL",
|
||||
OpBinaryOp: "BINARYOP",
|
||||
}
|
||||
|
||||
// OpcodeOperands is the number of operands.
|
||||
var OpcodeOperands = [...][]int{
|
||||
OpConstant: {2},
|
||||
OpPop: {},
|
||||
OpTrue: {},
|
||||
OpFalse: {},
|
||||
OpAdd: {},
|
||||
OpSub: {},
|
||||
OpMul: {},
|
||||
OpDiv: {},
|
||||
OpRem: {},
|
||||
OpBAnd: {},
|
||||
OpBOr: {},
|
||||
OpBXor: {},
|
||||
OpBAndNot: {},
|
||||
OpBShiftLeft: {},
|
||||
OpBShiftRight: {},
|
||||
OpBComplement: {},
|
||||
OpEqual: {},
|
||||
OpNotEqual: {},
|
||||
OpGreaterThan: {},
|
||||
OpGreaterThanEqual: {},
|
||||
OpMinus: {},
|
||||
OpLNot: {},
|
||||
OpJumpFalsy: {2},
|
||||
OpAndJump: {2},
|
||||
OpOrJump: {2},
|
||||
OpJump: {2},
|
||||
OpNull: {},
|
||||
OpGetGlobal: {2},
|
||||
OpSetGlobal: {2},
|
||||
OpSetSelGlobal: {2, 1},
|
||||
OpArray: {2},
|
||||
OpMap: {2},
|
||||
OpError: {},
|
||||
OpImmutable: {},
|
||||
OpIndex: {},
|
||||
OpSliceIndex: {},
|
||||
OpCall: {1},
|
||||
OpReturn: {},
|
||||
OpReturnValue: {},
|
||||
OpGetLocal: {1},
|
||||
OpSetLocal: {1},
|
||||
OpDefineLocal: {1},
|
||||
OpSetSelLocal: {1, 1},
|
||||
OpGetBuiltin: {1},
|
||||
OpGetBuiltinModule: {},
|
||||
OpClosure: {2, 1},
|
||||
OpGetFree: {1},
|
||||
OpSetFree: {1},
|
||||
OpSetSelFree: {1, 1},
|
||||
OpIteratorInit: {},
|
||||
OpIteratorNext: {},
|
||||
OpIteratorKey: {},
|
||||
OpIteratorValue: {},
|
||||
OpConstant: {2},
|
||||
OpPop: {},
|
||||
OpTrue: {},
|
||||
OpFalse: {},
|
||||
OpBComplement: {},
|
||||
OpEqual: {},
|
||||
OpNotEqual: {},
|
||||
OpMinus: {},
|
||||
OpLNot: {},
|
||||
OpJumpFalsy: {2},
|
||||
OpAndJump: {2},
|
||||
OpOrJump: {2},
|
||||
OpJump: {2},
|
||||
OpNull: {},
|
||||
OpGetGlobal: {2},
|
||||
OpSetGlobal: {2},
|
||||
OpSetSelGlobal: {2, 1},
|
||||
OpArray: {2},
|
||||
OpMap: {2},
|
||||
OpError: {},
|
||||
OpImmutable: {},
|
||||
OpIndex: {},
|
||||
OpSliceIndex: {},
|
||||
OpCall: {1},
|
||||
OpReturn: {1},
|
||||
OpGetLocal: {1},
|
||||
OpSetLocal: {1},
|
||||
OpDefineLocal: {1},
|
||||
OpSetSelLocal: {1, 1},
|
||||
OpGetBuiltin: {1},
|
||||
OpClosure: {2, 1},
|
||||
OpGetFreePtr: {1},
|
||||
OpGetFree: {1},
|
||||
OpSetFree: {1},
|
||||
OpGetLocalPtr: {1},
|
||||
OpSetSelFree: {1, 1},
|
||||
OpIteratorInit: {},
|
||||
OpIteratorNext: {},
|
||||
OpIteratorKey: {},
|
||||
OpIteratorValue: {},
|
||||
OpBinaryOp: {1},
|
||||
}
|
||||
|
||||
// ReadOperands reads operands from the bytecode.
|
||||
|
28
vendor/github.com/d5/tengo/compiler/parser/parse_file.go
generated
vendored
28
vendor/github.com/d5/tengo/compiler/parser/parse_file.go
generated
vendored
@ -1,28 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/d5/tengo/compiler/ast"
|
||||
"github.com/d5/tengo/compiler/source"
|
||||
)
|
||||
|
||||
// ParseFile parses a file with a given src.
|
||||
func ParseFile(file *source.File, src []byte, trace io.Writer) (res *ast.File, err error) {
|
||||
p := NewParser(file, src, trace)
|
||||
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if _, ok := e.(bailout); !ok {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
p.errors.Sort()
|
||||
err = p.errors.Err()
|
||||
}()
|
||||
|
||||
res, err = p.ParseFile()
|
||||
|
||||
return
|
||||
}
|
3
vendor/github.com/d5/tengo/compiler/parser/parse_source.go
generated
vendored
3
vendor/github.com/d5/tengo/compiler/parser/parse_source.go
generated
vendored
@ -12,5 +12,6 @@ func ParseSource(filename string, src []byte, trace io.Writer) (res *ast.File, e
|
||||
fileSet := source.NewFileSet()
|
||||
file := fileSet.AddFile(filename, -1, len(src))
|
||||
|
||||
return ParseFile(file, src, trace)
|
||||
p := NewParser(file, src, trace)
|
||||
return p.ParseFile()
|
||||
}
|
||||
|
39
vendor/github.com/d5/tengo/compiler/parser/parser.go
generated
vendored
39
vendor/github.com/d5/tengo/compiler/parser/parser.go
generated
vendored
@ -57,7 +57,18 @@ func NewParser(file *source.File, src []byte, trace io.Writer) *Parser {
|
||||
}
|
||||
|
||||
// ParseFile parses the source and returns an AST file unit.
|
||||
func (p *Parser) ParseFile() (*ast.File, error) {
|
||||
func (p *Parser) ParseFile() (file *ast.File, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if _, ok := e.(bailout); !ok {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
p.errors.Sort()
|
||||
err = p.errors.Err()
|
||||
}()
|
||||
|
||||
if p.trace {
|
||||
defer un(trace(p, "File"))
|
||||
}
|
||||
@ -71,10 +82,12 @@ func (p *Parser) ParseFile() (*ast.File, error) {
|
||||
return nil, p.errors.Err()
|
||||
}
|
||||
|
||||
return &ast.File{
|
||||
file = &ast.File{
|
||||
InputFile: p.file,
|
||||
Stmts: stmts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpr() ast.Expr {
|
||||
@ -1002,16 +1015,26 @@ func (p *Parser) parseMapElementLit() *ast.MapElementLit {
|
||||
defer un(trace(p, "MapElementLit"))
|
||||
}
|
||||
|
||||
// key: read identifier token but it's not actually an identifier
|
||||
ident := p.parseIdent()
|
||||
pos := p.pos
|
||||
name := "_"
|
||||
|
||||
if p.token == token.Ident {
|
||||
name = p.tokenLit
|
||||
} else if p.token == token.String {
|
||||
v, _ := strconv.Unquote(p.tokenLit)
|
||||
name = v
|
||||
} else {
|
||||
p.errorExpected(pos, "map key")
|
||||
}
|
||||
|
||||
p.next()
|
||||
|
||||
colonPos := p.expect(token.Colon)
|
||||
|
||||
valueExpr := p.parseExpr()
|
||||
|
||||
return &ast.MapElementLit{
|
||||
Key: ident.Name,
|
||||
KeyPos: ident.NamePos,
|
||||
Key: name,
|
||||
KeyPos: pos,
|
||||
ColonPos: colonPos,
|
||||
Value: valueExpr,
|
||||
}
|
||||
|
32
vendor/github.com/d5/tengo/compiler/symbol_table.go
generated
vendored
32
vendor/github.com/d5/tengo/compiler/symbol_table.go
generated
vendored
@ -2,12 +2,13 @@ package compiler
|
||||
|
||||
// SymbolTable represents a symbol table.
|
||||
type SymbolTable struct {
|
||||
parent *SymbolTable
|
||||
block bool
|
||||
store map[string]*Symbol
|
||||
numDefinition int
|
||||
maxDefinition int
|
||||
freeSymbols []*Symbol
|
||||
parent *SymbolTable
|
||||
block bool
|
||||
store map[string]*Symbol
|
||||
numDefinition int
|
||||
maxDefinition int
|
||||
freeSymbols []*Symbol
|
||||
builtinSymbols []*Symbol
|
||||
}
|
||||
|
||||
// NewSymbolTable creates a SymbolTable.
|
||||
@ -37,6 +38,10 @@ func (t *SymbolTable) Define(name string) *Symbol {
|
||||
|
||||
// DefineBuiltin adds a symbol for builtin function.
|
||||
func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol {
|
||||
if t.parent != nil {
|
||||
return t.parent.DefineBuiltin(index, name)
|
||||
}
|
||||
|
||||
symbol := &Symbol{
|
||||
Name: name,
|
||||
Index: index,
|
||||
@ -45,6 +50,8 @@ func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol {
|
||||
|
||||
t.store[name] = symbol
|
||||
|
||||
t.builtinSymbols = append(t.builtinSymbols, symbol)
|
||||
|
||||
return symbol
|
||||
}
|
||||
|
||||
@ -57,9 +64,7 @@ func (t *SymbolTable) Resolve(name string) (symbol *Symbol, depth int, ok bool)
|
||||
return
|
||||
}
|
||||
|
||||
if !t.block {
|
||||
depth++
|
||||
}
|
||||
depth++
|
||||
|
||||
// if symbol is defined in parent table and if it's not global/builtin
|
||||
// then it's free variable.
|
||||
@ -101,6 +106,15 @@ func (t *SymbolTable) FreeSymbols() []*Symbol {
|
||||
return t.freeSymbols
|
||||
}
|
||||
|
||||
// BuiltinSymbols returns builtin symbols for the scope.
|
||||
func (t *SymbolTable) BuiltinSymbols() []*Symbol {
|
||||
if t.parent != nil {
|
||||
return t.parent.BuiltinSymbols()
|
||||
}
|
||||
|
||||
return t.builtinSymbols
|
||||
}
|
||||
|
||||
// Names returns the name of all the symbols.
|
||||
func (t *SymbolTable) Names() []string {
|
||||
var names []string
|
||||
|
37
vendor/github.com/d5/tengo/objects/break.go
generated
vendored
37
vendor/github.com/d5/tengo/objects/break.go
generated
vendored
@ -1,37 +0,0 @@
|
||||
package objects
|
||||
|
||||
import "github.com/d5/tengo/compiler/token"
|
||||
|
||||
// Break represents a break statement.
|
||||
type Break struct{}
|
||||
|
||||
// TypeName returns the name of the type.
|
||||
func (o *Break) TypeName() string {
|
||||
return "break"
|
||||
}
|
||||
|
||||
func (o *Break) String() string {
|
||||
return "<break>"
|
||||
}
|
||||
|
||||
// BinaryOp returns another object that is the result of
|
||||
// a given binary operator and a right-hand side object.
|
||||
func (o *Break) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
||||
return nil, ErrInvalidOperator
|
||||
}
|
||||
|
||||
// Copy returns a copy of the type.
|
||||
func (o *Break) Copy() Object {
|
||||
return &Break{}
|
||||
}
|
||||
|
||||
// IsFalsy returns true if the value of the type is falsy.
|
||||
func (o *Break) IsFalsy() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Equals returns true if the value of the type
|
||||
// is equal to the value of another object.
|
||||
func (o *Break) Equals(x Object) bool {
|
||||
return false
|
||||
}
|
14
vendor/github.com/d5/tengo/objects/builtin_convert.go
generated
vendored
14
vendor/github.com/d5/tengo/objects/builtin_convert.go
generated
vendored
@ -1,5 +1,7 @@
|
||||
package objects
|
||||
|
||||
import "github.com/d5/tengo"
|
||||
|
||||
func builtinString(args ...Object) (Object, error) {
|
||||
argsLen := len(args)
|
||||
if !(argsLen == 1 || argsLen == 2) {
|
||||
@ -12,6 +14,10 @@ func builtinString(args ...Object) (Object, error) {
|
||||
|
||||
v, ok := ToString(args[0])
|
||||
if ok {
|
||||
if len(v) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
|
||||
return &String{Value: v}, nil
|
||||
}
|
||||
|
||||
@ -117,11 +123,19 @@ func builtinBytes(args ...Object) (Object, error) {
|
||||
|
||||
// bytes(N) => create a new bytes with given size N
|
||||
if n, ok := args[0].(*Int); ok {
|
||||
if n.Value > int64(tengo.MaxBytesLen) {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
|
||||
return &Bytes{Value: make([]byte, int(n.Value))}, nil
|
||||
}
|
||||
|
||||
v, ok := ToByteSlice(args[0])
|
||||
if ok {
|
||||
if len(v) > tengo.MaxBytesLen {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
|
||||
return &Bytes{Value: v}, nil
|
||||
}
|
||||
|
||||
|
54
vendor/github.com/d5/tengo/objects/builtin_json.go
generated
vendored
54
vendor/github.com/d5/tengo/objects/builtin_json.go
generated
vendored
@ -1,54 +0,0 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// to_json(v object) => bytes
|
||||
func builtinToJSON(args ...Object) (Object, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrWrongNumArguments
|
||||
}
|
||||
|
||||
res, err := json.Marshal(objectToInterface(args[0]))
|
||||
if err != nil {
|
||||
return &Error{Value: &String{Value: err.Error()}}, nil
|
||||
}
|
||||
|
||||
return &Bytes{Value: res}, nil
|
||||
}
|
||||
|
||||
// from_json(data string/bytes) => object
|
||||
func builtinFromJSON(args ...Object) (Object, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrWrongNumArguments
|
||||
}
|
||||
|
||||
var target interface{}
|
||||
|
||||
switch o := args[0].(type) {
|
||||
case *Bytes:
|
||||
err := json.Unmarshal(o.Value, &target)
|
||||
if err != nil {
|
||||
return &Error{Value: &String{Value: err.Error()}}, nil
|
||||
}
|
||||
case *String:
|
||||
err := json.Unmarshal([]byte(o.Value), &target)
|
||||
if err != nil {
|
||||
return &Error{Value: &String{Value: err.Error()}}, nil
|
||||
}
|
||||
default:
|
||||
return nil, ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "bytes/string",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
res, err := FromInterface(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
23
vendor/github.com/d5/tengo/objects/builtin_module.go
generated
vendored
Normal file
23
vendor/github.com/d5/tengo/objects/builtin_module.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package objects
|
||||
|
||||
// BuiltinModule is an importable module that's written in Go.
|
||||
type BuiltinModule struct {
|
||||
Attrs map[string]Object
|
||||
}
|
||||
|
||||
// Import returns an immutable map for the module.
|
||||
func (m *BuiltinModule) Import(moduleName string) (interface{}, error) {
|
||||
return m.AsImmutableMap(moduleName), nil
|
||||
}
|
||||
|
||||
// AsImmutableMap converts builtin module into an immutable map.
|
||||
func (m *BuiltinModule) AsImmutableMap(moduleName string) *ImmutableMap {
|
||||
attrs := make(map[string]Object, len(m.Attrs))
|
||||
for k, v := range m.Attrs {
|
||||
attrs[k] = v.Copy()
|
||||
}
|
||||
|
||||
attrs["__module_name__"] = &String{Value: moduleName}
|
||||
|
||||
return &ImmutableMap{Value: attrs}
|
||||
}
|
75
vendor/github.com/d5/tengo/objects/builtin_print.go
generated
vendored
75
vendor/github.com/d5/tengo/objects/builtin_print.go
generated
vendored
@ -1,75 +0,0 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// print(args...)
|
||||
func builtinPrint(args ...Object) (Object, error) {
|
||||
for _, arg := range args {
|
||||
if str, ok := arg.(*String); ok {
|
||||
fmt.Println(str.Value)
|
||||
} else {
|
||||
fmt.Println(arg.String())
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// printf("format", args...)
|
||||
func builtinPrintf(args ...Object) (Object, error) {
|
||||
numArgs := len(args)
|
||||
if numArgs == 0 {
|
||||
return nil, ErrWrongNumArguments
|
||||
}
|
||||
|
||||
format, ok := args[0].(*String)
|
||||
if !ok {
|
||||
return nil, ErrInvalidArgumentType{
|
||||
Name: "format",
|
||||
Expected: "string",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
if numArgs == 1 {
|
||||
fmt.Print(format)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
formatArgs := make([]interface{}, numArgs-1, numArgs-1)
|
||||
for idx, arg := range args[1:] {
|
||||
formatArgs[idx] = objectToInterface(arg)
|
||||
}
|
||||
|
||||
fmt.Printf(format.Value, formatArgs...)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// sprintf("format", args...)
|
||||
func builtinSprintf(args ...Object) (Object, error) {
|
||||
numArgs := len(args)
|
||||
if numArgs == 0 {
|
||||
return nil, ErrWrongNumArguments
|
||||
}
|
||||
|
||||
format, ok := args[0].(*String)
|
||||
if !ok {
|
||||
return nil, ErrInvalidArgumentType{
|
||||
Name: "format",
|
||||
Expected: "string",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
if numArgs == 1 {
|
||||
return format, nil // okay to return 'format' directly as String is immutable
|
||||
}
|
||||
|
||||
formatArgs := make([]interface{}, numArgs-1, numArgs-1)
|
||||
for idx, arg := range args[1:] {
|
||||
formatArgs[idx] = objectToInterface(arg)
|
||||
}
|
||||
|
||||
return &String{Value: fmt.Sprintf(format.Value, formatArgs...)}, nil
|
||||
}
|
12
vendor/github.com/d5/tengo/objects/builtin_type_checks.go
generated
vendored
12
vendor/github.com/d5/tengo/objects/builtin_type_checks.go
generated
vendored
@ -181,3 +181,15 @@ func builtinIsCallable(args ...Object) (Object, error) {
|
||||
|
||||
return FalseValue, nil
|
||||
}
|
||||
|
||||
func builtinIsIterable(args ...Object) (Object, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrWrongNumArguments
|
||||
}
|
||||
|
||||
if _, ok := args[0].(Iterable); ok {
|
||||
return TrueValue, nil
|
||||
}
|
||||
|
||||
return FalseValue, nil
|
||||
}
|
||||
|
133
vendor/github.com/d5/tengo/objects/builtins.go
generated
vendored
133
vendor/github.com/d5/tengo/objects/builtins.go
generated
vendored
@ -1,135 +1,114 @@
|
||||
package objects
|
||||
|
||||
// NamedBuiltinFunc is a named builtin function.
|
||||
type NamedBuiltinFunc struct {
|
||||
Name string
|
||||
Func CallableFunc
|
||||
}
|
||||
|
||||
// Builtins contains all default builtin functions.
|
||||
var Builtins = []NamedBuiltinFunc{
|
||||
// Use GetBuiltinFunctions instead of accessing Builtins directly.
|
||||
var Builtins = []*BuiltinFunction{
|
||||
{
|
||||
Name: "print",
|
||||
Func: builtinPrint,
|
||||
Name: "len",
|
||||
Value: builtinLen,
|
||||
},
|
||||
{
|
||||
Name: "printf",
|
||||
Func: builtinPrintf,
|
||||
Name: "copy",
|
||||
Value: builtinCopy,
|
||||
},
|
||||
{
|
||||
Name: "sprintf",
|
||||
Func: builtinSprintf,
|
||||
Name: "append",
|
||||
Value: builtinAppend,
|
||||
},
|
||||
{
|
||||
Name: "len",
|
||||
Func: builtinLen,
|
||||
Name: "string",
|
||||
Value: builtinString,
|
||||
},
|
||||
{
|
||||
Name: "copy",
|
||||
Func: builtinCopy,
|
||||
Name: "int",
|
||||
Value: builtinInt,
|
||||
},
|
||||
{
|
||||
Name: "append",
|
||||
Func: builtinAppend,
|
||||
Name: "bool",
|
||||
Value: builtinBool,
|
||||
},
|
||||
{
|
||||
Name: "string",
|
||||
Func: builtinString,
|
||||
Name: "float",
|
||||
Value: builtinFloat,
|
||||
},
|
||||
{
|
||||
Name: "int",
|
||||
Func: builtinInt,
|
||||
Name: "char",
|
||||
Value: builtinChar,
|
||||
},
|
||||
{
|
||||
Name: "bool",
|
||||
Func: builtinBool,
|
||||
Name: "bytes",
|
||||
Value: builtinBytes,
|
||||
},
|
||||
{
|
||||
Name: "float",
|
||||
Func: builtinFloat,
|
||||
Name: "time",
|
||||
Value: builtinTime,
|
||||
},
|
||||
{
|
||||
Name: "char",
|
||||
Func: builtinChar,
|
||||
Name: "is_int",
|
||||
Value: builtinIsInt,
|
||||
},
|
||||
{
|
||||
Name: "bytes",
|
||||
Func: builtinBytes,
|
||||
Name: "is_float",
|
||||
Value: builtinIsFloat,
|
||||
},
|
||||
{
|
||||
Name: "time",
|
||||
Func: builtinTime,
|
||||
Name: "is_string",
|
||||
Value: builtinIsString,
|
||||
},
|
||||
{
|
||||
Name: "is_int",
|
||||
Func: builtinIsInt,
|
||||
Name: "is_bool",
|
||||
Value: builtinIsBool,
|
||||
},
|
||||
{
|
||||
Name: "is_float",
|
||||
Func: builtinIsFloat,
|
||||
Name: "is_char",
|
||||
Value: builtinIsChar,
|
||||
},
|
||||
{
|
||||
Name: "is_string",
|
||||
Func: builtinIsString,
|
||||
Name: "is_bytes",
|
||||
Value: builtinIsBytes,
|
||||
},
|
||||
{
|
||||
Name: "is_bool",
|
||||
Func: builtinIsBool,
|
||||
Name: "is_array",
|
||||
Value: builtinIsArray,
|
||||
},
|
||||
{
|
||||
Name: "is_char",
|
||||
Func: builtinIsChar,
|
||||
Name: "is_immutable_array",
|
||||
Value: builtinIsImmutableArray,
|
||||
},
|
||||
{
|
||||
Name: "is_bytes",
|
||||
Func: builtinIsBytes,
|
||||
Name: "is_map",
|
||||
Value: builtinIsMap,
|
||||
},
|
||||
{
|
||||
Name: "is_array",
|
||||
Func: builtinIsArray,
|
||||
Name: "is_immutable_map",
|
||||
Value: builtinIsImmutableMap,
|
||||
},
|
||||
{
|
||||
Name: "is_immutable_array",
|
||||
Func: builtinIsImmutableArray,
|
||||
Name: "is_iterable",
|
||||
Value: builtinIsIterable,
|
||||
},
|
||||
{
|
||||
Name: "is_map",
|
||||
Func: builtinIsMap,
|
||||
Name: "is_time",
|
||||
Value: builtinIsTime,
|
||||
},
|
||||
{
|
||||
Name: "is_immutable_map",
|
||||
Func: builtinIsImmutableMap,
|
||||
Name: "is_error",
|
||||
Value: builtinIsError,
|
||||
},
|
||||
{
|
||||
Name: "is_time",
|
||||
Func: builtinIsTime,
|
||||
Name: "is_undefined",
|
||||
Value: builtinIsUndefined,
|
||||
},
|
||||
{
|
||||
Name: "is_error",
|
||||
Func: builtinIsError,
|
||||
Name: "is_function",
|
||||
Value: builtinIsFunction,
|
||||
},
|
||||
{
|
||||
Name: "is_undefined",
|
||||
Func: builtinIsUndefined,
|
||||
Name: "is_callable",
|
||||
Value: builtinIsCallable,
|
||||
},
|
||||
{
|
||||
Name: "is_function",
|
||||
Func: builtinIsFunction,
|
||||
},
|
||||
{
|
||||
Name: "is_callable",
|
||||
Func: builtinIsCallable,
|
||||
},
|
||||
{
|
||||
Name: "to_json",
|
||||
Func: builtinToJSON,
|
||||
},
|
||||
{
|
||||
Name: "from_json",
|
||||
Func: builtinFromJSON,
|
||||
},
|
||||
{
|
||||
Name: "type_name",
|
||||
Func: builtinTypeName,
|
||||
Name: "type_name",
|
||||
Value: builtinTypeName,
|
||||
},
|
||||
}
|
||||
|
13
vendor/github.com/d5/tengo/objects/bytes.go
generated
vendored
13
vendor/github.com/d5/tengo/objects/bytes.go
generated
vendored
@ -3,6 +3,7 @@ package objects
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/compiler/token"
|
||||
)
|
||||
|
||||
@ -27,6 +28,10 @@ func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
||||
case token.Add:
|
||||
switch rhs := rhs.(type) {
|
||||
case *Bytes:
|
||||
if len(o.Value)+len(rhs.Value) > tengo.MaxBytesLen {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
|
||||
return &Bytes{Value: append(o.Value, rhs.Value...)}, nil
|
||||
}
|
||||
}
|
||||
@ -74,3 +79,11 @@ func (o *Bytes) IndexGet(index Object) (res Object, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Iterate creates a bytes iterator.
|
||||
func (o *Bytes) Iterate() Iterator {
|
||||
return &BytesIterator{
|
||||
v: o.Value,
|
||||
l: len(o.Value),
|
||||
}
|
||||
}
|
||||
|
57
vendor/github.com/d5/tengo/objects/bytes_iterator.go
generated
vendored
Normal file
57
vendor/github.com/d5/tengo/objects/bytes_iterator.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package objects
|
||||
|
||||
import "github.com/d5/tengo/compiler/token"
|
||||
|
||||
// BytesIterator represents an iterator for a string.
|
||||
type BytesIterator struct {
|
||||
v []byte
|
||||
i int
|
||||
l int
|
||||
}
|
||||
|
||||
// TypeName returns the name of the type.
|
||||
func (i *BytesIterator) TypeName() string {
|
||||
return "bytes-iterator"
|
||||
}
|
||||
|
||||
func (i *BytesIterator) String() string {
|
||||
return "<bytes-iterator>"
|
||||
}
|
||||
|
||||
// BinaryOp returns another object that is the result of
|
||||
// a given binary operator and a right-hand side object.
|
||||
func (i *BytesIterator) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
||||
return nil, ErrInvalidOperator
|
||||
}
|
||||
|
||||
// IsFalsy returns true if the value of the type is falsy.
|
||||
func (i *BytesIterator) IsFalsy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Equals returns true if the value of the type
|
||||
// is equal to the value of another object.
|
||||
func (i *BytesIterator) Equals(Object) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Copy returns a copy of the type.
|
||||
func (i *BytesIterator) Copy() Object {
|
||||
return &BytesIterator{v: i.v, i: i.i, l: i.l}
|
||||
}
|
||||
|
||||
// Next returns true if there are more elements to iterate.
|
||||
func (i *BytesIterator) Next() bool {
|
||||
i.i++
|
||||
return i.i <= i.l
|
||||
}
|
||||
|
||||
// Key returns the key or index value of the current element.
|
||||
func (i *BytesIterator) Key() Object {
|
||||
return &Int{Value: int64(i.i - 1)}
|
||||
}
|
||||
|
||||
// Value returns the value of the current element.
|
||||
func (i *BytesIterator) Value() Object {
|
||||
return &Int{Value: int64(i.v[i.i-1])}
|
||||
}
|
2
vendor/github.com/d5/tengo/objects/callable_func.go
generated
vendored
2
vendor/github.com/d5/tengo/objects/callable_func.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
package objects
|
||||
|
||||
// CallableFunc is a function signature for the callable functions.
|
||||
type CallableFunc func(args ...Object) (ret Object, err error)
|
||||
type CallableFunc = func(args ...Object) (ret Object, err error)
|
||||
|
4
vendor/github.com/d5/tengo/objects/closure.go
generated
vendored
4
vendor/github.com/d5/tengo/objects/closure.go
generated
vendored
@ -7,7 +7,7 @@ import (
|
||||
// Closure represents a function closure.
|
||||
type Closure struct {
|
||||
Fn *CompiledFunction
|
||||
Free []*Object
|
||||
Free []*ObjectPtr
|
||||
}
|
||||
|
||||
// TypeName returns the name of the type.
|
||||
@ -29,7 +29,7 @@ func (o *Closure) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
||||
func (o *Closure) Copy() Object {
|
||||
return &Closure{
|
||||
Fn: o.Fn.Copy().(*CompiledFunction),
|
||||
Free: append([]*Object{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers
|
||||
Free: append([]*ObjectPtr{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers
|
||||
}
|
||||
}
|
||||
|
||||
|
11
vendor/github.com/d5/tengo/objects/compiled_function.go
generated
vendored
11
vendor/github.com/d5/tengo/objects/compiled_function.go
generated
vendored
@ -47,3 +47,14 @@ func (o *CompiledFunction) IsFalsy() bool {
|
||||
func (o *CompiledFunction) Equals(x Object) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SourcePos returns the source position of the instruction at ip.
|
||||
func (o *CompiledFunction) SourcePos(ip int) source.Pos {
|
||||
for ip >= 0 {
|
||||
if p, ok := o.SourceMap[ip]; ok {
|
||||
return p
|
||||
}
|
||||
ip--
|
||||
}
|
||||
return source.NoPos
|
||||
}
|
||||
|
38
vendor/github.com/d5/tengo/objects/continue.go
generated
vendored
38
vendor/github.com/d5/tengo/objects/continue.go
generated
vendored
@ -1,38 +0,0 @@
|
||||
package objects
|
||||
|
||||
import "github.com/d5/tengo/compiler/token"
|
||||
|
||||
// Continue represents a continue statement.
|
||||
type Continue struct {
|
||||
}
|
||||
|
||||
// TypeName returns the name of the type.
|
||||
func (o *Continue) TypeName() string {
|
||||
return "continue"
|
||||
}
|
||||
|
||||
func (o *Continue) String() string {
|
||||
return "<continue>"
|
||||
}
|
||||
|
||||
// BinaryOp returns another object that is the result of
|
||||
// a given binary operator and a right-hand side object.
|
||||
func (o *Continue) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
||||
return nil, ErrInvalidOperator
|
||||
}
|
||||
|
||||
// Copy returns a copy of the type.
|
||||
func (o *Continue) Copy() Object {
|
||||
return &Continue{}
|
||||
}
|
||||
|
||||
// IsFalsy returns true if the value of the type is falsy.
|
||||
func (o *Continue) IsFalsy() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Equals returns true if the value of the type
|
||||
// is equal to the value of another object.
|
||||
func (o *Continue) Equals(x Object) bool {
|
||||
return false
|
||||
}
|
35
vendor/github.com/d5/tengo/objects/conversion.go
generated
vendored
35
vendor/github.com/d5/tengo/objects/conversion.go
generated
vendored
@ -1,9 +1,12 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
)
|
||||
|
||||
// ToString will try to convert object o to string value.
|
||||
@ -156,8 +159,8 @@ func ToTime(o Object) (v time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// objectToInterface attempts to convert an object o to an interface{} value
|
||||
func objectToInterface(o Object) (res interface{}) {
|
||||
// ToInterface attempts to convert an object o to an interface{} value
|
||||
func ToInterface(o Object) (res interface{}) {
|
||||
switch o := o.(type) {
|
||||
case *Int:
|
||||
res = o.Value
|
||||
@ -174,13 +177,29 @@ func objectToInterface(o Object) (res interface{}) {
|
||||
case *Array:
|
||||
res = make([]interface{}, len(o.Value))
|
||||
for i, val := range o.Value {
|
||||
res.([]interface{})[i] = objectToInterface(val)
|
||||
res.([]interface{})[i] = ToInterface(val)
|
||||
}
|
||||
case *ImmutableArray:
|
||||
res = make([]interface{}, len(o.Value))
|
||||
for i, val := range o.Value {
|
||||
res.([]interface{})[i] = ToInterface(val)
|
||||
}
|
||||
case *Map:
|
||||
res = make(map[string]interface{})
|
||||
for key, v := range o.Value {
|
||||
res.(map[string]interface{})[key] = objectToInterface(v)
|
||||
res.(map[string]interface{})[key] = ToInterface(v)
|
||||
}
|
||||
case *ImmutableMap:
|
||||
res = make(map[string]interface{})
|
||||
for key, v := range o.Value {
|
||||
res.(map[string]interface{})[key] = ToInterface(v)
|
||||
}
|
||||
case *Time:
|
||||
res = o.Value
|
||||
case *Error:
|
||||
res = errors.New(o.String())
|
||||
case *Undefined:
|
||||
res = nil
|
||||
case Object:
|
||||
return o
|
||||
}
|
||||
@ -194,6 +213,9 @@ func FromInterface(v interface{}) (Object, error) {
|
||||
case nil:
|
||||
return UndefinedValue, nil
|
||||
case string:
|
||||
if len(v) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
return &String{Value: v}, nil
|
||||
case int64:
|
||||
return &Int{Value: v}, nil
|
||||
@ -211,6 +233,9 @@ func FromInterface(v interface{}) (Object, error) {
|
||||
case float64:
|
||||
return &Float{Value: v}, nil
|
||||
case []byte:
|
||||
if len(v) > tengo.MaxBytesLen {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
return &Bytes{Value: v}, nil
|
||||
case error:
|
||||
return &Error{Value: &String{Value: v.Error()}}, nil
|
||||
@ -243,6 +268,8 @@ func FromInterface(v interface{}) (Object, error) {
|
||||
return &Time{Value: v}, nil
|
||||
case Object:
|
||||
return v, nil
|
||||
case CallableFunc:
|
||||
return &UserFunction{Value: v}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot convert to object: %T", v)
|
||||
|
31
vendor/github.com/d5/tengo/objects/count_objects.go
generated
vendored
Normal file
31
vendor/github.com/d5/tengo/objects/count_objects.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package objects
|
||||
|
||||
// CountObjects returns the number of objects that a given object o contains.
|
||||
// For scalar value types, it will always be 1. For compound value types,
|
||||
// this will include its elements and all of their elements recursively.
|
||||
func CountObjects(o Object) (c int) {
|
||||
c = 1
|
||||
|
||||
switch o := o.(type) {
|
||||
case *Array:
|
||||
for _, v := range o.Value {
|
||||
c += CountObjects(v)
|
||||
}
|
||||
case *ImmutableArray:
|
||||
for _, v := range o.Value {
|
||||
c += CountObjects(v)
|
||||
}
|
||||
case *Map:
|
||||
for _, v := range o.Value {
|
||||
c += CountObjects(v)
|
||||
}
|
||||
case *ImmutableMap:
|
||||
for _, v := range o.Value {
|
||||
c += CountObjects(v)
|
||||
}
|
||||
case *Error:
|
||||
c += CountObjects(o.Value)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
6
vendor/github.com/d5/tengo/objects/errors.go
generated
vendored
6
vendor/github.com/d5/tengo/objects/errors.go
generated
vendored
@ -20,6 +20,12 @@ var ErrInvalidOperator = errors.New("invalid operator")
|
||||
// ErrWrongNumArguments represents a wrong number of arguments error.
|
||||
var ErrWrongNumArguments = errors.New("wrong number of arguments")
|
||||
|
||||
// ErrBytesLimit represents an error where the size of bytes value exceeds the limit.
|
||||
var ErrBytesLimit = errors.New("exceeding bytes size limit")
|
||||
|
||||
// ErrStringLimit represents an error where the size of string value exceeds the limit.
|
||||
var ErrStringLimit = errors.New("exceeding string size limit")
|
||||
|
||||
// ErrInvalidArgumentType represents an invalid argument value type error.
|
||||
type ErrInvalidArgumentType struct {
|
||||
Name string
|
||||
|
7
vendor/github.com/d5/tengo/objects/importable.go
generated
vendored
Normal file
7
vendor/github.com/d5/tengo/objects/importable.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package objects
|
||||
|
||||
// Importable interface represents importable module instance.
|
||||
type Importable interface {
|
||||
// Import should return either an Object or module source code ([]byte).
|
||||
Import(moduleName string) (interface{}, error)
|
||||
}
|
77
vendor/github.com/d5/tengo/objects/module_map.go
generated
vendored
Normal file
77
vendor/github.com/d5/tengo/objects/module_map.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
package objects
|
||||
|
||||
// ModuleMap represents a set of named modules.
|
||||
// Use NewModuleMap to create a new module map.
|
||||
type ModuleMap struct {
|
||||
m map[string]Importable
|
||||
}
|
||||
|
||||
// NewModuleMap creates a new module map.
|
||||
func NewModuleMap() *ModuleMap {
|
||||
return &ModuleMap{
|
||||
m: make(map[string]Importable),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds an import module.
|
||||
func (m *ModuleMap) Add(name string, module Importable) {
|
||||
m.m[name] = module
|
||||
}
|
||||
|
||||
// AddBuiltinModule adds a builtin module.
|
||||
func (m *ModuleMap) AddBuiltinModule(name string, attrs map[string]Object) {
|
||||
m.m[name] = &BuiltinModule{Attrs: attrs}
|
||||
}
|
||||
|
||||
// AddSourceModule adds a source module.
|
||||
func (m *ModuleMap) AddSourceModule(name string, src []byte) {
|
||||
m.m[name] = &SourceModule{Src: src}
|
||||
}
|
||||
|
||||
// Remove removes a named module.
|
||||
func (m *ModuleMap) Remove(name string) {
|
||||
delete(m.m, name)
|
||||
}
|
||||
|
||||
// Get returns an import module identified by name.
|
||||
// It returns if the name is not found.
|
||||
func (m *ModuleMap) Get(name string) Importable {
|
||||
return m.m[name]
|
||||
}
|
||||
|
||||
// GetBuiltinModule returns a builtin module identified by name.
|
||||
// It returns if the name is not found or the module is not a builtin module.
|
||||
func (m *ModuleMap) GetBuiltinModule(name string) *BuiltinModule {
|
||||
mod, _ := m.m[name].(*BuiltinModule)
|
||||
return mod
|
||||
}
|
||||
|
||||
// GetSourceModule returns a source module identified by name.
|
||||
// It returns if the name is not found or the module is not a source module.
|
||||
func (m *ModuleMap) GetSourceModule(name string) *SourceModule {
|
||||
mod, _ := m.m[name].(*SourceModule)
|
||||
return mod
|
||||
}
|
||||
|
||||
// Copy creates a copy of the module map.
|
||||
func (m *ModuleMap) Copy() *ModuleMap {
|
||||
c := &ModuleMap{
|
||||
m: make(map[string]Importable),
|
||||
}
|
||||
for name, mod := range m.m {
|
||||
c.m[name] = mod
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Len returns the number of named modules.
|
||||
func (m *ModuleMap) Len() int {
|
||||
return len(m.m)
|
||||
}
|
||||
|
||||
// AddMap adds named modules from another module map.
|
||||
func (m *ModuleMap) AddMap(o *ModuleMap) {
|
||||
for name, mod := range o.m {
|
||||
m.m[name] = mod
|
||||
}
|
||||
}
|
41
vendor/github.com/d5/tengo/objects/object_ptr.go
generated
vendored
Normal file
41
vendor/github.com/d5/tengo/objects/object_ptr.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"github.com/d5/tengo/compiler/token"
|
||||
)
|
||||
|
||||
// ObjectPtr represents a free variable.
|
||||
type ObjectPtr struct {
|
||||
Value *Object
|
||||
}
|
||||
|
||||
func (o *ObjectPtr) String() string {
|
||||
return "free-var"
|
||||
}
|
||||
|
||||
// TypeName returns the name of the type.
|
||||
func (o *ObjectPtr) TypeName() string {
|
||||
return "<free-var>"
|
||||
}
|
||||
|
||||
// BinaryOp returns another object that is the result of
|
||||
// a given binary operator and a right-hand side object.
|
||||
func (o *ObjectPtr) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
||||
return nil, ErrInvalidOperator
|
||||
}
|
||||
|
||||
// Copy returns a copy of the type.
|
||||
func (o *ObjectPtr) Copy() Object {
|
||||
return o
|
||||
}
|
||||
|
||||
// IsFalsy returns true if the value of the type is falsy.
|
||||
func (o *ObjectPtr) IsFalsy() bool {
|
||||
return o.Value == nil
|
||||
}
|
||||
|
||||
// Equals returns true if the value of the type
|
||||
// is equal to the value of another object.
|
||||
func (o *ObjectPtr) Equals(x Object) bool {
|
||||
return o == x
|
||||
}
|
39
vendor/github.com/d5/tengo/objects/return_value.go
generated
vendored
39
vendor/github.com/d5/tengo/objects/return_value.go
generated
vendored
@ -1,39 +0,0 @@
|
||||
package objects
|
||||
|
||||
import "github.com/d5/tengo/compiler/token"
|
||||
|
||||
// ReturnValue represents a value that is being returned.
|
||||
type ReturnValue struct {
|
||||
Value Object
|
||||
}
|
||||
|
||||
// TypeName returns the name of the type.
|
||||
func (o *ReturnValue) TypeName() string {
|
||||
return "return-value"
|
||||
}
|
||||
|
||||
func (o *ReturnValue) String() string {
|
||||
return "<return-value>"
|
||||
}
|
||||
|
||||
// BinaryOp returns another object that is the result of
|
||||
// a given binary operator and a right-hand side object.
|
||||
func (o *ReturnValue) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
||||
return nil, ErrInvalidOperator
|
||||
}
|
||||
|
||||
// Copy returns a copy of the type.
|
||||
func (o *ReturnValue) Copy() Object {
|
||||
return &ReturnValue{Value: o.Copy()}
|
||||
}
|
||||
|
||||
// IsFalsy returns true if the value of the type is falsy.
|
||||
func (o *ReturnValue) IsFalsy() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Equals returns true if the value of the type
|
||||
// is equal to the value of another object.
|
||||
func (o *ReturnValue) Equals(x Object) bool {
|
||||
return false
|
||||
}
|
11
vendor/github.com/d5/tengo/objects/source_module.go
generated
vendored
Normal file
11
vendor/github.com/d5/tengo/objects/source_module.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package objects
|
||||
|
||||
// SourceModule is an importable module that's written in Tengo.
|
||||
type SourceModule struct {
|
||||
Src []byte
|
||||
}
|
||||
|
||||
// Import returns a module source code.
|
||||
func (m *SourceModule) Import(_ string) (interface{}, error) {
|
||||
return m.Src, nil
|
||||
}
|
10
vendor/github.com/d5/tengo/objects/string.go
generated
vendored
10
vendor/github.com/d5/tengo/objects/string.go
generated
vendored
@ -3,6 +3,7 @@ package objects
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/compiler/token"
|
||||
)
|
||||
|
||||
@ -28,9 +29,16 @@ func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
||||
case token.Add:
|
||||
switch rhs := rhs.(type) {
|
||||
case *String:
|
||||
if len(o.Value)+len(rhs.Value) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
return &String{Value: o.Value + rhs.Value}, nil
|
||||
default:
|
||||
return &String{Value: o.Value + rhs.String()}, nil
|
||||
rhsStr := rhs.String()
|
||||
if len(o.Value)+len(rhsStr) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
return &String{Value: o.Value + rhsStr}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
5
vendor/github.com/d5/tengo/objects/user_function.go
generated
vendored
5
vendor/github.com/d5/tengo/objects/user_function.go
generated
vendored
@ -6,8 +6,9 @@ import (
|
||||
|
||||
// UserFunction represents a user function.
|
||||
type UserFunction struct {
|
||||
Name string
|
||||
Value CallableFunc
|
||||
Name string
|
||||
Value CallableFunc
|
||||
EncodingID string
|
||||
}
|
||||
|
||||
// TypeName returns the name of the type.
|
||||
|
3
vendor/github.com/d5/tengo/runtime/errors.go
generated
vendored
3
vendor/github.com/d5/tengo/runtime/errors.go
generated
vendored
@ -6,3 +6,6 @@ import (
|
||||
|
||||
// ErrStackOverflow is a stack overflow error.
|
||||
var ErrStackOverflow = errors.New("stack overflow")
|
||||
|
||||
// ErrObjectAllocLimit is an objects allocation limit error.
|
||||
var ErrObjectAllocLimit = errors.New("object allocation limit exceeded")
|
||||
|
2
vendor/github.com/d5/tengo/runtime/frame.go
generated
vendored
2
vendor/github.com/d5/tengo/runtime/frame.go
generated
vendored
@ -7,7 +7,7 @@ import (
|
||||
// Frame represents a function call frame.
|
||||
type Frame struct {
|
||||
fn *objects.CompiledFunction
|
||||
freeVars []*objects.Object
|
||||
freeVars []*objects.ObjectPtr
|
||||
ip int
|
||||
basePointer int
|
||||
}
|
||||
|
1183
vendor/github.com/d5/tengo/runtime/vm.go
generated
vendored
1183
vendor/github.com/d5/tengo/runtime/vm.go
generated
vendored
File diff suppressed because it is too large
Load Diff
102
vendor/github.com/d5/tengo/script/compiled.go
generated
vendored
102
vendor/github.com/d5/tengo/script/compiled.go
generated
vendored
@ -3,6 +3,7 @@ package script
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/d5/tengo/compiler"
|
||||
"github.com/d5/tengo/objects"
|
||||
@ -12,26 +13,39 @@ import (
|
||||
// Compiled is a compiled instance of the user script.
|
||||
// Use Script.Compile() to create Compiled object.
|
||||
type Compiled struct {
|
||||
symbolTable *compiler.SymbolTable
|
||||
machine *runtime.VM
|
||||
globalIndexes map[string]int // global symbol name to index
|
||||
bytecode *compiler.Bytecode
|
||||
globals []objects.Object
|
||||
maxAllocs int64
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// Run executes the compiled script in the virtual machine.
|
||||
func (c *Compiled) Run() error {
|
||||
return c.machine.Run()
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs)
|
||||
|
||||
return v.Run()
|
||||
}
|
||||
|
||||
// RunContext is like Run but includes a context.
|
||||
func (c *Compiled) RunContext(ctx context.Context) (err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs)
|
||||
|
||||
ch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
ch <- c.machine.Run()
|
||||
ch <- v.Run()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.machine.Abort()
|
||||
v.Abort()
|
||||
<-ch
|
||||
err = ctx.Err()
|
||||
case err = <-ch:
|
||||
@ -40,30 +54,58 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Clone creates a new copy of Compiled.
|
||||
// Cloned copies are safe for concurrent use by multiple goroutines.
|
||||
func (c *Compiled) Clone() *Compiled {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
clone := &Compiled{
|
||||
globalIndexes: c.globalIndexes,
|
||||
bytecode: c.bytecode,
|
||||
globals: make([]objects.Object, len(c.globals)),
|
||||
maxAllocs: c.maxAllocs,
|
||||
}
|
||||
|
||||
// copy global objects
|
||||
for idx, g := range c.globals {
|
||||
if g != nil {
|
||||
clone.globals[idx] = g
|
||||
}
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// IsDefined returns true if the variable name is defined (has value) before or after the execution.
|
||||
func (c *Compiled) IsDefined(name string) bool {
|
||||
symbol, _, ok := c.symbolTable.Resolve(name)
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
idx, ok := c.globalIndexes[name]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
v := c.machine.Globals()[symbol.Index]
|
||||
v := c.globals[idx]
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return *v != objects.UndefinedValue
|
||||
return v != objects.UndefinedValue
|
||||
}
|
||||
|
||||
// Get returns a variable identified by the name.
|
||||
func (c *Compiled) Get(name string) *Variable {
|
||||
value := &objects.UndefinedValue
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
symbol, _, ok := c.symbolTable.Resolve(name)
|
||||
if ok && symbol.Scope == compiler.ScopeGlobal {
|
||||
value = c.machine.Globals()[symbol.Index]
|
||||
value := objects.UndefinedValue
|
||||
|
||||
if idx, ok := c.globalIndexes[name]; ok {
|
||||
value = c.globals[idx]
|
||||
if value == nil {
|
||||
value = &objects.UndefinedValue
|
||||
value = objects.UndefinedValue
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,20 +117,21 @@ func (c *Compiled) Get(name string) *Variable {
|
||||
|
||||
// GetAll returns all the variables that are defined by the compiled script.
|
||||
func (c *Compiled) GetAll() []*Variable {
|
||||
var vars []*Variable
|
||||
for _, name := range c.symbolTable.Names() {
|
||||
symbol, _, ok := c.symbolTable.Resolve(name)
|
||||
if ok && symbol.Scope == compiler.ScopeGlobal {
|
||||
value := c.machine.Globals()[symbol.Index]
|
||||
if value == nil {
|
||||
value = &objects.UndefinedValue
|
||||
}
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
vars = append(vars, &Variable{
|
||||
name: name,
|
||||
value: value,
|
||||
})
|
||||
var vars []*Variable
|
||||
|
||||
for name, idx := range c.globalIndexes {
|
||||
value := c.globals[idx]
|
||||
if value == nil {
|
||||
value = objects.UndefinedValue
|
||||
}
|
||||
|
||||
vars = append(vars, &Variable{
|
||||
name: name,
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
|
||||
return vars
|
||||
@ -97,17 +140,20 @@ func (c *Compiled) GetAll() []*Variable {
|
||||
// Set replaces the value of a global variable identified by the name.
|
||||
// An error will be returned if the name was not defined during compilation.
|
||||
func (c *Compiled) Set(name string, value interface{}) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
obj, err := objects.FromInterface(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
symbol, _, ok := c.symbolTable.Resolve(name)
|
||||
if !ok || symbol.Scope != compiler.ScopeGlobal {
|
||||
idx, ok := c.globalIndexes[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("'%s' is not defined", name)
|
||||
}
|
||||
|
||||
c.machine.Globals()[symbol.Index] = &obj
|
||||
c.globals[idx] = obj
|
||||
|
||||
return nil
|
||||
}
|
||||
|
33
vendor/github.com/d5/tengo/script/conversion.go
generated
vendored
33
vendor/github.com/d5/tengo/script/conversion.go
generated
vendored
@ -1,33 +0,0 @@
|
||||
package script
|
||||
|
||||
import (
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
func objectToInterface(o objects.Object) interface{} {
|
||||
switch val := o.(type) {
|
||||
case *objects.Array:
|
||||
return val.Value
|
||||
case *objects.Map:
|
||||
return val.Value
|
||||
case *objects.Int:
|
||||
return val.Value
|
||||
case *objects.Float:
|
||||
return val.Value
|
||||
case *objects.Bool:
|
||||
if val == objects.TrueValue {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case *objects.Char:
|
||||
return val.Value
|
||||
case *objects.String:
|
||||
return val.Value
|
||||
case *objects.Bytes:
|
||||
return val.Value
|
||||
case *objects.Undefined:
|
||||
return nil
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
110
vendor/github.com/d5/tengo/script/script.go
generated
vendored
110
vendor/github.com/d5/tengo/script/script.go
generated
vendored
@ -9,23 +9,25 @@ import (
|
||||
"github.com/d5/tengo/compiler/source"
|
||||
"github.com/d5/tengo/objects"
|
||||
"github.com/d5/tengo/runtime"
|
||||
"github.com/d5/tengo/stdlib"
|
||||
)
|
||||
|
||||
// Script can simplify compilation and execution of embedded scripts.
|
||||
type Script struct {
|
||||
variables map[string]*Variable
|
||||
removedBuiltins map[string]bool
|
||||
removedStdModules map[string]bool
|
||||
userModuleLoader compiler.ModuleLoader
|
||||
input []byte
|
||||
variables map[string]*Variable
|
||||
modules *objects.ModuleMap
|
||||
input []byte
|
||||
maxAllocs int64
|
||||
maxConstObjects int
|
||||
enableFileImport bool
|
||||
}
|
||||
|
||||
// New creates a Script instance with an input script.
|
||||
func New(input []byte) *Script {
|
||||
return &Script{
|
||||
variables: make(map[string]*Variable),
|
||||
input: input,
|
||||
variables: make(map[string]*Variable),
|
||||
input: input,
|
||||
maxAllocs: -1,
|
||||
maxConstObjects: -1,
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +40,7 @@ func (s *Script) Add(name string, value interface{}) error {
|
||||
|
||||
s.variables[name] = &Variable{
|
||||
name: name,
|
||||
value: &obj,
|
||||
value: obj,
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -56,32 +58,31 @@ func (s *Script) Remove(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// DisableBuiltinFunction disables a builtin function.
|
||||
func (s *Script) DisableBuiltinFunction(name string) {
|
||||
if s.removedBuiltins == nil {
|
||||
s.removedBuiltins = make(map[string]bool)
|
||||
}
|
||||
|
||||
s.removedBuiltins[name] = true
|
||||
// SetImports sets import modules.
|
||||
func (s *Script) SetImports(modules *objects.ModuleMap) {
|
||||
s.modules = modules
|
||||
}
|
||||
|
||||
// DisableStdModule disables a standard library module.
|
||||
func (s *Script) DisableStdModule(name string) {
|
||||
if s.removedStdModules == nil {
|
||||
s.removedStdModules = make(map[string]bool)
|
||||
}
|
||||
|
||||
s.removedStdModules[name] = true
|
||||
// SetMaxAllocs sets the maximum number of objects allocations during the run time.
|
||||
// Compiled script will return runtime.ErrObjectAllocLimit error if it exceeds this limit.
|
||||
func (s *Script) SetMaxAllocs(n int64) {
|
||||
s.maxAllocs = n
|
||||
}
|
||||
|
||||
// SetUserModuleLoader sets the user module loader for the compiler.
|
||||
func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
|
||||
s.userModuleLoader = loader
|
||||
// SetMaxConstObjects sets the maximum number of objects in the compiled constants.
|
||||
func (s *Script) SetMaxConstObjects(n int) {
|
||||
s.maxConstObjects = n
|
||||
}
|
||||
|
||||
// EnableFileImport enables or disables module loading from local files.
|
||||
// Local file modules are disabled by default.
|
||||
func (s *Script) EnableFileImport(enable bool) {
|
||||
s.enableFileImport = enable
|
||||
}
|
||||
|
||||
// Compile compiles the script with all the defined variables, and, returns Compiled object.
|
||||
func (s *Script) Compile() (*Compiled, error) {
|
||||
symbolTable, stdModules, globals, err := s.prepCompile()
|
||||
symbolTable, globals, err := s.prepCompile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -92,22 +93,44 @@ func (s *Script) Compile() (*Compiled, error) {
|
||||
p := parser.NewParser(srcFile, s.input, nil)
|
||||
file, err := p.ParseFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse error: %s", err.Error())
|
||||
}
|
||||
|
||||
c := compiler.NewCompiler(srcFile, symbolTable, nil, stdModules, nil)
|
||||
|
||||
if s.userModuleLoader != nil {
|
||||
c.SetModuleLoader(s.userModuleLoader)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := compiler.NewCompiler(srcFile, symbolTable, nil, s.modules, nil)
|
||||
c.EnableFileImport(s.enableFileImport)
|
||||
if err := c.Compile(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// reduce globals size
|
||||
globals = globals[:symbolTable.MaxSymbols()+1]
|
||||
|
||||
// global symbol names to indexes
|
||||
globalIndexes := make(map[string]int, len(globals))
|
||||
for _, name := range symbolTable.Names() {
|
||||
symbol, _, _ := symbolTable.Resolve(name)
|
||||
if symbol.Scope == compiler.ScopeGlobal {
|
||||
globalIndexes[name] = symbol.Index
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates from constants
|
||||
bytecode := c.Bytecode()
|
||||
bytecode.RemoveDuplicates()
|
||||
|
||||
// check the constant objects limit
|
||||
if s.maxConstObjects >= 0 {
|
||||
cnt := bytecode.CountObjects()
|
||||
if cnt > s.maxConstObjects {
|
||||
return nil, fmt.Errorf("exceeding constant objects limit: %d", cnt)
|
||||
}
|
||||
}
|
||||
|
||||
return &Compiled{
|
||||
symbolTable: symbolTable,
|
||||
machine: runtime.NewVM(c.Bytecode(), globals, nil),
|
||||
globalIndexes: globalIndexes,
|
||||
bytecode: bytecode,
|
||||
globals: globals,
|
||||
maxAllocs: s.maxAllocs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -136,7 +159,7 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]bool, globals []*objects.Object, err error) {
|
||||
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []objects.Object, err error) {
|
||||
var names []string
|
||||
for name := range s.variables {
|
||||
names = append(names, name)
|
||||
@ -144,19 +167,10 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules ma
|
||||
|
||||
symbolTable = compiler.NewSymbolTable()
|
||||
for idx, fn := range objects.Builtins {
|
||||
if !s.removedBuiltins[fn.Name] {
|
||||
symbolTable.DefineBuiltin(idx, fn.Name)
|
||||
}
|
||||
symbolTable.DefineBuiltin(idx, fn.Name)
|
||||
}
|
||||
|
||||
stdModules = make(map[string]bool)
|
||||
for name := range stdlib.Modules {
|
||||
if !s.removedStdModules[name] {
|
||||
stdModules[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
globals = make([]*objects.Object, runtime.GlobalsSize, runtime.GlobalsSize)
|
||||
globals = make([]objects.Object, runtime.GlobalsSize)
|
||||
|
||||
for idx, name := range names {
|
||||
symbol := symbolTable.Define(name)
|
||||
|
36
vendor/github.com/d5/tengo/script/variable.go
generated
vendored
36
vendor/github.com/d5/tengo/script/variable.go
generated
vendored
@ -9,7 +9,7 @@ import (
|
||||
// Variable is a user-defined variable for the script.
|
||||
type Variable struct {
|
||||
name string
|
||||
value *objects.Object
|
||||
value objects.Object
|
||||
}
|
||||
|
||||
// NewVariable creates a Variable.
|
||||
@ -21,7 +21,7 @@ func NewVariable(name string, value interface{}) (*Variable, error) {
|
||||
|
||||
return &Variable{
|
||||
name: name,
|
||||
value: &obj,
|
||||
value: obj,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -32,18 +32,18 @@ func (v *Variable) Name() string {
|
||||
|
||||
// Value returns an empty interface of the variable value.
|
||||
func (v *Variable) Value() interface{} {
|
||||
return objectToInterface(*v.value)
|
||||
return objects.ToInterface(v.value)
|
||||
}
|
||||
|
||||
// ValueType returns the name of the value type.
|
||||
func (v *Variable) ValueType() string {
|
||||
return (*v.value).TypeName()
|
||||
return v.value.TypeName()
|
||||
}
|
||||
|
||||
// Int returns int value of the variable value.
|
||||
// It returns 0 if the value is not convertible to int.
|
||||
func (v *Variable) Int() int {
|
||||
c, _ := objects.ToInt(*v.value)
|
||||
c, _ := objects.ToInt(v.value)
|
||||
|
||||
return c
|
||||
}
|
||||
@ -51,7 +51,7 @@ func (v *Variable) Int() int {
|
||||
// Int64 returns int64 value of the variable value.
|
||||
// It returns 0 if the value is not convertible to int64.
|
||||
func (v *Variable) Int64() int64 {
|
||||
c, _ := objects.ToInt64(*v.value)
|
||||
c, _ := objects.ToInt64(v.value)
|
||||
|
||||
return c
|
||||
}
|
||||
@ -59,7 +59,7 @@ func (v *Variable) Int64() int64 {
|
||||
// Float returns float64 value of the variable value.
|
||||
// It returns 0.0 if the value is not convertible to float64.
|
||||
func (v *Variable) Float() float64 {
|
||||
c, _ := objects.ToFloat64(*v.value)
|
||||
c, _ := objects.ToFloat64(v.value)
|
||||
|
||||
return c
|
||||
}
|
||||
@ -67,7 +67,7 @@ func (v *Variable) Float() float64 {
|
||||
// Char returns rune value of the variable value.
|
||||
// It returns 0 if the value is not convertible to rune.
|
||||
func (v *Variable) Char() rune {
|
||||
c, _ := objects.ToRune(*v.value)
|
||||
c, _ := objects.ToRune(v.value)
|
||||
|
||||
return c
|
||||
}
|
||||
@ -75,7 +75,7 @@ func (v *Variable) Char() rune {
|
||||
// Bool returns bool value of the variable value.
|
||||
// It returns 0 if the value is not convertible to bool.
|
||||
func (v *Variable) Bool() bool {
|
||||
c, _ := objects.ToBool(*v.value)
|
||||
c, _ := objects.ToBool(v.value)
|
||||
|
||||
return c
|
||||
}
|
||||
@ -83,11 +83,11 @@ func (v *Variable) Bool() bool {
|
||||
// Array returns []interface value of the variable value.
|
||||
// It returns 0 if the value is not convertible to []interface.
|
||||
func (v *Variable) Array() []interface{} {
|
||||
switch val := (*v.value).(type) {
|
||||
switch val := v.value.(type) {
|
||||
case *objects.Array:
|
||||
var arr []interface{}
|
||||
for _, e := range val.Value {
|
||||
arr = append(arr, objectToInterface(e))
|
||||
arr = append(arr, objects.ToInterface(e))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
@ -98,11 +98,11 @@ func (v *Variable) Array() []interface{} {
|
||||
// Map returns map[string]interface{} value of the variable value.
|
||||
// It returns 0 if the value is not convertible to map[string]interface{}.
|
||||
func (v *Variable) Map() map[string]interface{} {
|
||||
switch val := (*v.value).(type) {
|
||||
switch val := v.value.(type) {
|
||||
case *objects.Map:
|
||||
kv := make(map[string]interface{})
|
||||
for mk, mv := range val.Value {
|
||||
kv[mk] = objectToInterface(mv)
|
||||
kv[mk] = objects.ToInterface(mv)
|
||||
}
|
||||
return kv
|
||||
}
|
||||
@ -113,7 +113,7 @@ func (v *Variable) Map() map[string]interface{} {
|
||||
// String returns string value of the variable value.
|
||||
// It returns 0 if the value is not convertible to string.
|
||||
func (v *Variable) String() string {
|
||||
c, _ := objects.ToString(*v.value)
|
||||
c, _ := objects.ToString(v.value)
|
||||
|
||||
return c
|
||||
}
|
||||
@ -121,7 +121,7 @@ func (v *Variable) String() string {
|
||||
// Bytes returns a byte slice of the variable value.
|
||||
// It returns nil if the value is not convertible to byte slice.
|
||||
func (v *Variable) Bytes() []byte {
|
||||
c, _ := objects.ToByteSlice(*v.value)
|
||||
c, _ := objects.ToByteSlice(v.value)
|
||||
|
||||
return c
|
||||
}
|
||||
@ -129,7 +129,7 @@ func (v *Variable) Bytes() []byte {
|
||||
// Error returns an error if the underlying value is error object.
|
||||
// If not, this returns nil.
|
||||
func (v *Variable) Error() error {
|
||||
err, ok := (*v.value).(*objects.Error)
|
||||
err, ok := v.value.(*objects.Error)
|
||||
if ok {
|
||||
return errors.New(err.String())
|
||||
}
|
||||
@ -140,10 +140,10 @@ func (v *Variable) Error() error {
|
||||
// Object returns an underlying Object of the variable value.
|
||||
// Note that returned Object is a copy of an actual Object used in the script.
|
||||
func (v *Variable) Object() objects.Object {
|
||||
return *v.value
|
||||
return v.value
|
||||
}
|
||||
|
||||
// IsUndefined returns true if the underlying value is undefined.
|
||||
func (v *Variable) IsUndefined() bool {
|
||||
return *v.value == objects.UndefinedValue
|
||||
return v.value == objects.UndefinedValue
|
||||
}
|
||||
|
14
vendor/github.com/d5/tengo/stdlib/builtin_modules.go
generated
vendored
Normal file
14
vendor/github.com/d5/tengo/stdlib/builtin_modules.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package stdlib
|
||||
|
||||
import "github.com/d5/tengo/objects"
|
||||
|
||||
// BuiltinModules are builtin type standard library modules.
|
||||
var BuiltinModules = map[string]map[string]objects.Object{
|
||||
"math": mathModule,
|
||||
"os": osModule,
|
||||
"text": textModule,
|
||||
"times": timesModule,
|
||||
"rand": randModule,
|
||||
"fmt": fmtModule,
|
||||
"json": jsonModule,
|
||||
}
|
116
vendor/github.com/d5/tengo/stdlib/fmt.go
generated
vendored
Normal file
116
vendor/github.com/d5/tengo/stdlib/fmt.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
package stdlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
var fmtModule = map[string]objects.Object{
|
||||
"print": &objects.UserFunction{Name: "print", Value: fmtPrint},
|
||||
"printf": &objects.UserFunction{Name: "printf", Value: fmtPrintf},
|
||||
"println": &objects.UserFunction{Name: "println", Value: fmtPrintln},
|
||||
"sprintf": &objects.UserFunction{Name: "sprintf", Value: fmtSprintf},
|
||||
}
|
||||
|
||||
func fmtPrint(args ...objects.Object) (ret objects.Object, err error) {
|
||||
printArgs, err := getPrintArgs(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _ = fmt.Print(printArgs...)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func fmtPrintf(args ...objects.Object) (ret objects.Object, err error) {
|
||||
numArgs := len(args)
|
||||
if numArgs == 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
format, ok := args[0].(*objects.String)
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "format",
|
||||
Expected: "string",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
if numArgs == 1 {
|
||||
fmt.Print(format)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
formatArgs := make([]interface{}, numArgs-1, numArgs-1)
|
||||
for idx, arg := range args[1:] {
|
||||
formatArgs[idx] = objects.ToInterface(arg)
|
||||
}
|
||||
|
||||
fmt.Printf(format.Value, formatArgs...)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func fmtPrintln(args ...objects.Object) (ret objects.Object, err error) {
|
||||
printArgs, err := getPrintArgs(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
printArgs = append(printArgs, "\n")
|
||||
_, _ = fmt.Print(printArgs...)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func fmtSprintf(args ...objects.Object) (ret objects.Object, err error) {
|
||||
numArgs := len(args)
|
||||
if numArgs == 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
format, ok := args[0].(*objects.String)
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "format",
|
||||
Expected: "string",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
if numArgs == 1 {
|
||||
return format, nil // okay to return 'format' directly as String is immutable
|
||||
}
|
||||
|
||||
formatArgs := make([]interface{}, numArgs-1, numArgs-1)
|
||||
for idx, arg := range args[1:] {
|
||||
formatArgs[idx] = objects.ToInterface(arg)
|
||||
}
|
||||
|
||||
s := fmt.Sprintf(format.Value, formatArgs...)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
|
||||
func getPrintArgs(args ...objects.Object) ([]interface{}, error) {
|
||||
var printArgs []interface{}
|
||||
l := 0
|
||||
for _, arg := range args {
|
||||
s, _ := objects.ToString(arg)
|
||||
slen := len(s)
|
||||
if l+slen > tengo.MaxStringLen { // make sure length does not exceed the limit
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
l += slen
|
||||
|
||||
printArgs = append(printArgs, s)
|
||||
}
|
||||
|
||||
return printArgs, nil
|
||||
}
|
88
vendor/github.com/d5/tengo/stdlib/func_typedefs.go
generated
vendored
88
vendor/github.com/d5/tengo/stdlib/func_typedefs.go
generated
vendored
@ -3,6 +3,7 @@ package stdlib
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
@ -124,7 +125,13 @@ func FuncARS(fn func() string) objects.CallableFunc {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn()}, nil
|
||||
s := fn()
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +148,10 @@ func FuncARSE(fn func() (string, error)) objects.CallableFunc {
|
||||
return wrapError(err), nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: res}, nil
|
||||
}
|
||||
}
|
||||
@ -158,6 +169,10 @@ func FuncARYE(fn func() ([]byte, error)) objects.CallableFunc {
|
||||
return wrapError(err), nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxBytesLen {
|
||||
return nil, objects.ErrBytesLimit
|
||||
}
|
||||
|
||||
return &objects.Bytes{Value: res}, nil
|
||||
}
|
||||
}
|
||||
@ -183,8 +198,12 @@ func FuncARSs(fn func() []string) objects.CallableFunc {
|
||||
}
|
||||
|
||||
arr := &objects.Array{}
|
||||
for _, osArg := range fn() {
|
||||
arr.Value = append(arr.Value, &objects.String{Value: osArg})
|
||||
for _, elem := range fn() {
|
||||
if len(elem) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: elem})
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
@ -493,7 +512,13 @@ func FuncASRS(fn func(string) string) objects.CallableFunc {
|
||||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(s1)}, nil
|
||||
s := fn(s1)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -516,8 +541,12 @@ func FuncASRSs(fn func(string) []string) objects.CallableFunc {
|
||||
res := fn(s1)
|
||||
|
||||
arr := &objects.Array{}
|
||||
for _, osArg := range res {
|
||||
arr.Value = append(arr.Value, &objects.String{Value: osArg})
|
||||
for _, elem := range res {
|
||||
if len(elem) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: elem})
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
@ -546,6 +575,10 @@ func FuncASRSE(fn func(string) (string, error)) objects.CallableFunc {
|
||||
return wrapError(err), nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: res}, nil
|
||||
}
|
||||
}
|
||||
@ -628,6 +661,10 @@ func FuncASSRSs(fn func(string, string) []string) objects.CallableFunc {
|
||||
|
||||
arr := &objects.Array{}
|
||||
for _, res := range fn(s1, s2) {
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: res})
|
||||
}
|
||||
|
||||
@ -671,6 +708,10 @@ func FuncASSIRSs(fn func(string, string, int) []string) objects.CallableFunc {
|
||||
|
||||
arr := &objects.Array{}
|
||||
for _, res := range fn(s1, s2, i3) {
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: res})
|
||||
}
|
||||
|
||||
@ -732,7 +773,13 @@ func FuncASSRS(fn func(string, string) string) objects.CallableFunc {
|
||||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(s1, s2)}, nil
|
||||
s := fn(s1, s2)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -819,7 +866,12 @@ func FuncASsSRS(fn func([]string, string) string) objects.CallableFunc {
|
||||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(ss1, s2)}, nil
|
||||
s := fn(ss1, s2)
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -909,7 +961,13 @@ func FuncASIRS(fn func(string, int) string) objects.CallableFunc {
|
||||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(s1, i2)}, nil
|
||||
s := fn(s1, i2)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -1028,6 +1086,10 @@ func FuncAIRSsE(fn func(int) ([]string, error)) objects.CallableFunc {
|
||||
|
||||
arr := &objects.Array{}
|
||||
for _, r := range res {
|
||||
if len(r) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: r})
|
||||
}
|
||||
|
||||
@ -1052,6 +1114,12 @@ func FuncAIRS(fn func(int) string) objects.CallableFunc {
|
||||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(i1)}, nil
|
||||
s := fn(i1)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
53
vendor/github.com/d5/tengo/stdlib/gensrcmods.go
generated
vendored
Normal file
53
vendor/github.com/d5/tengo/stdlib/gensrcmods.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var tengoModFileRE = regexp.MustCompile(`^srcmod_(\w+).tengo$`)
|
||||
|
||||
func main() {
|
||||
modules := make(map[string]string)
|
||||
|
||||
// enumerate all Tengo module files
|
||||
files, err := ioutil.ReadDir(".")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
m := tengoModFileRE.FindStringSubmatch(file.Name())
|
||||
if m != nil {
|
||||
modName := m[1]
|
||||
|
||||
src, err := ioutil.ReadFile(file.Name())
|
||||
if err != nil {
|
||||
log.Fatalf("file '%s' read error: %s", file.Name(), err.Error())
|
||||
}
|
||||
|
||||
modules[modName] = string(src)
|
||||
}
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
out.WriteString(`// Code generated using gensrcmods.go; DO NOT EDIT.
|
||||
|
||||
package stdlib
|
||||
|
||||
// SourceModules are source type standard library modules.
|
||||
var SourceModules = map[string]string{` + "\n")
|
||||
for modName, modSrc := range modules {
|
||||
out.WriteString("\t\"" + modName + "\": " + strconv.Quote(modSrc) + ",\n")
|
||||
}
|
||||
out.WriteString("}\n")
|
||||
|
||||
const target = "source_modules.go"
|
||||
if err := ioutil.WriteFile(target, out.Bytes(), 0644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
126
vendor/github.com/d5/tengo/stdlib/json.go
generated
vendored
Normal file
126
vendor/github.com/d5/tengo/stdlib/json.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
package stdlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
gojson "encoding/json"
|
||||
|
||||
"github.com/d5/tengo/objects"
|
||||
"github.com/d5/tengo/stdlib/json"
|
||||
)
|
||||
|
||||
var jsonModule = map[string]objects.Object{
|
||||
"decode": &objects.UserFunction{Name: "decode", Value: jsonDecode},
|
||||
"encode": &objects.UserFunction{Name: "encode", Value: jsonEncode},
|
||||
"indent": &objects.UserFunction{Name: "encode", Value: jsonIndent},
|
||||
"html_escape": &objects.UserFunction{Name: "html_escape", Value: jsonHTMLEscape},
|
||||
}
|
||||
|
||||
func jsonDecode(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
switch o := args[0].(type) {
|
||||
case *objects.Bytes:
|
||||
v, err := json.Decode(o.Value)
|
||||
if err != nil {
|
||||
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
|
||||
}
|
||||
return v, nil
|
||||
case *objects.String:
|
||||
v, err := json.Decode([]byte(o.Value))
|
||||
if err != nil {
|
||||
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "bytes/string",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func jsonEncode(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
b, err := json.Encode(args[0])
|
||||
if err != nil {
|
||||
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
|
||||
}
|
||||
|
||||
return &objects.Bytes{Value: b}, nil
|
||||
}
|
||||
|
||||
func jsonIndent(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 3 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
prefix, ok := objects.ToString(args[1])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "prefix",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[1].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
indent, ok := objects.ToString(args[2])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "indent",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[2].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
switch o := args[0].(type) {
|
||||
case *objects.Bytes:
|
||||
var dst bytes.Buffer
|
||||
err := gojson.Indent(&dst, o.Value, prefix, indent)
|
||||
if err != nil {
|
||||
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
|
||||
}
|
||||
return &objects.Bytes{Value: dst.Bytes()}, nil
|
||||
case *objects.String:
|
||||
var dst bytes.Buffer
|
||||
err := gojson.Indent(&dst, []byte(o.Value), prefix, indent)
|
||||
if err != nil {
|
||||
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
|
||||
}
|
||||
return &objects.Bytes{Value: dst.Bytes()}, nil
|
||||
default:
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "bytes/string",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func jsonHTMLEscape(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
switch o := args[0].(type) {
|
||||
case *objects.Bytes:
|
||||
var dst bytes.Buffer
|
||||
gojson.HTMLEscape(&dst, o.Value)
|
||||
return &objects.Bytes{Value: dst.Bytes()}, nil
|
||||
case *objects.String:
|
||||
var dst bytes.Buffer
|
||||
gojson.HTMLEscape(&dst, []byte(o.Value))
|
||||
return &objects.Bytes{Value: dst.Bytes()}, nil
|
||||
default:
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "bytes/string",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
}
|
374
vendor/github.com/d5/tengo/stdlib/json/decode.go
generated
vendored
Normal file
374
vendor/github.com/d5/tengo/stdlib/json/decode.go
generated
vendored
Normal file
@ -0,0 +1,374 @@
|
||||
// A modified version of Go's JSON implementation.
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
// Decode parses the JSON-encoded data and returns the result object.
|
||||
func Decode(data []byte) (objects.Object, error) {
|
||||
var d decodeState
|
||||
err := checkValid(data, &d.scan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.init(data)
|
||||
d.scan.reset()
|
||||
d.scanWhile(scanSkipSpace)
|
||||
|
||||
return d.value()
|
||||
}
|
||||
|
||||
// decodeState represents the state while decoding a JSON value.
|
||||
type decodeState struct {
|
||||
data []byte
|
||||
off int // next read offset in data
|
||||
opcode int // last read result
|
||||
scan scanner
|
||||
}
|
||||
|
||||
// readIndex returns the position of the last byte read.
|
||||
func (d *decodeState) readIndex() int {
|
||||
return d.off - 1
|
||||
}
|
||||
|
||||
const phasePanicMsg = "JSON decoder out of sync - data changing underfoot?"
|
||||
|
||||
func (d *decodeState) init(data []byte) *decodeState {
|
||||
d.data = data
|
||||
d.off = 0
|
||||
return d
|
||||
}
|
||||
|
||||
// scanNext processes the byte at d.data[d.off].
|
||||
func (d *decodeState) scanNext() {
|
||||
if d.off < len(d.data) {
|
||||
d.opcode = d.scan.step(&d.scan, d.data[d.off])
|
||||
d.off++
|
||||
} else {
|
||||
d.opcode = d.scan.eof()
|
||||
d.off = len(d.data) + 1 // mark processed EOF with len+1
|
||||
}
|
||||
}
|
||||
|
||||
// scanWhile processes bytes in d.data[d.off:] until it
|
||||
// receives a scan code not equal to op.
|
||||
func (d *decodeState) scanWhile(op int) {
|
||||
s, data, i := &d.scan, d.data, d.off
|
||||
for i < len(data) {
|
||||
newOp := s.step(s, data[i])
|
||||
i++
|
||||
if newOp != op {
|
||||
d.opcode = newOp
|
||||
d.off = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
d.off = len(data) + 1 // mark processed EOF with len+1
|
||||
d.opcode = d.scan.eof()
|
||||
}
|
||||
|
||||
func (d *decodeState) value() (objects.Object, error) {
|
||||
switch d.opcode {
|
||||
default:
|
||||
panic(phasePanicMsg)
|
||||
|
||||
case scanBeginArray:
|
||||
o, err := d.array()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.scanNext()
|
||||
|
||||
return o, nil
|
||||
|
||||
case scanBeginObject:
|
||||
o, err := d.object()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.scanNext()
|
||||
|
||||
return o, nil
|
||||
|
||||
case scanBeginLiteral:
|
||||
return d.literal()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decodeState) array() (objects.Object, error) {
|
||||
var arr []objects.Object
|
||||
for {
|
||||
// Look ahead for ] - can only happen on first iteration.
|
||||
d.scanWhile(scanSkipSpace)
|
||||
if d.opcode == scanEndArray {
|
||||
break
|
||||
}
|
||||
|
||||
o, err := d.value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr = append(arr, o)
|
||||
|
||||
// Next token must be , or ].
|
||||
if d.opcode == scanSkipSpace {
|
||||
d.scanWhile(scanSkipSpace)
|
||||
}
|
||||
if d.opcode == scanEndArray {
|
||||
break
|
||||
}
|
||||
if d.opcode != scanArrayValue {
|
||||
panic(phasePanicMsg)
|
||||
}
|
||||
}
|
||||
|
||||
return &objects.Array{Value: arr}, nil
|
||||
}
|
||||
|
||||
func (d *decodeState) object() (objects.Object, error) {
|
||||
m := make(map[string]objects.Object)
|
||||
for {
|
||||
// Read opening " of string key or closing }.
|
||||
d.scanWhile(scanSkipSpace)
|
||||
if d.opcode == scanEndObject {
|
||||
// closing } - can only happen on first iteration.
|
||||
break
|
||||
}
|
||||
if d.opcode != scanBeginLiteral {
|
||||
panic(phasePanicMsg)
|
||||
}
|
||||
|
||||
// Read string key.
|
||||
start := d.readIndex()
|
||||
d.scanWhile(scanContinue)
|
||||
item := d.data[start:d.readIndex()]
|
||||
key, ok := unquote(item)
|
||||
if !ok {
|
||||
panic(phasePanicMsg)
|
||||
}
|
||||
|
||||
// Read : before value.
|
||||
if d.opcode == scanSkipSpace {
|
||||
d.scanWhile(scanSkipSpace)
|
||||
}
|
||||
if d.opcode != scanObjectKey {
|
||||
panic(phasePanicMsg)
|
||||
}
|
||||
d.scanWhile(scanSkipSpace)
|
||||
|
||||
// Read value.
|
||||
o, err := d.value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m[key] = o
|
||||
|
||||
// Next token must be , or }.
|
||||
if d.opcode == scanSkipSpace {
|
||||
d.scanWhile(scanSkipSpace)
|
||||
}
|
||||
if d.opcode == scanEndObject {
|
||||
break
|
||||
}
|
||||
if d.opcode != scanObjectValue {
|
||||
panic(phasePanicMsg)
|
||||
}
|
||||
}
|
||||
|
||||
return &objects.Map{Value: m}, nil
|
||||
}
|
||||
|
||||
func (d *decodeState) literal() (objects.Object, error) {
|
||||
// All bytes inside literal return scanContinue op code.
|
||||
start := d.readIndex()
|
||||
d.scanWhile(scanContinue)
|
||||
|
||||
item := d.data[start:d.readIndex()]
|
||||
|
||||
switch c := item[0]; c {
|
||||
case 'n': // null
|
||||
return objects.UndefinedValue, nil
|
||||
|
||||
case 't', 'f': // true, false
|
||||
if c == 't' {
|
||||
return objects.TrueValue, nil
|
||||
}
|
||||
return objects.FalseValue, nil
|
||||
|
||||
case '"': // string
|
||||
s, ok := unquote(item)
|
||||
if !ok {
|
||||
panic(phasePanicMsg)
|
||||
}
|
||||
return &objects.String{Value: s}, nil
|
||||
|
||||
default: // number
|
||||
if c != '-' && (c < '0' || c > '9') {
|
||||
panic(phasePanicMsg)
|
||||
}
|
||||
|
||||
n, _ := strconv.ParseFloat(string(item), 10)
|
||||
return &objects.Float{Value: n}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
|
||||
// or it returns -1.
|
||||
func getu4(s []byte) rune {
|
||||
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
|
||||
return -1
|
||||
}
|
||||
var r rune
|
||||
for _, c := range s[2:6] {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
c = c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
c = c - 'a' + 10
|
||||
case 'A' <= c && c <= 'F':
|
||||
c = c - 'A' + 10
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
r = r*16 + rune(c)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// unquote converts a quoted JSON string literal s into an actual string t.
|
||||
// The rules are different than for Go, so cannot use strconv.Unquote.
|
||||
func unquote(s []byte) (t string, ok bool) {
|
||||
s, ok = unquoteBytes(s)
|
||||
t = string(s)
|
||||
return
|
||||
}
|
||||
|
||||
func unquoteBytes(s []byte) (t []byte, ok bool) {
|
||||
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
|
||||
return
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
|
||||
// Check for unusual characters. If there are none,
|
||||
// then no unquoting is needed, so return a slice of the
|
||||
// original bytes.
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
c := s[r]
|
||||
if c == '\\' || c == '"' || c < ' ' {
|
||||
break
|
||||
}
|
||||
if c < utf8.RuneSelf {
|
||||
r++
|
||||
continue
|
||||
}
|
||||
rr, size := utf8.DecodeRune(s[r:])
|
||||
if rr == utf8.RuneError && size == 1 {
|
||||
break
|
||||
}
|
||||
r += size
|
||||
}
|
||||
if r == len(s) {
|
||||
return s, true
|
||||
}
|
||||
|
||||
b := make([]byte, len(s)+2*utf8.UTFMax)
|
||||
w := copy(b, s[0:r])
|
||||
for r < len(s) {
|
||||
// Out of room? Can only happen if s is full of
|
||||
// malformed UTF-8 and we're replacing each
|
||||
// byte with RuneError.
|
||||
if w >= len(b)-2*utf8.UTFMax {
|
||||
nb := make([]byte, (len(b)+utf8.UTFMax)*2)
|
||||
copy(nb, b[0:w])
|
||||
b = nb
|
||||
}
|
||||
switch c := s[r]; {
|
||||
case c == '\\':
|
||||
r++
|
||||
if r >= len(s) {
|
||||
return
|
||||
}
|
||||
switch s[r] {
|
||||
default:
|
||||
return
|
||||
case '"', '\\', '/', '\'':
|
||||
b[w] = s[r]
|
||||
r++
|
||||
w++
|
||||
case 'b':
|
||||
b[w] = '\b'
|
||||
r++
|
||||
w++
|
||||
case 'f':
|
||||
b[w] = '\f'
|
||||
r++
|
||||
w++
|
||||
case 'n':
|
||||
b[w] = '\n'
|
||||
r++
|
||||
w++
|
||||
case 'r':
|
||||
b[w] = '\r'
|
||||
r++
|
||||
w++
|
||||
case 't':
|
||||
b[w] = '\t'
|
||||
r++
|
||||
w++
|
||||
case 'u':
|
||||
r--
|
||||
rr := getu4(s[r:])
|
||||
if rr < 0 {
|
||||
return
|
||||
}
|
||||
r += 6
|
||||
if utf16.IsSurrogate(rr) {
|
||||
rr1 := getu4(s[r:])
|
||||
if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
|
||||
// A valid pair; consume.
|
||||
r += 6
|
||||
w += utf8.EncodeRune(b[w:], dec)
|
||||
break
|
||||
}
|
||||
// Invalid surrogate; fall back to replacement rune.
|
||||
rr = unicode.ReplacementChar
|
||||
}
|
||||
w += utf8.EncodeRune(b[w:], rr)
|
||||
}
|
||||
|
||||
// Quote, control characters are invalid.
|
||||
case c == '"', c < ' ':
|
||||
return
|
||||
|
||||
// ASCII
|
||||
case c < utf8.RuneSelf:
|
||||
b[w] = c
|
||||
r++
|
||||
w++
|
||||
|
||||
// Coerce to well-formed UTF-8.
|
||||
default:
|
||||
rr, size := utf8.DecodeRune(s[r:])
|
||||
r += size
|
||||
w += utf8.EncodeRune(b[w:], rr)
|
||||
}
|
||||
}
|
||||
return b[0:w], true
|
||||
}
|
147
vendor/github.com/d5/tengo/stdlib/json/encode.go
generated
vendored
Normal file
147
vendor/github.com/d5/tengo/stdlib/json/encode.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
// A modified version of Go's JSON implementation.
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
// Encode returns the JSON encoding of the object.
|
||||
func Encode(o objects.Object) ([]byte, error) {
|
||||
var b []byte
|
||||
|
||||
switch o := o.(type) {
|
||||
case *objects.Array:
|
||||
b = append(b, '[')
|
||||
len1 := len(o.Value) - 1
|
||||
for idx, elem := range o.Value {
|
||||
eb, err := Encode(elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = append(b, eb...)
|
||||
if idx < len1 {
|
||||
b = append(b, ',')
|
||||
}
|
||||
}
|
||||
b = append(b, ']')
|
||||
case *objects.ImmutableArray:
|
||||
b = append(b, '[')
|
||||
len1 := len(o.Value) - 1
|
||||
for idx, elem := range o.Value {
|
||||
eb, err := Encode(elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = append(b, eb...)
|
||||
if idx < len1 {
|
||||
b = append(b, ',')
|
||||
}
|
||||
}
|
||||
b = append(b, ']')
|
||||
case *objects.Map:
|
||||
b = append(b, '{')
|
||||
len1 := len(o.Value) - 1
|
||||
idx := 0
|
||||
for key, value := range o.Value {
|
||||
b = strconv.AppendQuote(b, key)
|
||||
b = append(b, ':')
|
||||
eb, err := Encode(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = append(b, eb...)
|
||||
if idx < len1 {
|
||||
b = append(b, ',')
|
||||
}
|
||||
idx++
|
||||
}
|
||||
b = append(b, '}')
|
||||
case *objects.ImmutableMap:
|
||||
b = append(b, '{')
|
||||
len1 := len(o.Value) - 1
|
||||
idx := 0
|
||||
for key, value := range o.Value {
|
||||
b = strconv.AppendQuote(b, key)
|
||||
b = append(b, ':')
|
||||
eb, err := Encode(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = append(b, eb...)
|
||||
if idx < len1 {
|
||||
b = append(b, ',')
|
||||
}
|
||||
idx++
|
||||
}
|
||||
b = append(b, '}')
|
||||
case *objects.Bool:
|
||||
if o.IsFalsy() {
|
||||
b = strconv.AppendBool(b, false)
|
||||
} else {
|
||||
b = strconv.AppendBool(b, true)
|
||||
}
|
||||
case *objects.Bytes:
|
||||
b = append(b, '"')
|
||||
encodedLen := base64.StdEncoding.EncodedLen(len(o.Value))
|
||||
dst := make([]byte, encodedLen)
|
||||
base64.StdEncoding.Encode(dst, o.Value)
|
||||
b = append(b, dst...)
|
||||
b = append(b, '"')
|
||||
case *objects.Char:
|
||||
b = strconv.AppendInt(b, int64(o.Value), 10)
|
||||
case *objects.Float:
|
||||
var y []byte
|
||||
|
||||
f := o.Value
|
||||
if math.IsInf(f, 0) || math.IsNaN(f) {
|
||||
return nil, errors.New("unsupported float value")
|
||||
}
|
||||
|
||||
// Convert as if by ES6 number to string conversion.
|
||||
// This matches most other JSON generators.
|
||||
abs := math.Abs(f)
|
||||
fmt := byte('f')
|
||||
if abs != 0 {
|
||||
if abs < 1e-6 || abs >= 1e21 {
|
||||
fmt = 'e'
|
||||
}
|
||||
}
|
||||
y = strconv.AppendFloat(y, f, fmt, -1, 64)
|
||||
if fmt == 'e' {
|
||||
// clean up e-09 to e-9
|
||||
n := len(y)
|
||||
if n >= 4 && y[n-4] == 'e' && y[n-3] == '-' && y[n-2] == '0' {
|
||||
y[n-2] = y[n-1]
|
||||
y = y[:n-1]
|
||||
}
|
||||
}
|
||||
|
||||
b = append(b, y...)
|
||||
case *objects.Int:
|
||||
b = strconv.AppendInt(b, o.Value, 10)
|
||||
case *objects.String:
|
||||
b = strconv.AppendQuote(b, o.Value)
|
||||
case *objects.Time:
|
||||
y, err := o.Value.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = append(b, y...)
|
||||
case *objects.Undefined:
|
||||
b = append(b, "null"...)
|
||||
default:
|
||||
// unknown type: ignore
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
559
vendor/github.com/d5/tengo/stdlib/json/scanner.go
generated
vendored
Normal file
559
vendor/github.com/d5/tengo/stdlib/json/scanner.go
generated
vendored
Normal file
@ -0,0 +1,559 @@
|
||||
// A modified version of Go's JSON implementation.
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import "strconv"
|
||||
|
||||
func checkValid(data []byte, scan *scanner) error {
|
||||
scan.reset()
|
||||
for _, c := range data {
|
||||
scan.bytes++
|
||||
if scan.step(scan, c) == scanError {
|
||||
return scan.err
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
return scan.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A SyntaxError is a description of a JSON syntax error.
|
||||
type SyntaxError struct {
|
||||
msg string // description of error
|
||||
Offset int64 // error occurred after reading Offset bytes
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string { return e.msg }
|
||||
|
||||
// A scanner is a JSON scanning state machine.
|
||||
// Callers call scan.reset() and then pass bytes in one at a time
|
||||
// by calling scan.step(&scan, c) for each byte.
|
||||
// The return value, referred to as an opcode, tells the
|
||||
// caller about significant parsing events like beginning
|
||||
// and ending literals, objects, and arrays, so that the
|
||||
// caller can follow along if it wishes.
|
||||
// The return value scanEnd indicates that a single top-level
|
||||
// JSON value has been completed, *before* the byte that
|
||||
// just got passed in. (The indication must be delayed in order
|
||||
// to recognize the end of numbers: is 123 a whole value or
|
||||
// the beginning of 12345e+6?).
|
||||
type scanner struct {
|
||||
// The step is a func to be called to execute the next transition.
|
||||
// Also tried using an integer constant and a single func
|
||||
// with a switch, but using the func directly was 10% faster
|
||||
// on a 64-bit Mac Mini, and it's nicer to read.
|
||||
step func(*scanner, byte) int
|
||||
|
||||
// Reached end of top-level value.
|
||||
endTop bool
|
||||
|
||||
// Stack of what we're in the middle of - array values, object keys, object values.
|
||||
parseState []int
|
||||
|
||||
// Error that happened, if any.
|
||||
err error
|
||||
|
||||
// total bytes consumed, updated by decoder.Decode
|
||||
bytes int64
|
||||
}
|
||||
|
||||
// These values are returned by the state transition functions
|
||||
// assigned to scanner.state and the method scanner.eof.
|
||||
// They give details about the current state of the scan that
|
||||
// callers might be interested to know about.
|
||||
// It is okay to ignore the return value of any particular
|
||||
// call to scanner.state: if one call returns scanError,
|
||||
// every subsequent call will return scanError too.
|
||||
const (
|
||||
// Continue.
|
||||
scanContinue = iota // uninteresting byte
|
||||
scanBeginLiteral // end implied by next result != scanContinue
|
||||
scanBeginObject // begin object
|
||||
scanObjectKey // just finished object key (string)
|
||||
scanObjectValue // just finished non-last object value
|
||||
scanEndObject // end object (implies scanObjectValue if possible)
|
||||
scanBeginArray // begin array
|
||||
scanArrayValue // just finished array value
|
||||
scanEndArray // end array (implies scanArrayValue if possible)
|
||||
scanSkipSpace // space byte; can skip; known to be last "continue" result
|
||||
|
||||
// Stop.
|
||||
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
|
||||
scanError // hit an error, scanner.err.
|
||||
)
|
||||
|
||||
// These values are stored in the parseState stack.
|
||||
// They give the current state of a composite value
|
||||
// being scanned. If the parser is inside a nested value
|
||||
// the parseState describes the nested state, outermost at entry 0.
|
||||
const (
|
||||
parseObjectKey = iota // parsing object key (before colon)
|
||||
parseObjectValue // parsing object value (after colon)
|
||||
parseArrayValue // parsing array value
|
||||
)
|
||||
|
||||
// reset prepares the scanner for use.
|
||||
// It must be called before calling s.step.
|
||||
func (s *scanner) reset() {
|
||||
s.step = stateBeginValue
|
||||
s.parseState = s.parseState[0:0]
|
||||
s.err = nil
|
||||
s.endTop = false
|
||||
}
|
||||
|
||||
// eof tells the scanner that the end of input has been reached.
|
||||
// It returns a scan status just as s.step does.
|
||||
func (s *scanner) eof() int {
|
||||
if s.err != nil {
|
||||
return scanError
|
||||
}
|
||||
if s.endTop {
|
||||
return scanEnd
|
||||
}
|
||||
s.step(s, ' ')
|
||||
if s.endTop {
|
||||
return scanEnd
|
||||
}
|
||||
if s.err == nil {
|
||||
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
|
||||
}
|
||||
return scanError
|
||||
}
|
||||
|
||||
// pushParseState pushes a new parse state p onto the parse stack.
|
||||
func (s *scanner) pushParseState(p int) {
|
||||
s.parseState = append(s.parseState, p)
|
||||
}
|
||||
|
||||
// popParseState pops a parse state (already obtained) off the stack
|
||||
// and updates s.step accordingly.
|
||||
func (s *scanner) popParseState() {
|
||||
n := len(s.parseState) - 1
|
||||
s.parseState = s.parseState[0:n]
|
||||
if n == 0 {
|
||||
s.step = stateEndTop
|
||||
s.endTop = true
|
||||
} else {
|
||||
s.step = stateEndValue
|
||||
}
|
||||
}
|
||||
|
||||
func isSpace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
||||
}
|
||||
|
||||
// stateBeginValueOrEmpty is the state after reading `[`.
|
||||
func stateBeginValueOrEmpty(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == ']' {
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
return stateBeginValue(s, c)
|
||||
}
|
||||
|
||||
// stateBeginValue is the state at the beginning of the input.
|
||||
func stateBeginValue(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
switch c {
|
||||
case '{':
|
||||
s.step = stateBeginStringOrEmpty
|
||||
s.pushParseState(parseObjectKey)
|
||||
return scanBeginObject
|
||||
case '[':
|
||||
s.step = stateBeginValueOrEmpty
|
||||
s.pushParseState(parseArrayValue)
|
||||
return scanBeginArray
|
||||
case '"':
|
||||
s.step = stateInString
|
||||
return scanBeginLiteral
|
||||
case '-':
|
||||
s.step = stateNeg
|
||||
return scanBeginLiteral
|
||||
case '0': // beginning of 0.123
|
||||
s.step = state0
|
||||
return scanBeginLiteral
|
||||
case 't': // beginning of true
|
||||
s.step = stateT
|
||||
return scanBeginLiteral
|
||||
case 'f': // beginning of false
|
||||
s.step = stateF
|
||||
return scanBeginLiteral
|
||||
case 'n': // beginning of null
|
||||
s.step = stateN
|
||||
return scanBeginLiteral
|
||||
}
|
||||
if '1' <= c && c <= '9' { // beginning of 1234.5
|
||||
s.step = state1
|
||||
return scanBeginLiteral
|
||||
}
|
||||
return s.error(c, "looking for beginning of value")
|
||||
}
|
||||
|
||||
// stateBeginStringOrEmpty is the state after reading `{`.
|
||||
func stateBeginStringOrEmpty(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == '}' {
|
||||
n := len(s.parseState)
|
||||
s.parseState[n-1] = parseObjectValue
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
return stateBeginString(s, c)
|
||||
}
|
||||
|
||||
// stateBeginString is the state after reading `{"key": value,`.
|
||||
func stateBeginString(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == '"' {
|
||||
s.step = stateInString
|
||||
return scanBeginLiteral
|
||||
}
|
||||
return s.error(c, "looking for beginning of object key string")
|
||||
}
|
||||
|
||||
// stateEndValue is the state after completing a value,
|
||||
// such as after reading `{}` or `true` or `["x"`.
|
||||
func stateEndValue(s *scanner, c byte) int {
|
||||
n := len(s.parseState)
|
||||
if n == 0 {
|
||||
// Completed top-level before the current byte.
|
||||
s.step = stateEndTop
|
||||
s.endTop = true
|
||||
return stateEndTop(s, c)
|
||||
}
|
||||
if c <= ' ' && isSpace(c) {
|
||||
s.step = stateEndValue
|
||||
return scanSkipSpace
|
||||
}
|
||||
ps := s.parseState[n-1]
|
||||
switch ps {
|
||||
case parseObjectKey:
|
||||
if c == ':' {
|
||||
s.parseState[n-1] = parseObjectValue
|
||||
s.step = stateBeginValue
|
||||
return scanObjectKey
|
||||
}
|
||||
return s.error(c, "after object key")
|
||||
case parseObjectValue:
|
||||
if c == ',' {
|
||||
s.parseState[n-1] = parseObjectKey
|
||||
s.step = stateBeginString
|
||||
return scanObjectValue
|
||||
}
|
||||
if c == '}' {
|
||||
s.popParseState()
|
||||
return scanEndObject
|
||||
}
|
||||
return s.error(c, "after object key:value pair")
|
||||
case parseArrayValue:
|
||||
if c == ',' {
|
||||
s.step = stateBeginValue
|
||||
return scanArrayValue
|
||||
}
|
||||
if c == ']' {
|
||||
s.popParseState()
|
||||
return scanEndArray
|
||||
}
|
||||
return s.error(c, "after array element")
|
||||
}
|
||||
return s.error(c, "")
|
||||
}
|
||||
|
||||
// stateEndTop is the state after finishing the top-level value,
|
||||
// such as after reading `{}` or `[1,2,3]`.
|
||||
// Only space characters should be seen now.
|
||||
func stateEndTop(s *scanner, c byte) int {
|
||||
if !isSpace(c) {
|
||||
// Complain about non-space byte on next call.
|
||||
s.error(c, "after top-level value")
|
||||
}
|
||||
return scanEnd
|
||||
}
|
||||
|
||||
// stateInString is the state after reading `"`.
|
||||
func stateInString(s *scanner, c byte) int {
|
||||
if c == '"' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
if c == '\\' {
|
||||
s.step = stateInStringEsc
|
||||
return scanContinue
|
||||
}
|
||||
if c < 0x20 {
|
||||
return s.error(c, "in string literal")
|
||||
}
|
||||
return scanContinue
|
||||
}
|
||||
|
||||
// stateInStringEsc is the state after reading `"\` during a quoted string.
|
||||
func stateInStringEsc(s *scanner, c byte) int {
|
||||
switch c {
|
||||
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
|
||||
s.step = stateInString
|
||||
return scanContinue
|
||||
case 'u':
|
||||
s.step = stateInStringEscU
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in string escape code")
|
||||
}
|
||||
|
||||
// stateInStringEscU is the state after reading `"\u` during a quoted string.
|
||||
func stateInStringEscU(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU1
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
|
||||
func stateInStringEscU1(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU12
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
|
||||
func stateInStringEscU12(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU123
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
|
||||
func stateInStringEscU123(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInString
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateNeg is the state after reading `-` during a number.
|
||||
func stateNeg(s *scanner, c byte) int {
|
||||
if c == '0' {
|
||||
s.step = state0
|
||||
return scanContinue
|
||||
}
|
||||
if '1' <= c && c <= '9' {
|
||||
s.step = state1
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in numeric literal")
|
||||
}
|
||||
|
||||
// state1 is the state after reading a non-zero integer during a number,
|
||||
// such as after reading `1` or `100` but not `0`.
|
||||
func state1(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = state1
|
||||
return scanContinue
|
||||
}
|
||||
return state0(s, c)
|
||||
}
|
||||
|
||||
// state0 is the state after reading `0` during a number.
|
||||
func state0(s *scanner, c byte) int {
|
||||
if c == '.' {
|
||||
s.step = stateDot
|
||||
return scanContinue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
s.step = stateE
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateDot is the state after reading the integer and decimal point in a number,
|
||||
// such as after reading `1.`.
|
||||
func stateDot(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = stateDot0
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "after decimal point in numeric literal")
|
||||
}
|
||||
|
||||
// stateDot0 is the state after reading the integer, decimal point, and subsequent
|
||||
// digits of a number, such as after reading `3.14`.
|
||||
func stateDot0(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
return scanContinue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
s.step = stateE
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateE is the state after reading the mantissa and e in a number,
|
||||
// such as after reading `314e` or `0.314e`.
|
||||
func stateE(s *scanner, c byte) int {
|
||||
if c == '+' || c == '-' {
|
||||
s.step = stateESign
|
||||
return scanContinue
|
||||
}
|
||||
return stateESign(s, c)
|
||||
}
|
||||
|
||||
// stateESign is the state after reading the mantissa, e, and sign in a number,
|
||||
// such as after reading `314e-` or `0.314e+`.
|
||||
func stateESign(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = stateE0
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in exponent of numeric literal")
|
||||
}
|
||||
|
||||
// stateE0 is the state after reading the mantissa, e, optional sign,
|
||||
// and at least one digit of the exponent in a number,
|
||||
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
|
||||
func stateE0(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateT is the state after reading `t`.
|
||||
func stateT(s *scanner, c byte) int {
|
||||
if c == 'r' {
|
||||
s.step = stateTr
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'r')")
|
||||
}
|
||||
|
||||
// stateTr is the state after reading `tr`.
|
||||
func stateTr(s *scanner, c byte) int {
|
||||
if c == 'u' {
|
||||
s.step = stateTru
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'u')")
|
||||
}
|
||||
|
||||
// stateTru is the state after reading `tru`.
|
||||
func stateTru(s *scanner, c byte) int {
|
||||
if c == 'e' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'e')")
|
||||
}
|
||||
|
||||
// stateF is the state after reading `f`.
|
||||
func stateF(s *scanner, c byte) int {
|
||||
if c == 'a' {
|
||||
s.step = stateFa
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'a')")
|
||||
}
|
||||
|
||||
// stateFa is the state after reading `fa`.
|
||||
func stateFa(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateFal
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateFal is the state after reading `fal`.
|
||||
func stateFal(s *scanner, c byte) int {
|
||||
if c == 's' {
|
||||
s.step = stateFals
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 's')")
|
||||
}
|
||||
|
||||
// stateFals is the state after reading `fals`.
|
||||
func stateFals(s *scanner, c byte) int {
|
||||
if c == 'e' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'e')")
|
||||
}
|
||||
|
||||
// stateN is the state after reading `n`.
|
||||
func stateN(s *scanner, c byte) int {
|
||||
if c == 'u' {
|
||||
s.step = stateNu
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'u')")
|
||||
}
|
||||
|
||||
// stateNu is the state after reading `nu`.
|
||||
func stateNu(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateNul
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateNul is the state after reading `nul`.
|
||||
func stateNul(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateError is the state after reaching a syntax error,
|
||||
// such as after reading `[1}` or `5.1.2`.
|
||||
func stateError(s *scanner, c byte) int {
|
||||
return scanError
|
||||
}
|
||||
|
||||
// error records an error and switches to the error state.
|
||||
func (s *scanner) error(c byte, context string) int {
|
||||
s.step = stateError
|
||||
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
|
||||
return scanError
|
||||
}
|
||||
|
||||
// quoteChar formats c as a quoted character literal
|
||||
func quoteChar(c byte) string {
|
||||
// special cases - different from quoted strings
|
||||
if c == '\'' {
|
||||
return `'\''`
|
||||
}
|
||||
if c == '"' {
|
||||
return `'"'`
|
||||
}
|
||||
|
||||
// use quoted string with different quotation marks
|
||||
s := strconv.Quote(string(c))
|
||||
return "'" + s[1:len(s)-1] + "'"
|
||||
}
|
85
vendor/github.com/d5/tengo/stdlib/os.go
generated
vendored
85
vendor/github.com/d5/tengo/stdlib/os.go
generated
vendored
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
@ -39,14 +40,14 @@ var osModule = map[string]objects.Object{
|
||||
"seek_set": &objects.Int{Value: int64(io.SeekStart)},
|
||||
"seek_cur": &objects.Int{Value: int64(io.SeekCurrent)},
|
||||
"seek_end": &objects.Int{Value: int64(io.SeekEnd)},
|
||||
"args": &objects.UserFunction{Value: osArgs}, // args() => array(string)
|
||||
"args": &objects.UserFunction{Name: "args", Value: osArgs}, // args() => array(string)
|
||||
"chdir": &objects.UserFunction{Name: "chdir", Value: FuncASRE(os.Chdir)}, // chdir(dir string) => error
|
||||
"chmod": osFuncASFmRE(os.Chmod), // chmod(name string, mode int) => error
|
||||
"chmod": osFuncASFmRE("chmod", os.Chmod), // chmod(name string, mode int) => error
|
||||
"chown": &objects.UserFunction{Name: "chown", Value: FuncASIIRE(os.Chown)}, // chown(name string, uid int, gid int) => error
|
||||
"clearenv": &objects.UserFunction{Name: "clearenv", Value: FuncAR(os.Clearenv)}, // clearenv()
|
||||
"environ": &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)}, // environ() => array(string)
|
||||
"exit": &objects.UserFunction{Name: "exit", Value: FuncAIR(os.Exit)}, // exit(code int)
|
||||
"expand_env": &objects.UserFunction{Name: "expand_env", Value: FuncASRS(os.ExpandEnv)}, // expand_env(s string) => string
|
||||
"expand_env": &objects.UserFunction{Name: "expand_env", Value: osExpandEnv}, // expand_env(s string) => string
|
||||
"getegid": &objects.UserFunction{Name: "getegid", Value: FuncARI(os.Getegid)}, // getegid() => int
|
||||
"getenv": &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)}, // getenv(s string) => string
|
||||
"geteuid": &objects.UserFunction{Name: "geteuid", Value: FuncARI(os.Geteuid)}, // geteuid() => int
|
||||
@ -60,9 +61,9 @@ var osModule = map[string]objects.Object{
|
||||
"hostname": &objects.UserFunction{Name: "hostname", Value: FuncARSE(os.Hostname)}, // hostname() => string/error
|
||||
"lchown": &objects.UserFunction{Name: "lchown", Value: FuncASIIRE(os.Lchown)}, // lchown(name string, uid int, gid int) => error
|
||||
"link": &objects.UserFunction{Name: "link", Value: FuncASSRE(os.Link)}, // link(oldname string, newname string) => error
|
||||
"lookup_env": &objects.UserFunction{Value: osLookupEnv}, // lookup_env(key string) => string/false
|
||||
"mkdir": osFuncASFmRE(os.Mkdir), // mkdir(name string, perm int) => error
|
||||
"mkdir_all": osFuncASFmRE(os.MkdirAll), // mkdir_all(name string, perm int) => error
|
||||
"lookup_env": &objects.UserFunction{Name: "lookup_env", Value: osLookupEnv}, // lookup_env(key string) => string/false
|
||||
"mkdir": osFuncASFmRE("mkdir", os.Mkdir), // mkdir(name string, perm int) => error
|
||||
"mkdir_all": osFuncASFmRE("mkdir_all", os.MkdirAll), // mkdir_all(name string, perm int) => error
|
||||
"readlink": &objects.UserFunction{Name: "readlink", Value: FuncASRSE(os.Readlink)}, // readlink(name string) => string/error
|
||||
"remove": &objects.UserFunction{Name: "remove", Value: FuncASRE(os.Remove)}, // remove(name string) => error
|
||||
"remove_all": &objects.UserFunction{Name: "remove_all", Value: FuncASRE(os.RemoveAll)}, // remove_all(name string) => error
|
||||
@ -72,15 +73,15 @@ var osModule = map[string]objects.Object{
|
||||
"temp_dir": &objects.UserFunction{Name: "temp_dir", Value: FuncARS(os.TempDir)}, // temp_dir() => string
|
||||
"truncate": &objects.UserFunction{Name: "truncate", Value: FuncASI64RE(os.Truncate)}, // truncate(name string, size int) => error
|
||||
"unsetenv": &objects.UserFunction{Name: "unsetenv", Value: FuncASRE(os.Unsetenv)}, // unsetenv(key string) => error
|
||||
"create": &objects.UserFunction{Value: osCreate}, // create(name string) => imap(file)/error
|
||||
"open": &objects.UserFunction{Value: osOpen}, // open(name string) => imap(file)/error
|
||||
"open_file": &objects.UserFunction{Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error
|
||||
"find_process": &objects.UserFunction{Value: osFindProcess}, // find_process(pid int) => imap(process)/error
|
||||
"start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
|
||||
"create": &objects.UserFunction{Name: "create", Value: osCreate}, // create(name string) => imap(file)/error
|
||||
"open": &objects.UserFunction{Name: "open", Value: osOpen}, // open(name string) => imap(file)/error
|
||||
"open_file": &objects.UserFunction{Name: "open_file", Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error
|
||||
"find_process": &objects.UserFunction{Name: "find_process", Value: osFindProcess}, // find_process(pid int) => imap(process)/error
|
||||
"start_process": &objects.UserFunction{Name: "start_process", Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
|
||||
"exec_look_path": &objects.UserFunction{Name: "exec_look_path", Value: FuncASRSE(exec.LookPath)}, // exec_look_path(file) => string/error
|
||||
"exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command
|
||||
"stat": &objects.UserFunction{Value: osStat}, // stat(name) => imap(fileinfo)/error
|
||||
"read_file": &objects.UserFunction{Value: osReadFile}, // readfile(name) => array(byte)/error
|
||||
"exec": &objects.UserFunction{Name: "exec", Value: osExec}, // exec(name, args...) => command
|
||||
"stat": &objects.UserFunction{Name: "stat", Value: osStat}, // stat(name) => imap(fileinfo)/error
|
||||
"read_file": &objects.UserFunction{Name: "read_file", Value: osReadFile}, // readfile(name) => array(byte)/error
|
||||
}
|
||||
|
||||
func osReadFile(args ...objects.Object) (ret objects.Object, err error) {
|
||||
@ -102,6 +103,10 @@ func osReadFile(args ...objects.Object) (ret objects.Object, err error) {
|
||||
return wrapError(err), nil
|
||||
}
|
||||
|
||||
if len(bytes) > tengo.MaxBytesLen {
|
||||
return nil, objects.ErrBytesLimit
|
||||
}
|
||||
|
||||
return &objects.Bytes{Value: bytes}, nil
|
||||
}
|
||||
|
||||
@ -233,14 +238,19 @@ func osArgs(args ...objects.Object) (objects.Object, error) {
|
||||
|
||||
arr := &objects.Array{}
|
||||
for _, osArg := range os.Args {
|
||||
if len(osArg) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: osArg})
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction {
|
||||
func osFuncASFmRE(name string, fn func(string, os.FileMode) error) *objects.UserFunction {
|
||||
return &objects.UserFunction{
|
||||
Name: name,
|
||||
Value: func(args ...objects.Object) (objects.Object, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -287,9 +297,54 @@ func osLookupEnv(args ...objects.Object) (objects.Object, error) {
|
||||
return objects.FalseValue, nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: res}, nil
|
||||
}
|
||||
|
||||
func osExpandEnv(args ...objects.Object) (objects.Object, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
s1, ok := objects.ToString(args[0])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
var vlen int
|
||||
var failed bool
|
||||
s := os.Expand(s1, func(k string) string {
|
||||
if failed {
|
||||
return ""
|
||||
}
|
||||
|
||||
v := os.Getenv(k)
|
||||
|
||||
// this does not count the other texts that are not being replaced
|
||||
// but the code checks the final length at the end
|
||||
vlen += len(v)
|
||||
if vlen > tengo.MaxStringLen {
|
||||
failed = true
|
||||
return ""
|
||||
}
|
||||
|
||||
return v
|
||||
})
|
||||
|
||||
if failed || len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
|
||||
func osExec(args ...objects.Object) (objects.Object, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
4
vendor/github.com/d5/tengo/stdlib/os_exec.go
generated
vendored
4
vendor/github.com/d5/tengo/stdlib/os_exec.go
generated
vendored
@ -21,6 +21,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
|
||||
"wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, //
|
||||
// set_path(path string)
|
||||
"set_path": &objects.UserFunction{
|
||||
Name: "set_path",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -42,6 +43,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
|
||||
},
|
||||
// set_dir(dir string)
|
||||
"set_dir": &objects.UserFunction{
|
||||
Name: "set_dir",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -63,6 +65,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
|
||||
},
|
||||
// set_env(env array(string))
|
||||
"set_env": &objects.UserFunction{
|
||||
Name: "set_env",
|
||||
Value: func(args ...objects.Object) (objects.Object, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -96,6 +99,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
|
||||
},
|
||||
// process() => imap(process)
|
||||
"process": &objects.UserFunction{
|
||||
Name: "process",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
3
vendor/github.com/d5/tengo/stdlib/os_file.go
generated
vendored
3
vendor/github.com/d5/tengo/stdlib/os_file.go
generated
vendored
@ -29,6 +29,7 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
|
||||
"read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, //
|
||||
// chmod(mode int) => error
|
||||
"chmod": &objects.UserFunction{
|
||||
Name: "chmod",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -48,6 +49,7 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
|
||||
},
|
||||
// seek(offset int, whence int) => int/error
|
||||
"seek": &objects.UserFunction{
|
||||
Name: "seek",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 2 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -80,6 +82,7 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
|
||||
},
|
||||
// stat() => imap(fileinfo)/error
|
||||
"stat": &objects.UserFunction{
|
||||
Name: "start",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
2
vendor/github.com/d5/tengo/stdlib/os_process.go
generated
vendored
2
vendor/github.com/d5/tengo/stdlib/os_process.go
generated
vendored
@ -24,6 +24,7 @@ func makeOSProcess(proc *os.Process) *objects.ImmutableMap {
|
||||
"kill": &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)}, //
|
||||
"release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, //
|
||||
"signal": &objects.UserFunction{
|
||||
Name: "signal",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -42,6 +43,7 @@ func makeOSProcess(proc *os.Process) *objects.ImmutableMap {
|
||||
},
|
||||
},
|
||||
"wait": &objects.UserFunction{
|
||||
Name: "wait",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
3
vendor/github.com/d5/tengo/stdlib/rand.go
generated
vendored
3
vendor/github.com/d5/tengo/stdlib/rand.go
generated
vendored
@ -15,6 +15,7 @@ var randModule = map[string]objects.Object{
|
||||
"perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(rand.Perm)},
|
||||
"seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)},
|
||||
"read": &objects.UserFunction{
|
||||
Name: "read",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -39,6 +40,7 @@ var randModule = map[string]objects.Object{
|
||||
},
|
||||
},
|
||||
"rand": &objects.UserFunction{
|
||||
Name: "rand",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
@ -71,6 +73,7 @@ func randRand(r *rand.Rand) *objects.ImmutableMap {
|
||||
"perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)},
|
||||
"seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)},
|
||||
"read": &objects.UserFunction{
|
||||
Name: "read",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
8
vendor/github.com/d5/tengo/stdlib/source_modules.go
generated
vendored
Normal file
8
vendor/github.com/d5/tengo/stdlib/source_modules.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// Code generated using gensrcmods.go; DO NOT EDIT.
|
||||
|
||||
package stdlib
|
||||
|
||||
// SourceModules are source type standard library modules.
|
||||
var SourceModules = map[string]string{
|
||||
"enum": "is_enumerable := func(x) {\n return is_array(x) || is_map(x) || is_immutable_array(x) || is_immutable_map(x)\n}\n\nis_array_like := func(x) {\n return is_array(x) || is_immutable_array(x)\n}\n\nexport {\n // all returns true if the given function `fn` evaluates to a truthy value on\n // all of the items in `x`. It returns undefined if `x` is not enumerable.\n all: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if !fn(k, v) { return false }\n }\n\n return true\n },\n // any returns true if the given function `fn` evaluates to a truthy value on\n // any of the items in `x`. It returns undefined if `x` is not enumerable.\n any: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return true }\n }\n\n return false\n },\n // chunk returns an array of elements split into groups the length of size.\n // If `x` can't be split evenly, the final chunk will be the remaining elements.\n // It returns undefined if `x` is not array.\n chunk: func(x, size) {\n if !is_array_like(x) || !size { return undefined }\n\n numElements := len(x)\n if !numElements { return [] }\n\n res := []\n idx := 0\n for idx < numElements {\n res = append(res, x[idx:idx+size])\n idx += size\n }\n\n return res\n },\n // at returns an element at the given index (if `x` is array) or\n // key (if `x` is map). It returns undefined if `x` is not enumerable.\n at: func(x, key) {\n if !is_enumerable(x) { return undefined }\n\n if is_array_like(x) {\n if !is_int(key) { return undefined }\n } else {\n if !is_string(key) { return undefined }\n }\n\n return x[key]\n },\n // each iterates over elements of `x` and invokes `fn` for each element. `fn` is\n // invoked with two arguments: `key` and `value`. `key` is an int index\n // if `x` is array. `key` is a string key if `x` is map. It does not iterate\n // and returns undefined if `x` is not enumerable.\n each: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n fn(k, v)\n }\n },\n // filter iterates over elements of `x`, returning an array of all elements `fn`\n // returns truthy for. `fn` is invoked with two arguments: `key` and `value`.\n // `key` is an int index if `x` is array. `key` is a string key if `x` is map.\n // It returns undefined if `x` is not enumerable.\n filter: func(x, fn) {\n if !is_array_like(x) { return undefined }\n\n dst := []\n for k, v in x {\n if fn(k, v) { dst = append(dst, v) }\n }\n\n return dst\n },\n // find iterates over elements of `x`, returning value of the first element `fn`\n // returns truthy for. `fn` is invoked with two arguments: `key` and `value`.\n // `key` is an int index if `x` is array. `key` is a string key if `x` is map.\n // It returns undefined if `x` is not enumerable.\n find: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return v }\n }\n },\n // find_key iterates over elements of `x`, returning key or index of the first\n // element `fn` returns truthy for. `fn` is invoked with two arguments: `key`\n // and `value`. `key` is an int index if `x` is array. `key` is a string key if\n // `x` is map. It returns undefined if `x` is not enumerable.\n find_key: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return k }\n }\n },\n // map creates an array of values by running each element in `x` through `fn`.\n // `fn` is invoked with two arguments: `key` and `value`. `key` is an int index\n // if `x` is array. `key` is a string key if `x` is map. It returns undefined\n // if `x` is not enumerable.\n map: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n dst := []\n for k, v in x {\n dst = append(dst, fn(k, v))\n }\n\n return dst\n },\n // key returns the first argument.\n key: func(k, _) { return k },\n // value returns the second argument.\n value: func(_, v) { return v }\n}\n",
|
||||
}
|
128
vendor/github.com/d5/tengo/stdlib/srcmod_enum.tengo
generated
vendored
Normal file
128
vendor/github.com/d5/tengo/stdlib/srcmod_enum.tengo
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
is_enumerable := func(x) {
|
||||
return is_array(x) || is_map(x) || is_immutable_array(x) || is_immutable_map(x)
|
||||
}
|
||||
|
||||
is_array_like := func(x) {
|
||||
return is_array(x) || is_immutable_array(x)
|
||||
}
|
||||
|
||||
export {
|
||||
// all returns true if the given function `fn` evaluates to a truthy value on
|
||||
// all of the items in `x`. It returns undefined if `x` is not enumerable.
|
||||
all: func(x, fn) {
|
||||
if !is_enumerable(x) { return undefined }
|
||||
|
||||
for k, v in x {
|
||||
if !fn(k, v) { return false }
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
// any returns true if the given function `fn` evaluates to a truthy value on
|
||||
// any of the items in `x`. It returns undefined if `x` is not enumerable.
|
||||
any: func(x, fn) {
|
||||
if !is_enumerable(x) { return undefined }
|
||||
|
||||
for k, v in x {
|
||||
if fn(k, v) { return true }
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
// chunk returns an array of elements split into groups the length of size.
|
||||
// If `x` can't be split evenly, the final chunk will be the remaining elements.
|
||||
// It returns undefined if `x` is not array.
|
||||
chunk: func(x, size) {
|
||||
if !is_array_like(x) || !size { return undefined }
|
||||
|
||||
numElements := len(x)
|
||||
if !numElements { return [] }
|
||||
|
||||
res := []
|
||||
idx := 0
|
||||
for idx < numElements {
|
||||
res = append(res, x[idx:idx+size])
|
||||
idx += size
|
||||
}
|
||||
|
||||
return res
|
||||
},
|
||||
// at returns an element at the given index (if `x` is array) or
|
||||
// key (if `x` is map). It returns undefined if `x` is not enumerable.
|
||||
at: func(x, key) {
|
||||
if !is_enumerable(x) { return undefined }
|
||||
|
||||
if is_array_like(x) {
|
||||
if !is_int(key) { return undefined }
|
||||
} else {
|
||||
if !is_string(key) { return undefined }
|
||||
}
|
||||
|
||||
return x[key]
|
||||
},
|
||||
// each iterates over elements of `x` and invokes `fn` for each element. `fn` is
|
||||
// invoked with two arguments: `key` and `value`. `key` is an int index
|
||||
// if `x` is array. `key` is a string key if `x` is map. It does not iterate
|
||||
// and returns undefined if `x` is not enumerable.
|
||||
each: func(x, fn) {
|
||||
if !is_enumerable(x) { return undefined }
|
||||
|
||||
for k, v in x {
|
||||
fn(k, v)
|
||||
}
|
||||
},
|
||||
// filter iterates over elements of `x`, returning an array of all elements `fn`
|
||||
// returns truthy for. `fn` is invoked with two arguments: `key` and `value`.
|
||||
// `key` is an int index if `x` is array. `key` is a string key if `x` is map.
|
||||
// It returns undefined if `x` is not enumerable.
|
||||
filter: func(x, fn) {
|
||||
if !is_array_like(x) { return undefined }
|
||||
|
||||
dst := []
|
||||
for k, v in x {
|
||||
if fn(k, v) { dst = append(dst, v) }
|
||||
}
|
||||
|
||||
return dst
|
||||
},
|
||||
// find iterates over elements of `x`, returning value of the first element `fn`
|
||||
// returns truthy for. `fn` is invoked with two arguments: `key` and `value`.
|
||||
// `key` is an int index if `x` is array. `key` is a string key if `x` is map.
|
||||
// It returns undefined if `x` is not enumerable.
|
||||
find: func(x, fn) {
|
||||
if !is_enumerable(x) { return undefined }
|
||||
|
||||
for k, v in x {
|
||||
if fn(k, v) { return v }
|
||||
}
|
||||
},
|
||||
// find_key iterates over elements of `x`, returning key or index of the first
|
||||
// element `fn` returns truthy for. `fn` is invoked with two arguments: `key`
|
||||
// and `value`. `key` is an int index if `x` is array. `key` is a string key if
|
||||
// `x` is map. It returns undefined if `x` is not enumerable.
|
||||
find_key: func(x, fn) {
|
||||
if !is_enumerable(x) { return undefined }
|
||||
|
||||
for k, v in x {
|
||||
if fn(k, v) { return k }
|
||||
}
|
||||
},
|
||||
// map creates an array of values by running each element in `x` through `fn`.
|
||||
// `fn` is invoked with two arguments: `key` and `value`. `key` is an int index
|
||||
// if `x` is array. `key` is a string key if `x` is map. It returns undefined
|
||||
// if `x` is not enumerable.
|
||||
map: func(x, fn) {
|
||||
if !is_enumerable(x) { return undefined }
|
||||
|
||||
dst := []
|
||||
for k, v in x {
|
||||
dst = append(dst, fn(k, v))
|
||||
}
|
||||
|
||||
return dst
|
||||
},
|
||||
// key returns the first argument.
|
||||
key: func(k, _) { return k },
|
||||
// value returns the second argument.
|
||||
value: func(_, v) { return v }
|
||||
}
|
36
vendor/github.com/d5/tengo/stdlib/stdlib.go
generated
vendored
36
vendor/github.com/d5/tengo/stdlib/stdlib.go
generated
vendored
@ -1,16 +1,34 @@
|
||||
package stdlib
|
||||
|
||||
//go:generate go run gensrcmods.go
|
||||
|
||||
import "github.com/d5/tengo/objects"
|
||||
|
||||
// Modules contain the standard modules.
|
||||
var Modules = map[string]*objects.Object{
|
||||
"math": objectPtr(&objects.ImmutableMap{Value: mathModule}),
|
||||
"os": objectPtr(&objects.ImmutableMap{Value: osModule}),
|
||||
"text": objectPtr(&objects.ImmutableMap{Value: textModule}),
|
||||
"times": objectPtr(&objects.ImmutableMap{Value: timesModule}),
|
||||
"rand": objectPtr(&objects.ImmutableMap{Value: randModule}),
|
||||
// AllModuleNames returns a list of all default module names.
|
||||
func AllModuleNames() []string {
|
||||
var names []string
|
||||
for name := range BuiltinModules {
|
||||
names = append(names, name)
|
||||
}
|
||||
for name := range SourceModules {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func objectPtr(o objects.Object) *objects.Object {
|
||||
return &o
|
||||
// GetModuleMap returns the module map that includes all modules
|
||||
// for the given module names.
|
||||
func GetModuleMap(names ...string) *objects.ModuleMap {
|
||||
modules := objects.NewModuleMap()
|
||||
|
||||
for _, name := range names {
|
||||
if mod := BuiltinModules[name]; mod != nil {
|
||||
modules.AddBuiltinModule(name, mod)
|
||||
}
|
||||
if mod := SourceModules[name]; mod != "" {
|
||||
modules.AddSourceModule(name, []byte(mod))
|
||||
}
|
||||
}
|
||||
|
||||
return modules
|
||||
}
|
||||
|
184
vendor/github.com/d5/tengo/stdlib/text.go
generated
vendored
184
vendor/github.com/d5/tengo/stdlib/text.go
generated
vendored
@ -1,19 +1,22 @@
|
||||
package stdlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
var textModule = map[string]objects.Object{
|
||||
"re_match": &objects.UserFunction{Value: textREMatch}, // re_match(pattern, text) => bool/error
|
||||
"re_find": &objects.UserFunction{Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined
|
||||
"re_replace": &objects.UserFunction{Value: textREReplace}, // re_replace(pattern, text, repl) => string/error
|
||||
"re_split": &objects.UserFunction{Value: textRESplit}, // re_split(pattern, text, count) => [string]/error
|
||||
"re_compile": &objects.UserFunction{Value: textRECompile}, // re_compile(pattern) => Regexp/error
|
||||
"re_match": &objects.UserFunction{Name: "re_match", Value: textREMatch}, // re_match(pattern, text) => bool/error
|
||||
"re_find": &objects.UserFunction{Name: "re_find", Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined
|
||||
"re_replace": &objects.UserFunction{Name: "re_replace", Value: textREReplace}, // re_replace(pattern, text, repl) => string/error
|
||||
"re_split": &objects.UserFunction{Name: "re_split", Value: textRESplit}, // re_split(pattern, text, count) => [string]/error
|
||||
"re_compile": &objects.UserFunction{Name: "re_compile", Value: textRECompile}, // re_compile(pattern) => Regexp/error
|
||||
"compare": &objects.UserFunction{Name: "compare", Value: FuncASSRI(strings.Compare)}, // compare(a, b) => int
|
||||
"contains": &objects.UserFunction{Name: "contains", Value: FuncASSRB(strings.Contains)}, // contains(s, substr) => bool
|
||||
"contains_any": &objects.UserFunction{Name: "contains_any", Value: FuncASSRB(strings.ContainsAny)}, // contains_any(s, chars) => bool
|
||||
@ -24,11 +27,11 @@ var textModule = map[string]objects.Object{
|
||||
"has_suffix": &objects.UserFunction{Name: "has_suffix", Value: FuncASSRB(strings.HasSuffix)}, // has_suffix(s, suffix) => bool
|
||||
"index": &objects.UserFunction{Name: "index", Value: FuncASSRI(strings.Index)}, // index(s, substr) => int
|
||||
"index_any": &objects.UserFunction{Name: "index_any", Value: FuncASSRI(strings.IndexAny)}, // index_any(s, chars) => int
|
||||
"join": &objects.UserFunction{Name: "join", Value: FuncASsSRS(strings.Join)}, // join(arr, sep) => string
|
||||
"join": &objects.UserFunction{Name: "join", Value: textJoin}, // join(arr, sep) => string
|
||||
"last_index": &objects.UserFunction{Name: "last_index", Value: FuncASSRI(strings.LastIndex)}, // last_index(s, substr) => int
|
||||
"last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int
|
||||
"repeat": &objects.UserFunction{Name: "repeat", Value: FuncASIRS(strings.Repeat)}, // repeat(s, count) => string
|
||||
"replace": &objects.UserFunction{Value: textReplace}, // replace(s, old, new, n) => string
|
||||
"repeat": &objects.UserFunction{Name: "repeat", Value: textRepeat}, // repeat(s, count) => string
|
||||
"replace": &objects.UserFunction{Name: "replace", Value: textReplace}, // replace(s, old, new, n) => string
|
||||
"split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string]
|
||||
"split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string]
|
||||
"split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string]
|
||||
@ -43,13 +46,13 @@ var textModule = map[string]objects.Object{
|
||||
"trim_space": &objects.UserFunction{Name: "trim_space", Value: FuncASRS(strings.TrimSpace)}, // trim_space(s) => string
|
||||
"trim_suffix": &objects.UserFunction{Name: "trim_suffix", Value: FuncASSRS(strings.TrimSuffix)}, // trim_suffix(s, suffix) => string
|
||||
"atoi": &objects.UserFunction{Name: "atoi", Value: FuncASRIE(strconv.Atoi)}, // atoi(str) => int/error
|
||||
"format_bool": &objects.UserFunction{Value: textFormatBool}, // format_bool(b) => string
|
||||
"format_float": &objects.UserFunction{Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string
|
||||
"format_int": &objects.UserFunction{Value: textFormatInt}, // format_int(i, base) => string
|
||||
"format_bool": &objects.UserFunction{Name: "format_bool", Value: textFormatBool}, // format_bool(b) => string
|
||||
"format_float": &objects.UserFunction{Name: "format_float", Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string
|
||||
"format_int": &objects.UserFunction{Name: "format_int", Value: textFormatInt}, // format_int(i, base) => string
|
||||
"itoa": &objects.UserFunction{Name: "itoa", Value: FuncAIRS(strconv.Itoa)}, // itoa(i) => string
|
||||
"parse_bool": &objects.UserFunction{Value: textParseBool}, // parse_bool(str) => bool/error
|
||||
"parse_float": &objects.UserFunction{Value: textParseFloat}, // parse_float(str, bits) => float/error
|
||||
"parse_int": &objects.UserFunction{Value: textParseInt}, // parse_int(str, base, bits) => int/error
|
||||
"parse_bool": &objects.UserFunction{Name: "parse_bool", Value: textParseBool}, // parse_bool(str) => bool/error
|
||||
"parse_float": &objects.UserFunction{Name: "parse_float", Value: textParseFloat}, // parse_float(str, bits) => float/error
|
||||
"parse_int": &objects.UserFunction{Name: "parse_int", Value: textParseInt}, // parse_int(str, base, bits) => int/error
|
||||
"quote": &objects.UserFunction{Name: "quote", Value: FuncASRS(strconv.Quote)}, // quote(str) => string
|
||||
"unquote": &objects.UserFunction{Name: "unquote", Value: FuncASRSE(strconv.Unquote)}, // unquote(str) => string/error
|
||||
}
|
||||
@ -223,7 +226,12 @@ func textREReplace(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if err != nil {
|
||||
ret = wrapError(err)
|
||||
} else {
|
||||
ret = &objects.String{Value: re.ReplaceAllString(s2, s3)}
|
||||
s, ok := doTextRegexpReplace(re, s2, s3)
|
||||
if !ok {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: s}
|
||||
}
|
||||
|
||||
return
|
||||
@ -357,11 +365,106 @@ func textReplace(args ...objects.Object) (ret objects.Object, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)}
|
||||
s, ok := doTextReplace(s1, s2, s3, i4)
|
||||
if !ok {
|
||||
err = objects.ErrStringLimit
|
||||
return
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: s}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func textRepeat(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 2 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
s1, ok := objects.ToString(args[0])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
i2, ok := objects.ToInt(args[1])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "second",
|
||||
Expected: "int(compatible)",
|
||||
Found: args[1].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
if len(s1)*i2 > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: strings.Repeat(s1, i2)}, nil
|
||||
}
|
||||
|
||||
func textJoin(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 2 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
var slen int
|
||||
var ss1 []string
|
||||
switch arg0 := args[0].(type) {
|
||||
case *objects.Array:
|
||||
for idx, a := range arg0.Value {
|
||||
as, ok := objects.ToString(a)
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: fmt.Sprintf("first[%d]", idx),
|
||||
Expected: "string(compatible)",
|
||||
Found: a.TypeName(),
|
||||
}
|
||||
}
|
||||
slen += len(as)
|
||||
ss1 = append(ss1, as)
|
||||
}
|
||||
case *objects.ImmutableArray:
|
||||
for idx, a := range arg0.Value {
|
||||
as, ok := objects.ToString(a)
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: fmt.Sprintf("first[%d]", idx),
|
||||
Expected: "string(compatible)",
|
||||
Found: a.TypeName(),
|
||||
}
|
||||
}
|
||||
slen += len(as)
|
||||
ss1 = append(ss1, as)
|
||||
}
|
||||
default:
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "array",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
s2, ok := objects.ToString(args[1])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "second",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[1].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
// make sure output length does not exceed the limit
|
||||
if slen+len(s2)*(len(ss1)-1) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: strings.Join(ss1, s2)}, nil
|
||||
}
|
||||
|
||||
func textFormatBool(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
err = objects.ErrWrongNumArguments
|
||||
@ -583,3 +686,52 @@ func textParseInt(args ...objects.Object) (ret objects.Object, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Modified implementation of strings.Replace
|
||||
// to limit the maximum length of output string.
|
||||
func doTextReplace(s, old, new string, n int) (string, bool) {
|
||||
if old == new || n == 0 {
|
||||
return s, true // avoid allocation
|
||||
}
|
||||
|
||||
// Compute number of replacements.
|
||||
if m := strings.Count(s, old); m == 0 {
|
||||
return s, true // avoid allocation
|
||||
} else if n < 0 || m < n {
|
||||
n = m
|
||||
}
|
||||
|
||||
// Apply replacements to buffer.
|
||||
t := make([]byte, len(s)+n*(len(new)-len(old)))
|
||||
w := 0
|
||||
start := 0
|
||||
for i := 0; i < n; i++ {
|
||||
j := start
|
||||
if len(old) == 0 {
|
||||
if i > 0 {
|
||||
_, wid := utf8.DecodeRuneInString(s[start:])
|
||||
j += wid
|
||||
}
|
||||
} else {
|
||||
j += strings.Index(s[start:], old)
|
||||
}
|
||||
|
||||
ssj := s[start:j]
|
||||
if w+len(ssj)+len(new) > tengo.MaxStringLen {
|
||||
return "", false
|
||||
}
|
||||
|
||||
w += copy(t[w:], ssj)
|
||||
w += copy(t[w:], new)
|
||||
start = j + len(old)
|
||||
}
|
||||
|
||||
ss := s[start:]
|
||||
if w+len(ss) > tengo.MaxStringLen {
|
||||
return "", false
|
||||
}
|
||||
|
||||
w += copy(t[w:], ss)
|
||||
|
||||
return string(t[0:w]), true
|
||||
}
|
||||
|
35
vendor/github.com/d5/tengo/stdlib/text_regexp.go
generated
vendored
35
vendor/github.com/d5/tengo/stdlib/text_regexp.go
generated
vendored
@ -3,6 +3,7 @@ package stdlib
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
@ -141,7 +142,12 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
|
||||
return
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: re.ReplaceAllString(s1, s2)}
|
||||
s, ok := doTextRegexpReplace(re, s1, s2)
|
||||
if !ok {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: s}
|
||||
|
||||
return
|
||||
},
|
||||
@ -193,3 +199,30 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Size-limit checking implementation of regexp.ReplaceAllString.
|
||||
func doTextRegexpReplace(re *regexp.Regexp, src, repl string) (string, bool) {
|
||||
idx := 0
|
||||
out := ""
|
||||
|
||||
for _, m := range re.FindAllStringSubmatchIndex(src, -1) {
|
||||
var exp []byte
|
||||
exp = re.ExpandString(exp, repl, src, m)
|
||||
|
||||
if len(out)+m[0]-idx+len(exp) > tengo.MaxStringLen {
|
||||
return "", false
|
||||
}
|
||||
|
||||
out += src[idx:m[0]] + string(exp)
|
||||
idx = m[1]
|
||||
}
|
||||
if idx < len(src) {
|
||||
if len(out)+len(src)-idx > tengo.MaxStringLen {
|
||||
return "", false
|
||||
}
|
||||
|
||||
out += src[idx:]
|
||||
}
|
||||
|
||||
return string(out), true
|
||||
}
|
||||
|
9
vendor/github.com/d5/tengo/stdlib/times.go
generated
vendored
9
vendor/github.com/d5/tengo/stdlib/times.go
generated
vendored
@ -3,6 +3,7 @@ package stdlib
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
@ -867,7 +868,13 @@ func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: t1.Format(s2)}
|
||||
s := t1.Format(s2)
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: s}
|
||||
|
||||
return
|
||||
}
|
||||
|
11
vendor/github.com/d5/tengo/tengo.go
generated
vendored
Normal file
11
vendor/github.com/d5/tengo/tengo.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package tengo
|
||||
|
||||
var (
|
||||
// MaxStringLen is the maximum byte-length for string value.
|
||||
// Note this limit applies to all compiler/VM instances in the process.
|
||||
MaxStringLen = 2147483647
|
||||
|
||||
// MaxBytesLen is the maximum length for bytes value.
|
||||
// Note this limit applies to all compiler/VM instances in the process.
|
||||
MaxBytesLen = 2147483647
|
||||
)
|
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@ -17,14 +17,16 @@ github.com/Philipp15b/go-steam/rwu
|
||||
github.com/Philipp15b/go-steam/socialcache
|
||||
# github.com/bwmarrin/discordgo v0.19.0
|
||||
github.com/bwmarrin/discordgo
|
||||
# github.com/d5/tengo v1.9.2
|
||||
# github.com/d5/tengo v1.20.0
|
||||
github.com/d5/tengo/script
|
||||
github.com/d5/tengo/stdlib
|
||||
github.com/d5/tengo/compiler
|
||||
github.com/d5/tengo/compiler/parser
|
||||
github.com/d5/tengo/compiler/source
|
||||
github.com/d5/tengo/objects
|
||||
github.com/d5/tengo/runtime
|
||||
github.com/d5/tengo/stdlib
|
||||
github.com/d5/tengo
|
||||
github.com/d5/tengo/stdlib/json
|
||||
github.com/d5/tengo/compiler/ast
|
||||
github.com/d5/tengo/compiler/token
|
||||
github.com/d5/tengo/compiler/scanner
|
||||
|
Reference in New Issue
Block a user