forked from lug/matterbridge
		
	Compare commits
	
		
			46 Commits
		
	
	
		
			v1.14.0-rc
			...
			v1.14.4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6d8cccccf1 | ||
|   | dd63438e7b | ||
|   | 5f5d6c6e8a | ||
|   | aefa8a9341 | ||
|   | 1c3e764d57 | ||
|   | 0b03076a9d | ||
|   | 1e6a2bc8f7 | ||
|   | 82fe80e52f | ||
|   | db012bd9b7 | ||
|   | dd2374158b | ||
|   | 6693157258 | ||
|   | 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 |   # concurrency: 4 | ||||||
|  |  | ||||||
|   # timeout for analysis, e.g. 30s, 5m, default is 1m |   # 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 |   # exit code when at least one issue was found, default is 1 | ||||||
|   issues-exit-code: 1 |   issues-exit-code: 1 | ||||||
| @@ -105,10 +105,6 @@ linters-settings: | |||||||
|     # with golangci-lint call it on a directory with the changed file. |     # with golangci-lint call it on a directory with the changed file. | ||||||
|     check-exported: false |     check-exported: false | ||||||
|   unparam: |   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. |     # 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: |     # 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 |     # 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 |       - regexpMust | ||||||
|       - singleCaseSwitch |       - singleCaseSwitch | ||||||
|       - sloppyLen |       - sloppyLen | ||||||
|       - sloppyReassign |  | ||||||
|       - switchTrue |       - switchTrue | ||||||
|       - typeSwitchVar |       - typeSwitchVar | ||||||
|       - typeUnparen |       - typeUnparen | ||||||
|   | |||||||
							
								
								
									
										76
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,63 +1,55 @@ | |||||||
| language: go | language: go | ||||||
| go: |  | ||||||
|     - 1.11.x |  | ||||||
| go_import_path: github.com/42wim/matterbridge | 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 | install: true | ||||||
|  |  | ||||||
| git: | git: | ||||||
|   depth: 200 |   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: | notifications: | ||||||
|       email: false |   email: false | ||||||
|  |  | ||||||
| before_script: | branches: | ||||||
|   # Get version info from tags. |   only: | ||||||
|   - MY_VERSION="$(git describe --tags)" |   - master | ||||||
|   # 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 |  | ||||||
|  |  | ||||||
| script: | jobs: | ||||||
|   # Run the linter. |   include: | ||||||
|   - golangci-lint run |   - stage: lint | ||||||
|   # Run all the tests with the race detector and generate coverage. |     # Run linting in one Go environment only. | ||||||
|   - go test -v -race -coverprofile c.out ./... |     script: ./ci/lint.sh | ||||||
|   # Run the build script to generate the necessary binaries and images. |     go: 1.12.x | ||||||
|   - /bin/bash ci/bintray.sh |     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: | before_deploy: /bin/bash ci/bintray.sh | ||||||
|   # Upload test coverage to CodeClimate. |  | ||||||
|   - ./cc-test-reporter after-build --exit-code ${TRAVIS_TEST_RESULT} |  | ||||||
|  |  | ||||||
| deploy: | deploy: | ||||||
|   on: |  | ||||||
|      all_branches: true |  | ||||||
|   provider: bintray |  | ||||||
|   on: |   on: | ||||||
|     all_branches: true |     all_branches: true | ||||||
|  |     condition: $BINDEPLOY = 1 | ||||||
|  |   provider: bintray | ||||||
|   edge: |   edge: | ||||||
|     branch: v1.8.47 |     branch: v1.8.47 | ||||||
|   file: ci/deploy.json |   file: ci/deploy.json | ||||||
|   user: 42wim |   user: 42wim | ||||||
|   on: |  | ||||||
|      all_branches: true |  | ||||||
|   key: |   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> | **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 | ### Table of Contents | ||||||
|  * [Features](https://github.com/42wim/matterbridge/wiki/Features) |  * [Features](https://github.com/42wim/matterbridge/wiki/Features) | ||||||
|    * [Natively supported](#natively-supported) |    * [Natively supported](#natively-supported) | ||||||
| @@ -88,6 +94,7 @@ | |||||||
| * [Minecraft](https://github.com/elytra/MatterLink) | * [Minecraft](https://github.com/elytra/MatterLink) | ||||||
| * [Reddit](https://github.com/bonehurtingjuice/mattereddit) | * [Reddit](https://github.com/bonehurtingjuice/mattereddit) | ||||||
| * [Facebook messenger](https://github.com/VictorNine/fbridge) | * [Facebook messenger](https://github.com/VictorNine/fbridge) | ||||||
|  | * [Discourse](https://github.com/DeclanHoare/matterbabble) | ||||||
|  |  | ||||||
| ### API | ### API | ||||||
| The API is very basic at the moment.    | 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) | * [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot) | ||||||
| * [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support) | * [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support) | ||||||
| * [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support) | * [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support) | ||||||
|  | * [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support) | ||||||
|  |  | ||||||
| ## Chat with us | ## Chat with us | ||||||
|  |  | ||||||
| @@ -121,7 +129,7 @@ See https://github.com/42wim/matterbridge/wiki | |||||||
|  |  | ||||||
| ## Installing | ## Installing | ||||||
| ### Binaries | ### 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/) | * Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/) | ||||||
|  |  | ||||||
| ### Packages | ### 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 | * [mattermost-plugin](https://github.com/matterbridge/mattermost-plugin) - Run matterbridge as a plugin in mattermost | ||||||
| * [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot) | * [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot) | ||||||
| * [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support) | * [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 | ## Articles | ||||||
| * [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3) | * [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/ | * https://daniele.tech/2019/02/how-to-use-matterbridge-to-connect-2-different-slack-workspaces/ | ||||||
|  |  | ||||||
| ## Thanks | ## 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: | Matterbridge wouldn't exist without these libraries: | ||||||
| * discord - https://github.com/bwmarrin/discordgo | * discord - https://github.com/bwmarrin/discordgo | ||||||
|   | |||||||
| @@ -178,7 +178,10 @@ func ClipMessage(text string, length int) string { | |||||||
|  |  | ||||||
| func ParseMarkdown(input string) string { | func ParseMarkdown(input string) string { | ||||||
| 	md := markdown.New(markdown.XHTMLOutput(true), markdown.Breaks(true)) | 	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) | // 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 | 			return | ||||||
| 		} | 		} | ||||||
| 		b.Log.Debugf("<= Sending JOIN_LEAVE event from %s to gateway", b.Account) | 		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} | 		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.Log.Debugf("<= Message is %#v", msg) | ||||||
| 		b.Remote <- msg | 		b.Remote <- msg | ||||||
| @@ -160,7 +156,10 @@ func (b *Birc) handleOtherAuth(client *girc.Client, event girc.Event) { | |||||||
| 	b.handleNickServ() | 	b.handleNickServ() | ||||||
| 	b.handleRunCommands() | 	b.handleRunCommands() | ||||||
| 	// we are now fully connected | 	// we are now fully connected | ||||||
| 	b.connected <- nil | 	// only send on first connection | ||||||
|  | 	if b.FirstConnection { | ||||||
|  | 		b.connected <- nil | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { | func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { | ||||||
|   | |||||||
| @@ -137,6 +137,7 @@ func (b *Birc) Send(msg config.Message) (string, error) { | |||||||
| 	// we can be in between reconnects #385 | 	// we can be in between reconnects #385 | ||||||
| 	if !b.i.IsConnected() { | 	if !b.i.IsConnected() { | ||||||
| 		b.Log.Error("Not connected to server, dropping message") | 		b.Log.Error("Not connected to server, dropping message") | ||||||
|  | 		return "", nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Execute a command | 	// Execute a command | ||||||
| @@ -231,7 +232,7 @@ func (b *Birc) getClient() (*girc.Client, error) { | |||||||
| 	// fix strict user handling of girc | 	// fix strict user handling of girc | ||||||
| 	user := b.GetString("Nick") | 	user := b.GetString("Nick") | ||||||
| 	for !girc.IsValidUser(user) { | 	for !girc.IsValidUser(user) { | ||||||
| 		if len(user) == 1 { | 		if len(user) == 1 || len(user) == 0 { | ||||||
| 			user = "matterbridge" | 			user = "matterbridge" | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -186,6 +186,12 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool { | |||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Ignore non-post messages | ||||||
|  | 	if message.Post == nil { | ||||||
|  | 		b.Log.Debugf("ignoring nil message.Post: %#v", message) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Ignore messages sent from matterbridge | 	// Ignore messages sent from matterbridge | ||||||
| 	if message.Post.Props != nil { | 	if message.Post.Props != nil { | ||||||
| 		if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok { | 		if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok { | ||||||
|   | |||||||
| @@ -121,6 +121,12 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) { | |||||||
| 		return msg.ID, b.mc.DeleteMessage(msg.ID) | 		return msg.ID, b.mc.DeleteMessage(msg.ID) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Handle prefix hint for unthreaded messages. | ||||||
|  | 	if msg.ParentID == "msg-parent-not-found" { | ||||||
|  | 		msg.ParentID = "" | ||||||
|  | 		msg.Text = fmt.Sprintf("[thread]: %s", msg.Text) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Upload a file if it exists | 	// Upload a file if it exists | ||||||
| 	if msg.Extra != nil { | 	if msg.Extra != nil { | ||||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||||
|   | |||||||
| @@ -95,7 +95,7 @@ func (b *Brocketchat) getChannelID(name string) string { | |||||||
| 	b.RLock() | 	b.RLock() | ||||||
| 	defer b.RUnlock() | 	defer b.RUnlock() | ||||||
| 	for k, v := range b.channelMap { | 	for k, v := range b.channelMap { | ||||||
| 		if v == name { | 		if v == name || v == "#"+name { | ||||||
| 			return k | 			return k | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package brocketchat | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
|  |  | ||||||
| 	"github.com/42wim/matterbridge/bridge" | 	"github.com/42wim/matterbridge/bridge" | ||||||
| @@ -85,14 +86,14 @@ func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error { | |||||||
| 	if b.c == nil { | 	if b.c == nil { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	id, err := b.c.GetChannelId(channel.Name) | 	id, err := b.c.GetChannelId(strings.TrimPrefix(channel.Name, "#")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	b.Lock() | 	b.Lock() | ||||||
| 	b.channelMap[id] = channel.Name | 	b.channelMap[id] = channel.Name | ||||||
| 	b.Unlock() | 	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 { | 	if err := b.c.JoinChannel(id); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -103,6 +104,8 @@ func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Brocketchat) Send(msg config.Message) (string, 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} | 	channel := &models.Channel{ID: b.getChannelID(msg.Channel), Name: msg.Channel} | ||||||
|  |  | ||||||
| 	// Delete message | 	// Delete message | ||||||
| @@ -131,6 +134,8 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) { | |||||||
| 	// Upload a file if it exists | 	// Upload a file if it exists | ||||||
| 	if msg.Extra != nil { | 	if msg.Extra != nil { | ||||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||||
|  | 			// strip the # if people has set this | ||||||
|  | 			rmsg.Channel = strings.TrimPrefix(rmsg.Channel, "#") | ||||||
| 			smsg := &models.Message{ | 			smsg := &models.Message{ | ||||||
| 				RoomID: b.getChannelID(rmsg.Channel), | 				RoomID: b.getChannelID(rmsg.Channel), | ||||||
| 				Msg:    rmsg.Username + rmsg.Text, | 				Msg:    rmsg.Username + rmsg.Text, | ||||||
|   | |||||||
| @@ -22,20 +22,20 @@ func (b *Bslack) handleSlack() { | |||||||
| 	time.Sleep(time.Second) | 	time.Sleep(time.Second) | ||||||
| 	b.Log.Debug("Start listening for Slack messages") | 	b.Log.Debug("Start listening for Slack messages") | ||||||
| 	for message := range messages { | 	for message := range messages { | ||||||
| 		if message.Event != config.EventUserTyping { | 		// don't do any action on deleted/typing messages | ||||||
|  | 		if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete { | ||||||
| 			b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) | 			b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) | ||||||
|  | 			// cleanup the message | ||||||
|  | 			message.Text = b.replaceMention(message.Text) | ||||||
|  | 			message.Text = b.replaceVariable(message.Text) | ||||||
|  | 			message.Text = b.replaceChannel(message.Text) | ||||||
|  | 			message.Text = b.replaceURL(message.Text) | ||||||
|  | 			message.Text = html.UnescapeString(message.Text) | ||||||
|  |  | ||||||
|  | 			// Add the avatar | ||||||
|  | 			message.Avatar = b.users.getAvatar(message.UserID) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// cleanup the message |  | ||||||
| 		message.Text = b.replaceMention(message.Text) |  | ||||||
| 		message.Text = b.replaceVariable(message.Text) |  | ||||||
| 		message.Text = b.replaceChannel(message.Text) |  | ||||||
| 		message.Text = b.replaceURL(message.Text) |  | ||||||
| 		message.Text = html.UnescapeString(message.Text) |  | ||||||
|  |  | ||||||
| 		// Add the avatar |  | ||||||
| 		message.Avatar = b.getAvatar(message.UserID) |  | ||||||
|  |  | ||||||
| 		b.Log.Debugf("<= Message is %#v", message) | 		b.Log.Debugf("<= Message is %#v", message) | ||||||
| 		b.Remote <- *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 | 			// 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 | 			// well as the information for the channel that we joined as this | ||||||
| 			// should now tell that we are a member of it. | 			// should now tell that we are a member of it. | ||||||
| 			b.channelsMutex.Lock() | 			b.channels.registerChannel(ev.Channel) | ||||||
| 			b.channelsByID[ev.Channel.ID] = &ev.Channel |  | ||||||
| 			b.channelsByName[ev.Channel.Name] = &ev.Channel |  | ||||||
| 			b.channelsMutex.Unlock() |  | ||||||
| 		case *slack.ConnectedEvent: | 		case *slack.ConnectedEvent: | ||||||
| 			b.si = ev.Info | 			b.si = ev.Info | ||||||
| 			b.populateChannels(true) | 			b.channels.populateChannels(true) | ||||||
| 			b.populateUsers(true) | 			b.users.populateUsers(true) | ||||||
| 		case *slack.InvalidAuthEvent: | 		case *slack.InvalidAuthEvent: | ||||||
| 			b.Log.Fatalf("Invalid Token %#v", ev) | 			b.Log.Fatalf("Invalid Token %#v", ev) | ||||||
| 		case *slack.ConnectionErrorEvent: | 		case *slack.ConnectionErrorEvent: | ||||||
| 			b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj) | 			b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj) | ||||||
| 		case *slack.MemberJoinedChannelEvent: | 		case *slack.MemberJoinedChannelEvent: | ||||||
| 			b.populateUser(ev.User) | 			b.users.populateUser(ev.User) | ||||||
| 		case *slack.LatencyReport: | 		case *slack.LatencyReport: | ||||||
| 			continue | 			continue | ||||||
| 		default: | 		default: | ||||||
| @@ -133,12 +130,18 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool { | |||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// It seems ev.SubMessage.Edited == nil when slack unfurls. | 	if ev.SubMessage != nil { | ||||||
| 	// Do not forward these messages. See Github issue #266. | 		// It seems ev.SubMessage.Edited == nil when slack unfurls. | ||||||
| 	if ev.SubMessage != nil && | 		// Do not forward these messages. See Github issue #266. | ||||||
| 		ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp && | 		if ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp && | ||||||
| 		ev.SubMessage.Edited == nil { | 			ev.SubMessage.Edited == nil { | ||||||
| 		return true | 			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 { | 	if len(ev.Files) > 0 { | ||||||
| @@ -210,7 +213,7 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) | |||||||
| 		rmsg.Username = sSystemUser | 		rmsg.Username = sSystemUser | ||||||
| 		rmsg.Event = config.EventJoinLeave | 		rmsg.Event = config.EventJoinLeave | ||||||
| 	case sChannelTopic, sChannelPurpose: | 	case sChannelTopic, sChannelPurpose: | ||||||
| 		b.populateChannels(false) | 		b.channels.populateChannels(false) | ||||||
| 		rmsg.Event = config.EventTopicChange | 		rmsg.Event = config.EventTopicChange | ||||||
| 	case sMessageChanged: | 	case sMessageChanged: | ||||||
| 		rmsg.Text = ev.SubMessage.Text | 		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) { | 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 { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -316,36 +319,7 @@ func (b *Bslack) handleGetChannelMembers(rmsg *config.Message) bool { | |||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cMembers := config.ChannelMembers{} | 	cMembers := b.channels.getChannelMembers(b.users) | ||||||
|  |  | ||||||
| 	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() |  | ||||||
|  |  | ||||||
| 	extra := make(map[string][]interface{}) | 	extra := make(map[string][]interface{}) | ||||||
| 	extra[config.EventGetChannelMembers] = append(extra[config.EventGetChannelMembers], cMembers) | 	extra[config.EventGetChannelMembers] = append(extra[config.EventGetChannelMembers], cMembers) | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package bslack | package bslack | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -9,225 +8,14 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/nlopes/slack" | 	"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 | // populateReceivedMessage shapes the initial Matterbridge message that we will forward to the | ||||||
| // router before we apply message-dependent modifications. | // router before we apply message-dependent modifications. | ||||||
| func (b *Bslack) populateReceivedMessage(ev *slack.MessageEvent) (*config.Message, error) { | func (b *Bslack) populateReceivedMessage(ev *slack.MessageEvent) (*config.Message, error) { | ||||||
| 	// Use our own func because rtm.GetChannelInfo doesn't work for private channels. | 	// 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 { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -289,7 +77,7 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	user := b.getUser(userID) | 	user := b.users.getUser(userID) | ||||||
| 	if user == nil { | 	if user == nil { | ||||||
| 		return fmt.Errorf("could not find information for user with id %s", ev.User) | 		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 | 			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) | 			b.Log.Errorf("Could not retrieve bot information: %#v", err) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -360,7 +148,7 @@ func (b *Bslack) extractTopicOrPurpose(text string) (string, string) { | |||||||
| func (b *Bslack) replaceMention(text string) string { | func (b *Bslack) replaceMention(text string) string { | ||||||
| 	replaceFunc := func(match string) string { | 	replaceFunc := func(match string) string { | ||||||
| 		userID := strings.Trim(match, "@<>") | 		userID := strings.Trim(match, "@<>") | ||||||
| 		if username := b.getUsername(userID); userID != "" { | 		if username := b.users.getUsername(userID); userID != "" { | ||||||
| 			return "@" + username | 			return "@" + username | ||||||
| 		} | 		} | ||||||
| 		return match | 		return match | ||||||
| @@ -404,16 +192,6 @@ func (b *Bslack) replaceCodeFence(text string) string { | |||||||
| 	return codeFenceRE.ReplaceAllString(text, "```") | 	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 | // getUsersInConversation returns an array of userIDs that are members of channelID | ||||||
| func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) { | func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) { | ||||||
| 	channelMembers := []string{} | 	channelMembers := []string{} | ||||||
| @@ -424,7 +202,7 @@ func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) { | |||||||
|  |  | ||||||
| 		members, nextCursor, err := b.sc.GetUsersInConversation(queryParams) | 		members, nextCursor, err := b.sc.GetUsersInConversation(queryParams) | ||||||
| 		if err != nil { | 		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) | 				return channelMembers, fmt.Errorf("Could not retrieve users in channels: %#v", err) | ||||||
| 			} | 			} | ||||||
| 			continue | 			continue | ||||||
| @@ -439,3 +217,13 @@ func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) { | |||||||
| 	} | 	} | ||||||
| 	return channelMembers, nil | 	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) != "" { | 		if b.GetString(tokenConfig) != "" { | ||||||
| 			b.Log.Info("Connecting using token (receiving)") | 			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() | 			b.rtm = b.sc.NewRTM() | ||||||
| 			go b.rtm.ManageConnection() | 			go b.rtm.ManageConnection() | ||||||
| 			go b.handleSlack() | 			go b.handleSlack() | ||||||
| 		} | 		} | ||||||
| 	} else if b.GetString(tokenConfig) != "" { | 	} else if b.GetString(tokenConfig) != "" { | ||||||
| 		b.Log.Info("Connecting using token (sending and receiving)") | 		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() | 		b.rtm = b.sc.NewRTM() | ||||||
| 		go b.rtm.ManageConnection() | 		go b.rtm.ManageConnection() | ||||||
| 		go b.handleSlack() | 		go b.handleSlack() | ||||||
|   | |||||||
| @@ -30,20 +30,8 @@ type Bslack struct { | |||||||
| 	uuid         string | 	uuid         string | ||||||
| 	useChannelID bool | 	useChannelID bool | ||||||
|  |  | ||||||
| 	users      map[string]*slack.User | 	channels *channels | ||||||
| 	usersMutex sync.RWMutex | 	users    *users | ||||||
|  |  | ||||||
| 	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 |  | ||||||
| } | } | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @@ -94,14 +82,9 @@ func newBridge(cfg *bridge.Config) *Bslack { | |||||||
| 		cfg.Log.Fatalf("Could not create LRU cache for Slack bridge: %v", err) | 		cfg.Log.Fatalf("Could not create LRU cache for Slack bridge: %v", err) | ||||||
| 	} | 	} | ||||||
| 	b := &Bslack{ | 	b := &Bslack{ | ||||||
| 		Config:                 cfg, | 		Config: cfg, | ||||||
| 		uuid:                   xid.New().String(), | 		uuid:   xid.New().String(), | ||||||
| 		cache:                  newCache, | 		cache:  newCache, | ||||||
| 		users:                  map[string]*slack.User{}, |  | ||||||
| 		channelsByID:           map[string]*slack.Channel{}, |  | ||||||
| 		channelsByName:         map[string]*slack.Channel{}, |  | ||||||
| 		earliestChannelRefresh: time.Now(), |  | ||||||
| 		earliestUserRefresh:    time.Now(), |  | ||||||
| 	} | 	} | ||||||
| 	return b | 	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 we have a token we use the Slack websocket-based RTM for both sending and receiving. | ||||||
| 	if token := b.GetString(tokenConfig); token != "" { | 	if token := b.GetString(tokenConfig); token != "" { | ||||||
| 		b.Log.Info("Connecting using token") | 		b.Log.Info("Connecting using token") | ||||||
|  |  | ||||||
| 		b.sc = slack.New(token, slack.OptionDebug(b.GetBool("Debug"))) | 		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() | 		b.rtm = b.sc.NewRTM() | ||||||
| 		go b.rtm.ManageConnection() | 		go b.rtm.ManageConnection() | ||||||
| 		go b.handleSlack() | 		go b.handleSlack() | ||||||
| @@ -163,9 +151,9 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error { | |||||||
| 		return nil | 		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 { | 	if err != nil { | ||||||
| 		return fmt.Errorf("could not join channel: %#v", err) | 		return fmt.Errorf("could not join channel: %#v", err) | ||||||
| 	} | 	} | ||||||
| @@ -275,7 +263,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) { | |||||||
| 		return "", nil | 		return "", nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	channelInfo, err := b.getChannel(msg.Channel) | 	channelInfo, err := b.channels.getChannel(msg.Channel) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("could not send message: %v", err) | 		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 { | 		if err == nil { | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 		if err = b.handleRateLimit(err); err != nil { | 		if err = handleRateLimit(b.Log, err); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -392,7 +380,7 @@ func (b *Bslack) deleteMessage(msg *config.Message, channelInfo *slack.Channel) | |||||||
| 			return true, nil | 			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) | 			b.Log.Errorf("Failed to delete user message from Slack: %#v", err) | ||||||
| 			return true, err | 			return true, err | ||||||
| 		} | 		} | ||||||
| @@ -411,7 +399,7 @@ func (b *Bslack) editMessage(msg *config.Message, channelInfo *slack.Channel) (b | |||||||
| 			return true, nil | 			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) | 			b.Log.Errorf("Failed to edit user message on Slack: %#v", err) | ||||||
| 			return true, err | 			return true, err | ||||||
| 		} | 		} | ||||||
| @@ -424,14 +412,18 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s | |||||||
| 		return "", nil | 		return "", nil | ||||||
| 	} | 	} | ||||||
| 	messageOptions := b.prepareMessageOptions(msg) | 	messageOptions := b.prepareMessageOptions(msg) | ||||||
| 	messageOptions = append(messageOptions, slack.MsgOptionText(msg.Text, false)) | 	messageOptions = append( | ||||||
|  | 		messageOptions, | ||||||
|  | 		slack.MsgOptionText(msg.Text, false), | ||||||
|  | 		slack.MsgOptionEnableLinkUnfurl(), | ||||||
|  | 	) | ||||||
| 	for { | 	for { | ||||||
| 		_, id, err := b.rtm.PostMessage(channelInfo.ID, messageOptions...) | 		_, id, err := b.rtm.PostMessage(channelInfo.ID, messageOptions...) | ||||||
| 		if err == nil { | 		if err == nil { | ||||||
| 			return id, 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) | 			b.Log.Errorf("Failed to sent user message to Slack: %#v", err) | ||||||
| 			return "", err | 			return "", err | ||||||
| 		} | 		} | ||||||
|   | |||||||
							
								
								
									
										336
									
								
								bridge/slack/users_channels.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								bridge/slack/users_channels.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,336 @@ | |||||||
|  | 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{}) | ||||||
|  | 			defer func() { | ||||||
|  | 				// Wake up any waiting goroutines and remove the synchronization point. | ||||||
|  | 				close(b.usersSyncPoints[userID]) | ||||||
|  | 				delete(b.usersSyncPoints, userID) | ||||||
|  | 			}() | ||||||
|  | 			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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 | 		// handle groups | ||||||
| 		message = b.handleGroups(&rmsg, message, update) | 		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 | 		// set the ID's from the channel or group message | ||||||
| 		rmsg.ID = strconv.Itoa(message.MessageID) | 		rmsg.ID = strconv.Itoa(message.MessageID) | ||||||
| 		rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10) | 		rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10) | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,3 +1,37 @@ | |||||||
|  | # v1.14.4 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * mattermost: Add Id to EditMessage (mattermost). Fixes #802 | ||||||
|  | * mattermost: Fix panic on nil message.Post (mattermost). Fixes #804 | ||||||
|  | * mattermost: Handle unthreaded messages (mattermost). Fixes #803 | ||||||
|  | * mattermost: Use paging in initUser and UpdateUsers (mattermost) | ||||||
|  | * slack: Add lacking clean-up in Slack synchronisation (#811) | ||||||
|  | * slack: Disable user lookups on delete messages (slack) (#812) | ||||||
|  |  | ||||||
|  | # v1.14.3 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * irc: Fix deadlock on reconnect (irc). Closes #757 | ||||||
|  |  | ||||||
|  | # 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 | # v1.14.0 | ||||||
|  |  | ||||||
| ## Breaking | ## Breaking | ||||||
| @@ -19,6 +53,8 @@ | |||||||
|  |  | ||||||
| ## Enhancements | ## Enhancements | ||||||
| * general: Fail gracefully on incorrect human input. Fixes #739 (#740) | * 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 | ## Bugfix | ||||||
| * general: Handle file upload/download only once for each message (#742) | * general: Handle file upload/download only once for each message (#742) | ||||||
| @@ -27,9 +63,9 @@ | |||||||
| * irc: add support for (older) unrealircd versions. #708 | * irc: add support for (older) unrealircd versions. #708 | ||||||
| * irc: Support quits from irc correctly. Fixes #722 (#724) | * irc: Support quits from irc correctly. Fixes #722 (#724) | ||||||
| * matrix: Send username when uploading video/images (matrix). Fixes #715 (#717) | * matrix: Send username when uploading video/images (matrix). Fixes #715 (#717) | ||||||
| * matrix: Send notices on join/parts (matrix). Fixes #712 (#716) | * matrix: Trim <p> and </p> tags (matrix). Closes #686 (#753) | ||||||
| * matrix: Detect html nicks in RemoteNickFormat (matrix). Fixes #696 (#719) |  | ||||||
| * slack: Hint at thread replies when messages are unthreaded (slack) (#684) | * 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) | * xmpp: Do not send topic changes on connect (xmpp). Fixes #732 (#733) | ||||||
| * telegram: Fix regression in HTML handling (telegram). Closes #734 | * telegram: Fix regression in HTML handling (telegram). Closes #734 | ||||||
| * discord: Do not relay any bot messages (discord) (#743) | * discord: Do not relay any bot messages (discord) (#743) | ||||||
|   | |||||||
| @@ -1,5 +1,8 @@ | |||||||
| #!/bin/bash | #!/usr/bin/env bash | ||||||
| go version | grep go1.11 || exit | set -u -e -x -o pipefail | ||||||
|  |  | ||||||
|  | go version | grep go1.12 || exit | ||||||
|  |  | ||||||
| VERSION=$(git describe --tags) | VERSION=$(git describe --tags) | ||||||
| mkdir ci/binaries | 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 | 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" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/d5/tengo/script" | 	"github.com/d5/tengo/script" | ||||||
|  | 	"github.com/d5/tengo/stdlib" | ||||||
| 	lru "github.com/hashicorp/golang-lru" | 	lru "github.com/hashicorp/golang-lru" | ||||||
| 	"github.com/peterhellberg/emojilib" | 	"github.com/peterhellberg/emojilib" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| @@ -211,23 +212,6 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con | |||||||
| 		return channels | 		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 | 	// if source channel is in only, do nothing | ||||||
| 	for _, channel := range gw.Channels { | 	for _, channel := range gw.Channels { | ||||||
| 		// lookup the channel from the message | 		// lookup the channel from the message | ||||||
| @@ -503,6 +487,7 @@ func modifyMessageTengo(filename string, msg *config.Message) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	s := script.New(res) | 	s := script.New(res) | ||||||
|  | 	s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...)) | ||||||
| 	_ = s.Add("msgText", msg.Text) | 	_ = s.Add("msgText", msg.Text) | ||||||
| 	_ = s.Add("msgUsername", msg.Username) | 	_ = s.Add("msgUsername", msg.Username) | ||||||
| 	_ = s.Add("msgAccount", msg.Account) | 	_ = 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/Jeffail/gabs v1.1.1 // indirect | ||||||
| 	github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 | 	github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 | ||||||
| 	github.com/bwmarrin/discordgo v0.19.0 | 	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/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec | ||||||
| 	github.com/fsnotify/fsnotify v1.4.7 | 	github.com/fsnotify/fsnotify v1.4.7 | ||||||
| 	github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible | 	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/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-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/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.20.0 h1:lFmktzEGR6khlZu2MHUWJ5oDWS4l3jNRV/OhclZgcYc= | ||||||
| github.com/d5/tengo v1.9.2/go.mod h1:gsbjo7lBXzBIWBd6NQp1lRKqqiDDANqBOyhW8rTlFsY= | 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	version = "1.14.0-rc2" | 	version = "1.14.4" | ||||||
| 	githash string | 	githash string | ||||||
|  |  | ||||||
| 	flagConfig  = flag.String("conf", "matterbridge.toml", "config file") | 	flagConfig  = flag.String("conf", "matterbridge.toml", "config file") | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ UseTLS=false | |||||||
| #OPTIONAL (default false) | #OPTIONAL (default false) | ||||||
| UseSASL=false | UseSASL=false | ||||||
|  |  | ||||||
| #Enable to not verify the certificate on your irc server. i | #Enable to not verify the certificate on your irc server. | ||||||
| #e.g. when using selfsigned certificates | #e.g. when using selfsigned certificates | ||||||
| #OPTIONAL (default false) | #OPTIONAL (default false) | ||||||
| SkipTLSVerify=true | SkipTLSVerify=true | ||||||
| @@ -1007,9 +1007,10 @@ ShowTopicChange=false | |||||||
| Server="https://yourrocketchatserver.domain.com:443" | Server="https://yourrocketchatserver.domain.com:443" | ||||||
|  |  | ||||||
| #login/pass of your bot.  | #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!  | #Use a dedicated user for this and not your own!  | ||||||
| #REQUIRED (when not using webhooks) | #REQUIRED (when not using webhooks) | ||||||
| Login="yourlogin" | Login="yourlogin@domain.com" | ||||||
| Password="yourpass" | Password="yourpass" | ||||||
|  |  | ||||||
| #### Settings for webhook matterbridge. | #### Settings for webhook matterbridge. | ||||||
| @@ -1050,6 +1051,8 @@ SkipTLSVerify=true | |||||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||||
| #rocketchat server. If you set PrefixMessagesWithNick to true, each message  | #rocketchat server. If you set PrefixMessagesWithNick to true, each message  | ||||||
| #from bridge to rocketchat will by default be prefixed by the RemoteNickFormat setting. i | #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) | #OPTIONAL (default false) | ||||||
| PrefixMessagesWithNick=false | PrefixMessagesWithNick=false | ||||||
|  |  | ||||||
|   | |||||||
| @@ -51,8 +51,9 @@ func (m *MMClient) GetChannelId(name string, teamId string) string { //nolint:go | |||||||
| 				if res == name { | 				if res == name { | ||||||
| 					return channel.Id | 					return channel.Id | ||||||
| 				} | 				} | ||||||
|  | 			} else if channel.Name == name { | ||||||
|  | 				return channel.Id | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return "" | 	return "" | ||||||
|   | |||||||
| @@ -132,14 +132,25 @@ func (m *MMClient) initUser() error { | |||||||
| 		return resp.Error | 		return resp.Error | ||||||
| 	} | 	} | ||||||
| 	for _, team := range teams { | 	for _, team := range teams { | ||||||
| 		mmusers, resp := m.Client.GetUsersInTeam(team.Id, 0, 50000, "") | 		idx := 0 | ||||||
|  | 		max := 200 | ||||||
|  | 		usermap := make(map[string]*model.User) | ||||||
|  | 		mmusers, resp := m.Client.GetUsersInTeam(team.Id, idx, max, "") | ||||||
| 		if resp.Error != nil { | 		if resp.Error != nil { | ||||||
| 			return errors.New(resp.Error.DetailedError) | 			return errors.New(resp.Error.DetailedError) | ||||||
| 		} | 		} | ||||||
| 		usermap := make(map[string]*model.User) | 		for len(mmusers) > 0 { | ||||||
| 		for _, user := range mmusers { | 			for _, user := range mmusers { | ||||||
| 			usermap[user.Id] = user | 				usermap[user.Id] = user | ||||||
|  | 			} | ||||||
|  | 			mmusers, resp = m.Client.GetUsersInTeam(team.Id, idx, max, "") | ||||||
|  | 			if resp.Error != nil { | ||||||
|  | 				return errors.New(resp.Error.DetailedError) | ||||||
|  | 			} | ||||||
|  | 			idx++ | ||||||
|  | 			time.Sleep(time.Millisecond * 200) | ||||||
| 		} | 		} | ||||||
|  | 		m.logger.Infof("found %d users in team %s", len(usermap), team.Name) | ||||||
|  |  | ||||||
| 		t := &Team{Team: team, Users: usermap, Id: team.Id} | 		t := &Team{Team: team, Users: usermap, Id: team.Id} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -216,9 +216,14 @@ func (m *MMClient) WsReceiver() { | |||||||
| 			if msg.Post != nil { | 			if msg.Post != nil { | ||||||
| 				if msg.Text != "" || len(msg.Post.FileIds) > 0 || msg.Post.Type == "slack_attachment" { | 				if msg.Text != "" || len(msg.Post.FileIds) > 0 || msg.Post.Type == "slack_attachment" { | ||||||
| 					m.MessageChan <- msg | 					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 | 		var response model.WebSocketResponse | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ func (m *MMClient) DeleteMessage(postId string) error { //nolint:golint | |||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) EditMessage(postId string, text string) (string, error) { //nolint:golint | func (m *MMClient) EditMessage(postId string, text string) (string, error) { //nolint:golint | ||||||
| 	post := &model.Post{Message: text} | 	post := &model.Post{Message: text, Id: postId} | ||||||
| 	res, resp := m.Client.UpdatePost(postId, post) | 	res, resp := m.Client.UpdatePost(postId, post) | ||||||
| 	if resp.Error != nil { | 	if resp.Error != nil { | ||||||
| 		return "", resp.Error | 		return "", resp.Error | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package matterclient | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/mattermost/mattermost-server/model" | 	"github.com/mattermost/mattermost-server/model" | ||||||
| ) | ) | ||||||
| @@ -99,15 +100,25 @@ func (m *MMClient) GetUsers() map[string]*model.User { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) UpdateUsers() error { | func (m *MMClient) UpdateUsers() error { | ||||||
| 	mmusers, resp := m.Client.GetUsers(0, 50000, "") | 	idx := 0 | ||||||
|  | 	max := 200 | ||||||
|  | 	mmusers, resp := m.Client.GetUsers(idx, max, "") | ||||||
| 	if resp.Error != nil { | 	if resp.Error != nil { | ||||||
| 		return errors.New(resp.Error.DetailedError) | 		return errors.New(resp.Error.DetailedError) | ||||||
| 	} | 	} | ||||||
| 	m.Lock() | 	for len(mmusers) > 0 { | ||||||
| 	for _, user := range mmusers { | 		m.Lock() | ||||||
| 		m.Users[user.Id] = user | 		for _, user := range mmusers { | ||||||
|  | 			m.Users[user.Id] = user | ||||||
|  | 		} | ||||||
|  | 		m.Unlock() | ||||||
|  | 		mmusers, resp = m.Client.GetUsers(idx, max, "") | ||||||
|  | 		time.Sleep(time.Millisecond * 300) | ||||||
|  | 		if resp.Error != nil { | ||||||
|  | 			return errors.New(resp.Error.DetailedError) | ||||||
|  | 		} | ||||||
|  | 		idx++ | ||||||
| 	} | 	} | ||||||
| 	m.Unlock() |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										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 | 	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. | // Encode writes Bytecode data to the writer. | ||||||
| func (b *Bytecode) Encode(w io.Writer) error { | func (b *Bytecode) Encode(w io.Writer) error { | ||||||
| 	enc := gob.NewEncoder(w) | 	enc := gob.NewEncoder(w) | ||||||
| @@ -59,6 +33,17 @@ func (b *Bytecode) Encode(w io.Writer) error { | |||||||
| 	return enc.Encode(b.Constants) | 	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 | // FormatInstructions returns human readable string representations of | ||||||
| // compiled instructions. | // compiled instructions. | ||||||
| func (b *Bytecode) FormatInstructions() []string { | func (b *Bytecode) FormatInstructions() []string { | ||||||
| @@ -83,51 +68,22 @@ func (b *Bytecode) FormatConstants() (output []string) { | |||||||
| 	return | 	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() { | func init() { | ||||||
| 	gob.Register(&source.FileSet{}) | 	gob.Register(&source.FileSet{}) | ||||||
| 	gob.Register(&source.File{}) | 	gob.Register(&source.File{}) | ||||||
| 	gob.Register(&objects.Array{}) | 	gob.Register(&objects.Array{}) | ||||||
| 	gob.Register(&objects.ArrayIterator{}) |  | ||||||
| 	gob.Register(&objects.Bool{}) | 	gob.Register(&objects.Bool{}) | ||||||
| 	gob.Register(&objects.Break{}) |  | ||||||
| 	gob.Register(&objects.BuiltinFunction{}) |  | ||||||
| 	gob.Register(&objects.Bytes{}) | 	gob.Register(&objects.Bytes{}) | ||||||
| 	gob.Register(&objects.Char{}) | 	gob.Register(&objects.Char{}) | ||||||
| 	gob.Register(&objects.Closure{}) | 	gob.Register(&objects.Closure{}) | ||||||
| 	gob.Register(&objects.CompiledFunction{}) | 	gob.Register(&objects.CompiledFunction{}) | ||||||
| 	gob.Register(&objects.Continue{}) |  | ||||||
| 	gob.Register(&objects.Error{}) | 	gob.Register(&objects.Error{}) | ||||||
| 	gob.Register(&objects.Float{}) | 	gob.Register(&objects.Float{}) | ||||||
| 	gob.Register(&objects.ImmutableArray{}) | 	gob.Register(&objects.ImmutableArray{}) | ||||||
| 	gob.Register(&objects.ImmutableMap{}) | 	gob.Register(&objects.ImmutableMap{}) | ||||||
| 	gob.Register(&objects.Int{}) | 	gob.Register(&objects.Int{}) | ||||||
| 	gob.Register(&objects.Map{}) | 	gob.Register(&objects.Map{}) | ||||||
| 	gob.Register(&objects.MapIterator{}) |  | ||||||
| 	gob.Register(&objects.ReturnValue{}) |  | ||||||
| 	gob.Register(&objects.String{}) | 	gob.Register(&objects.String{}) | ||||||
| 	gob.Register(&objects.StringIterator{}) |  | ||||||
| 	gob.Register(&objects.Time{}) | 	gob.Register(&objects.Time{}) | ||||||
| 	gob.Register(&objects.Undefined{}) | 	gob.Register(&objects.Undefined{}) | ||||||
| 	gob.Register(&objects.UserFunction{}) | 	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 | // CompilationScope represents a compiled instructions | ||||||
| // and the last two instructions that were emitted. | // and the last two instructions that were emitted. | ||||||
| type CompilationScope struct { | type CompilationScope struct { | ||||||
| 	instructions     []byte | 	instructions []byte | ||||||
| 	lastInstructions [2]EmittedInstruction | 	symbolInit   map[string]bool | ||||||
| 	symbolInit       map[string]bool | 	sourceMap    map[int]source.Pos | ||||||
| 	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 ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"path/filepath" | ||||||
| 	"reflect" | 	"reflect" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| 	"github.com/d5/tengo/compiler/ast" | 	"github.com/d5/tengo/compiler/ast" | ||||||
| 	"github.com/d5/tengo/compiler/source" | 	"github.com/d5/tengo/compiler/source" | ||||||
| 	"github.com/d5/tengo/compiler/token" | 	"github.com/d5/tengo/compiler/token" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| 	"github.com/d5/tengo/stdlib" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Compiler compiles the AST into a bytecode. | // Compiler compiles the AST into a bytecode. | ||||||
| type Compiler struct { | type Compiler struct { | ||||||
| 	file            *source.File | 	file            *source.File | ||||||
| 	parent          *Compiler | 	parent          *Compiler | ||||||
| 	moduleName      string | 	modulePath      string | ||||||
| 	constants       []objects.Object | 	constants       []objects.Object | ||||||
| 	symbolTable     *SymbolTable | 	symbolTable     *SymbolTable | ||||||
| 	scopes          []CompilationScope | 	scopes          []CompilationScope | ||||||
| 	scopeIndex      int | 	scopeIndex      int | ||||||
| 	moduleLoader    ModuleLoader | 	modules         *objects.ModuleMap | ||||||
| 	builtinModules  map[string]bool |  | ||||||
| 	compiledModules map[string]*objects.CompiledFunction | 	compiledModules map[string]*objects.CompiledFunction | ||||||
|  | 	allowFileImport bool | ||||||
| 	loops           []*Loop | 	loops           []*Loop | ||||||
| 	loopIndex       int | 	loopIndex       int | ||||||
| 	trace           io.Writer | 	trace           io.Writer | ||||||
| @@ -31,12 +34,7 @@ type Compiler struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| // NewCompiler creates a Compiler. | // NewCompiler creates a Compiler. | ||||||
| // User can optionally provide the symbol table if one wants to add or remove | func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, modules *objects.ModuleMap, trace io.Writer) *Compiler { | ||||||
| // 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 { |  | ||||||
| 	mainScope := CompilationScope{ | 	mainScope := CompilationScope{ | ||||||
| 		symbolInit: make(map[string]bool), | 		symbolInit: make(map[string]bool), | ||||||
| 		sourceMap:  make(map[int]source.Pos), | 		sourceMap:  make(map[int]source.Pos), | ||||||
| @@ -45,18 +43,16 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object | |||||||
| 	// symbol table | 	// symbol table | ||||||
| 	if symbolTable == nil { | 	if symbolTable == nil { | ||||||
| 		symbolTable = NewSymbolTable() | 		symbolTable = NewSymbolTable() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 		for idx, fn := range objects.Builtins { | 	// add builtin functions to the symbol table | ||||||
| 			symbolTable.DefineBuiltin(idx, fn.Name) | 	for idx, fn := range objects.Builtins { | ||||||
| 		} | 		symbolTable.DefineBuiltin(idx, fn.Name) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// builtin modules | 	// builtin modules | ||||||
| 	if builtinModules == nil { | 	if modules == nil { | ||||||
| 		builtinModules = make(map[string]bool) | 		modules = objects.NewModuleMap() | ||||||
| 		for name := range stdlib.Modules { |  | ||||||
| 			builtinModules[name] = true |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &Compiler{ | 	return &Compiler{ | ||||||
| @@ -67,7 +63,7 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object | |||||||
| 		scopeIndex:      0, | 		scopeIndex:      0, | ||||||
| 		loopIndex:       -1, | 		loopIndex:       -1, | ||||||
| 		trace:           trace, | 		trace:           trace, | ||||||
| 		builtinModules:  builtinModules, | 		modules:         modules, | ||||||
| 		compiledModules: make(map[string]*objects.CompiledFunction), | 		compiledModules: make(map[string]*objects.CompiledFunction), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -123,7 +119,7 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			c.emit(node, OpGreaterThan) | 			c.emit(node, OpBinaryOp, int(token.Greater)) | ||||||
|  |  | ||||||
| 			return nil | 			return nil | ||||||
| 		} else if node.Token == token.LessEq { | 		} else if node.Token == token.LessEq { | ||||||
| @@ -134,7 +130,7 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			c.emit(node, OpGreaterThanEqual) | 			c.emit(node, OpBinaryOp, int(token.GreaterEq)) | ||||||
|  |  | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| @@ -148,35 +144,35 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
|  |  | ||||||
| 		switch node.Token { | 		switch node.Token { | ||||||
| 		case token.Add: | 		case token.Add: | ||||||
| 			c.emit(node, OpAdd) | 			c.emit(node, OpBinaryOp, int(token.Add)) | ||||||
| 		case token.Sub: | 		case token.Sub: | ||||||
| 			c.emit(node, OpSub) | 			c.emit(node, OpBinaryOp, int(token.Sub)) | ||||||
| 		case token.Mul: | 		case token.Mul: | ||||||
| 			c.emit(node, OpMul) | 			c.emit(node, OpBinaryOp, int(token.Mul)) | ||||||
| 		case token.Quo: | 		case token.Quo: | ||||||
| 			c.emit(node, OpDiv) | 			c.emit(node, OpBinaryOp, int(token.Quo)) | ||||||
| 		case token.Rem: | 		case token.Rem: | ||||||
| 			c.emit(node, OpRem) | 			c.emit(node, OpBinaryOp, int(token.Rem)) | ||||||
| 		case token.Greater: | 		case token.Greater: | ||||||
| 			c.emit(node, OpGreaterThan) | 			c.emit(node, OpBinaryOp, int(token.Greater)) | ||||||
| 		case token.GreaterEq: | 		case token.GreaterEq: | ||||||
| 			c.emit(node, OpGreaterThanEqual) | 			c.emit(node, OpBinaryOp, int(token.GreaterEq)) | ||||||
| 		case token.Equal: | 		case token.Equal: | ||||||
| 			c.emit(node, OpEqual) | 			c.emit(node, OpEqual) | ||||||
| 		case token.NotEqual: | 		case token.NotEqual: | ||||||
| 			c.emit(node, OpNotEqual) | 			c.emit(node, OpNotEqual) | ||||||
| 		case token.And: | 		case token.And: | ||||||
| 			c.emit(node, OpBAnd) | 			c.emit(node, OpBinaryOp, int(token.And)) | ||||||
| 		case token.Or: | 		case token.Or: | ||||||
| 			c.emit(node, OpBOr) | 			c.emit(node, OpBinaryOp, int(token.Or)) | ||||||
| 		case token.Xor: | 		case token.Xor: | ||||||
| 			c.emit(node, OpBXor) | 			c.emit(node, OpBinaryOp, int(token.Xor)) | ||||||
| 		case token.AndNot: | 		case token.AndNot: | ||||||
| 			c.emit(node, OpBAndNot) | 			c.emit(node, OpBinaryOp, int(token.AndNot)) | ||||||
| 		case token.Shl: | 		case token.Shl: | ||||||
| 			c.emit(node, OpBShiftLeft) | 			c.emit(node, OpBinaryOp, int(token.Shl)) | ||||||
| 		case token.Shr: | 		case token.Shr: | ||||||
| 			c.emit(node, OpBShiftRight) | 			c.emit(node, OpBinaryOp, int(token.Shr)) | ||||||
| 		default: | 		default: | ||||||
| 			return c.errorf(node, "invalid binary operator: %s", node.Token.String()) | 			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: | 	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})) | 		c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value})) | ||||||
|  |  | ||||||
| 	case *ast.CharLit: | 	case *ast.CharLit: | ||||||
| @@ -292,6 +292,15 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	case *ast.BlockStmt: | 	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 { | 		for _, stmt := range node.Stmts { | ||||||
| 			if err := c.Compile(stmt); err != nil { | 			if err := c.Compile(stmt); err != nil { | ||||||
| 				return err | 				return err | ||||||
| @@ -332,6 +341,9 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
| 	case *ast.MapLit: | 	case *ast.MapLit: | ||||||
| 		for _, elt := range node.Elements { | 		for _, elt := range node.Elements { | ||||||
| 			// key | 			// key | ||||||
|  | 			if len(elt.Key) > tengo.MaxStringLen { | ||||||
|  | 				return c.error(node, objects.ErrStringLimit) | ||||||
|  | 			} | ||||||
| 			c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key})) | 			c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key})) | ||||||
|  |  | ||||||
| 			// value | 			// value | ||||||
| @@ -401,10 +413,8 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// add OpReturn if function returns nothing | 		// code optimization | ||||||
| 		if !c.lastInstructionIs(OpReturnValue) && !c.lastInstructionIs(OpReturn) { | 		c.optimizeFunc(node) | ||||||
| 			c.emit(node, OpReturn) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		freeSymbols := c.symbolTable.FreeSymbols() | 		freeSymbols := c.symbolTable.FreeSymbols() | ||||||
| 		numLocals := c.symbolTable.MaxSymbols() | 		numLocals := c.symbolTable.MaxSymbols() | ||||||
| @@ -457,9 +467,9 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
| 					s.LocalAssigned = true | 					s.LocalAssigned = true | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				c.emit(node, OpGetLocal, s.Index) | 				c.emit(node, OpGetLocalPtr, s.Index) | ||||||
| 			case ScopeFree: | 			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 { | 		if node.Result == nil { | ||||||
| 			c.emit(node, OpReturn) | 			c.emit(node, OpReturn, 0) | ||||||
| 		} else { | 		} else { | ||||||
| 			if err := c.Compile(node.Result); err != nil { | 			if err := c.Compile(node.Result); err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			c.emit(node, OpReturnValue) | 			c.emit(node, OpReturn, 1) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	case *ast.CallExpr: | 	case *ast.CallExpr: | ||||||
| @@ -506,17 +516,57 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
| 		c.emit(node, OpCall, len(node.Args)) | 		c.emit(node, OpCall, len(node.Args)) | ||||||
|  |  | ||||||
| 	case *ast.ImportExpr: | 	case *ast.ImportExpr: | ||||||
| 		if c.builtinModules[node.ModuleName] { | 		if node.ModuleName == "" { | ||||||
| 			c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName})) | 			return c.errorf(node, "empty module name") | ||||||
| 			c.emit(node, OpGetBuiltinModule) | 		} | ||||||
| 		} else { |  | ||||||
| 			userMod, err := c.compileModule(node) | 		if mod := c.modules.Get(node.ModuleName); mod != nil { | ||||||
|  | 			v, err := mod.Import(node.ModuleName) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				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) | 			c.emit(node, OpCall, 0) | ||||||
|  | 		} else { | ||||||
|  | 			return c.errorf(node, "module '%s' not found", node.ModuleName) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	case *ast.ExportStmt: | 	case *ast.ExportStmt: | ||||||
| @@ -535,7 +585,7 @@ func (c *Compiler) Compile(node ast.Node) error { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		c.emit(node, OpImmutable) | 		c.emit(node, OpImmutable) | ||||||
| 		c.emit(node, OpReturnValue) | 		c.emit(node, OpReturn, 1) | ||||||
|  |  | ||||||
| 	case *ast.ErrorExpr: | 	case *ast.ErrorExpr: | ||||||
| 		if err := c.Compile(node.Expr); err != nil { | 		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. | // EnableFileImport enables or disables module loading from local files. | ||||||
| // Note that the module loader is used for user modules, | // Local file modules are disabled by default. | ||||||
| // not for the standard modules. | func (c *Compiler) EnableFileImport(enable bool) { | ||||||
| func (c *Compiler) SetModuleLoader(moduleLoader ModuleLoader) { | 	c.allowFileImport = enable | ||||||
| 	c.moduleLoader = moduleLoader |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *SymbolTable) *Compiler { | func (c *Compiler) fork(file *source.File, modulePath string, symbolTable *SymbolTable) *Compiler { | ||||||
| 	child := NewCompiler(file, symbolTable, nil, c.builtinModules, c.trace) | 	child := NewCompiler(file, symbolTable, nil, c.modules, c.trace) | ||||||
| 	child.moduleName = moduleName       // name of the module to compile | 	child.modulePath = modulePath // module file path | ||||||
| 	child.parent = c                    // parent to set to current compiler | 	child.parent = c              // parent to set to current compiler | ||||||
| 	child.moduleLoader = c.moduleLoader // share module loader |  | ||||||
|  |  | ||||||
| 	return child | 	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 { | func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error { | ||||||
| 	return &Error{ | 	return &Error{ | ||||||
| 		fileSet: c.file.Set(), | 		fileSet: c.file.Set(), | ||||||
| @@ -641,33 +697,6 @@ func (c *Compiler) addInstruction(b []byte) int { | |||||||
| 	return posNewIns | 	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) { | func (c *Compiler) replaceInstruction(pos int, inst []byte) { | ||||||
| 	copy(c.currentInstructions()[pos:], inst) | 	copy(c.currentInstructions()[pos:], inst) | ||||||
|  |  | ||||||
| @@ -684,6 +713,92 @@ func (c *Compiler) changeOperand(opPos int, operand ...int) { | |||||||
| 	c.replaceInstruction(opPos, inst) | 	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 { | func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int { | ||||||
| 	filePos := source.NoPos | 	filePos := source.NoPos | ||||||
| 	if node != nil { | 	if node != nil { | ||||||
| @@ -693,7 +808,6 @@ func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int { | |||||||
| 	inst := MakeInstruction(opcode, operands...) | 	inst := MakeInstruction(opcode, operands...) | ||||||
| 	pos := c.addInstruction(inst) | 	pos := c.addInstruction(inst) | ||||||
| 	c.scopes[c.scopeIndex].sourceMap[pos] = filePos | 	c.scopes[c.scopeIndex].sourceMap[pos] = filePos | ||||||
| 	c.setLastInstruction(opcode, pos) |  | ||||||
|  |  | ||||||
| 	if c.trace != nil { | 	if c.trace != nil { | ||||||
| 		c.printTrace(fmt.Sprintf("EMIT  %s", | 		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 { | 	switch op { | ||||||
| 	case token.AddAssign: | 	case token.AddAssign: | ||||||
| 		c.emit(node, OpAdd) | 		c.emit(node, OpBinaryOp, int(token.Add)) | ||||||
| 	case token.SubAssign: | 	case token.SubAssign: | ||||||
| 		c.emit(node, OpSub) | 		c.emit(node, OpBinaryOp, int(token.Sub)) | ||||||
| 	case token.MulAssign: | 	case token.MulAssign: | ||||||
| 		c.emit(node, OpMul) | 		c.emit(node, OpBinaryOp, int(token.Mul)) | ||||||
| 	case token.QuoAssign: | 	case token.QuoAssign: | ||||||
| 		c.emit(node, OpDiv) | 		c.emit(node, OpBinaryOp, int(token.Quo)) | ||||||
| 	case token.RemAssign: | 	case token.RemAssign: | ||||||
| 		c.emit(node, OpRem) | 		c.emit(node, OpBinaryOp, int(token.Rem)) | ||||||
| 	case token.AndAssign: | 	case token.AndAssign: | ||||||
| 		c.emit(node, OpBAnd) | 		c.emit(node, OpBinaryOp, int(token.And)) | ||||||
| 	case token.OrAssign: | 	case token.OrAssign: | ||||||
| 		c.emit(node, OpBOr) | 		c.emit(node, OpBinaryOp, int(token.Or)) | ||||||
| 	case token.AndNotAssign: | 	case token.AndNotAssign: | ||||||
| 		c.emit(node, OpBAndNot) | 		c.emit(node, OpBinaryOp, int(token.AndNot)) | ||||||
| 	case token.XorAssign: | 	case token.XorAssign: | ||||||
| 		c.emit(node, OpBXor) | 		c.emit(node, OpBinaryOp, int(token.Xor)) | ||||||
| 	case token.ShlAssign: | 	case token.ShlAssign: | ||||||
| 		c.emit(node, OpBShiftLeft) | 		c.emit(node, OpBinaryOp, int(token.Shl)) | ||||||
| 	case token.ShrAssign: | 	case token.ShrAssign: | ||||||
| 		c.emit(node, OpBShiftRight) | 		c.emit(node, OpBinaryOp, int(token.Shr)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// compile selector expressions (right to left) | 	// 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 | package compiler | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io/ioutil" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/d5/tengo/compiler/ast" | 	"github.com/d5/tengo/compiler/ast" | ||||||
| 	"github.com/d5/tengo/compiler/parser" | 	"github.com/d5/tengo/compiler/parser" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) { | func (c *Compiler) checkCyclicImports(node ast.Node, modulePath string) error { | ||||||
| 	compiledModule, exists := c.loadCompiledModule(expr.ModuleName) | 	if c.modulePath == modulePath { | ||||||
| 	if exists { | 		return c.errorf(node, "cyclic module import: %s", modulePath) | ||||||
| 		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) |  | ||||||
| 	} else if c.parent != nil { | 	} else if c.parent != nil { | ||||||
| 		return c.parent.checkCyclicImports(node, moduleName) | 		return c.parent.checkCyclicImports(node, modulePath) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	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)) | 	modFile := c.file.Set().AddFile(moduleName, -1, len(src)) | ||||||
| 	p := parser.NewParser(modFile, src, nil) | 	p := parser.NewParser(modFile, src, nil) | ||||||
| 	file, err := p.ParseFile() | 	file, err := p.ParseFile() | ||||||
| @@ -77,47 +36,44 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp | |||||||
| 	symbolTable := NewSymbolTable() | 	symbolTable := NewSymbolTable() | ||||||
|  |  | ||||||
| 	// inherit builtin functions | 	// inherit builtin functions | ||||||
| 	for idx, fn := range objects.Builtins { | 	for _, sym := range c.symbolTable.BuiltinSymbols() { | ||||||
| 		s, _, ok := c.symbolTable.Resolve(fn.Name) | 		symbolTable.DefineBuiltin(sym.Index, sym.Name) | ||||||
| 		if ok && s.Scope == ScopeBuiltin { |  | ||||||
| 			symbolTable.DefineBuiltin(idx, fn.Name) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// no global scope for the module | 	// no global scope for the module | ||||||
| 	symbolTable = symbolTable.Fork(false) | 	symbolTable = symbolTable.Fork(false) | ||||||
|  |  | ||||||
| 	// compile module | 	// compile module | ||||||
| 	moduleCompiler := c.fork(modFile, moduleName, symbolTable) | 	moduleCompiler := c.fork(modFile, modulePath, symbolTable) | ||||||
| 	if err := moduleCompiler.Compile(file); err != nil { | 	if err := moduleCompiler.Compile(file); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// add OpReturn (== export undefined) if export is missing | 	// code optimization | ||||||
| 	if !moduleCompiler.lastInstructionIs(OpReturnValue) { | 	moduleCompiler.optimizeFunc(node) | ||||||
| 		moduleCompiler.emit(nil, OpReturn) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	compiledFunc := moduleCompiler.Bytecode().MainFunction | 	compiledFunc := moduleCompiler.Bytecode().MainFunction | ||||||
| 	compiledFunc.NumLocals = symbolTable.MaxSymbols() | 	compiledFunc.NumLocals = symbolTable.MaxSymbols() | ||||||
|  |  | ||||||
|  | 	c.storeCompiledModule(modulePath, compiledFunc) | ||||||
|  |  | ||||||
| 	return compiledFunc, nil | 	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 { | 	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 | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Compiler) storeCompiledModule(moduleName string, module *objects.CompiledFunction) { | func (c *Compiler) storeCompiledModule(modulePath string, module *objects.CompiledFunction) { | ||||||
| 	if c.parent != nil { | 	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 | 	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 | // List of opcodes | ||||||
| const ( | const ( | ||||||
| 	OpConstant         Opcode = iota // Load constant | 	OpConstant      Opcode = iota // Load constant | ||||||
| 	OpAdd                            // Add | 	OpBComplement                 // bitwise complement | ||||||
| 	OpSub                            // Sub | 	OpPop                         // Pop | ||||||
| 	OpMul                            // Multiply | 	OpTrue                        // Push true | ||||||
| 	OpDiv                            // Divide | 	OpFalse                       // Push false | ||||||
| 	OpRem                            // Remainder | 	OpEqual                       // Equal == | ||||||
| 	OpBAnd                           // bitwise AND | 	OpNotEqual                    // Not equal != | ||||||
| 	OpBOr                            // bitwise OR | 	OpMinus                       // Minus - | ||||||
| 	OpBXor                           // bitwise XOR | 	OpLNot                        // Logical not ! | ||||||
| 	OpBShiftLeft                     // bitwise shift left | 	OpJumpFalsy                   // Jump if falsy | ||||||
| 	OpBShiftRight                    // bitwise shift right | 	OpAndJump                     // Logical AND jump | ||||||
| 	OpBAndNot                        // bitwise AND NOT | 	OpOrJump                      // Logical OR jump | ||||||
| 	OpBComplement                    // bitwise complement | 	OpJump                        // Jump | ||||||
| 	OpPop                            // Pop | 	OpNull                        // Push null | ||||||
| 	OpTrue                           // Push true | 	OpArray                       // Array object | ||||||
| 	OpFalse                          // Push false | 	OpMap                         // Map object | ||||||
| 	OpEqual                          // Equal == | 	OpError                       // Error object | ||||||
| 	OpNotEqual                       // Not equal != | 	OpImmutable                   // Immutable object | ||||||
| 	OpGreaterThan                    // Greater than >= | 	OpIndex                       // Index operation | ||||||
| 	OpGreaterThanEqual               // Greater than or equal to >= | 	OpSliceIndex                  // Slice operation | ||||||
| 	OpMinus                          // Minus - | 	OpCall                        // Call function | ||||||
| 	OpLNot                           // Logical not ! | 	OpReturn                      // Return | ||||||
| 	OpJumpFalsy                      // Jump if falsy | 	OpGetGlobal                   // Get global variable | ||||||
| 	OpAndJump                        // Logical AND jump | 	OpSetGlobal                   // Set global variable | ||||||
| 	OpOrJump                         // Logical OR jump | 	OpSetSelGlobal                // Set global variable using selectors | ||||||
| 	OpJump                           // Jump | 	OpGetLocal                    // Get local variable | ||||||
| 	OpNull                           // Push null | 	OpSetLocal                    // Set local variable | ||||||
| 	OpArray                          // Array object | 	OpDefineLocal                 // Define local variable | ||||||
| 	OpMap                            // Map object | 	OpSetSelLocal                 // Set local variable using selectors | ||||||
| 	OpError                          // Error object | 	OpGetFreePtr                  // Get free variable pointer object | ||||||
| 	OpImmutable                      // Immutable object | 	OpGetFree                     // Get free variables | ||||||
| 	OpIndex                          // Index operation | 	OpSetFree                     // Set free variables | ||||||
| 	OpSliceIndex                     // Slice operation | 	OpGetLocalPtr                 // Get local variable as a pointer | ||||||
| 	OpCall                           // Call function | 	OpSetSelFree                  // Set free variables using selectors | ||||||
| 	OpReturn                         // Return | 	OpGetBuiltin                  // Get builtin function | ||||||
| 	OpReturnValue                    // Return value | 	OpClosure                     // Push closure | ||||||
| 	OpGetGlobal                      // Get global variable | 	OpIteratorInit                // Iterator init | ||||||
| 	OpSetGlobal                      // Set global variable | 	OpIteratorNext                // Iterator next | ||||||
| 	OpSetSelGlobal                   // Set global variable using selectors | 	OpIteratorKey                 // Iterator key | ||||||
| 	OpGetLocal                       // Get local variable | 	OpIteratorValue               // Iterator value | ||||||
| 	OpSetLocal                       // Set local variable | 	OpBinaryOp                    // Binary Operation | ||||||
| 	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 |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // OpcodeNames is opcode names. | // OpcodeNames is opcode names. | ||||||
| var OpcodeNames = [...]string{ | var OpcodeNames = [...]string{ | ||||||
| 	OpConstant:         "CONST", | 	OpConstant:      "CONST", | ||||||
| 	OpPop:              "POP", | 	OpPop:           "POP", | ||||||
| 	OpTrue:             "TRUE", | 	OpTrue:          "TRUE", | ||||||
| 	OpFalse:            "FALSE", | 	OpFalse:         "FALSE", | ||||||
| 	OpAdd:              "ADD", | 	OpBComplement:   "NEG", | ||||||
| 	OpSub:              "SUB", | 	OpEqual:         "EQL", | ||||||
| 	OpMul:              "MUL", | 	OpNotEqual:      "NEQ", | ||||||
| 	OpDiv:              "DIV", | 	OpMinus:         "NEG", | ||||||
| 	OpRem:              "REM", | 	OpLNot:          "NOT", | ||||||
| 	OpBAnd:             "AND", | 	OpJumpFalsy:     "JMPF", | ||||||
| 	OpBOr:              "OR", | 	OpAndJump:       "ANDJMP", | ||||||
| 	OpBXor:             "XOR", | 	OpOrJump:        "ORJMP", | ||||||
| 	OpBAndNot:          "ANDN", | 	OpJump:          "JMP", | ||||||
| 	OpBShiftLeft:       "SHL", | 	OpNull:          "NULL", | ||||||
| 	OpBShiftRight:      "SHR", | 	OpGetGlobal:     "GETG", | ||||||
| 	OpBComplement:      "NEG", | 	OpSetGlobal:     "SETG", | ||||||
| 	OpEqual:            "EQL", | 	OpSetSelGlobal:  "SETSG", | ||||||
| 	OpNotEqual:         "NEQ", | 	OpArray:         "ARR", | ||||||
| 	OpGreaterThan:      "GTR", | 	OpMap:           "MAP", | ||||||
| 	OpGreaterThanEqual: "GEQ", | 	OpError:         "ERROR", | ||||||
| 	OpMinus:            "NEG", | 	OpImmutable:     "IMMUT", | ||||||
| 	OpLNot:             "NOT", | 	OpIndex:         "INDEX", | ||||||
| 	OpJumpFalsy:        "JMPF", | 	OpSliceIndex:    "SLICE", | ||||||
| 	OpAndJump:          "ANDJMP", | 	OpCall:          "CALL", | ||||||
| 	OpOrJump:           "ORJMP", | 	OpReturn:        "RET", | ||||||
| 	OpJump:             "JMP", | 	OpGetLocal:      "GETL", | ||||||
| 	OpNull:             "NULL", | 	OpSetLocal:      "SETL", | ||||||
| 	OpGetGlobal:        "GETG", | 	OpDefineLocal:   "DEFL", | ||||||
| 	OpSetGlobal:        "SETG", | 	OpSetSelLocal:   "SETSL", | ||||||
| 	OpSetSelGlobal:     "SETSG", | 	OpGetBuiltin:    "BUILTIN", | ||||||
| 	OpArray:            "ARR", | 	OpClosure:       "CLOSURE", | ||||||
| 	OpMap:              "MAP", | 	OpGetFreePtr:    "GETFP", | ||||||
| 	OpError:            "ERROR", | 	OpGetFree:       "GETF", | ||||||
| 	OpImmutable:        "IMMUT", | 	OpSetFree:       "SETF", | ||||||
| 	OpIndex:            "INDEX", | 	OpGetLocalPtr:   "GETLP", | ||||||
| 	OpSliceIndex:       "SLICE", | 	OpSetSelFree:    "SETSF", | ||||||
| 	OpCall:             "CALL", | 	OpIteratorInit:  "ITER", | ||||||
| 	OpReturn:           "RET", | 	OpIteratorNext:  "ITNXT", | ||||||
| 	OpReturnValue:      "RETVAL", | 	OpIteratorKey:   "ITKEY", | ||||||
| 	OpGetLocal:         "GETL", | 	OpIteratorValue: "ITVAL", | ||||||
| 	OpSetLocal:         "SETL", | 	OpBinaryOp:      "BINARYOP", | ||||||
| 	OpDefineLocal:      "DEFL", |  | ||||||
| 	OpSetSelLocal:      "SETSL", |  | ||||||
| 	OpGetBuiltin:       "BUILTIN", |  | ||||||
| 	OpGetBuiltinModule: "BLTMOD", |  | ||||||
| 	OpClosure:          "CLOSURE", |  | ||||||
| 	OpGetFree:          "GETF", |  | ||||||
| 	OpSetFree:          "SETF", |  | ||||||
| 	OpSetSelFree:       "SETSF", |  | ||||||
| 	OpIteratorInit:     "ITER", |  | ||||||
| 	OpIteratorNext:     "ITNXT", |  | ||||||
| 	OpIteratorKey:      "ITKEY", |  | ||||||
| 	OpIteratorValue:    "ITVAL", |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // OpcodeOperands is the number of operands. | // OpcodeOperands is the number of operands. | ||||||
| var OpcodeOperands = [...][]int{ | var OpcodeOperands = [...][]int{ | ||||||
| 	OpConstant:         {2}, | 	OpConstant:      {2}, | ||||||
| 	OpPop:              {}, | 	OpPop:           {}, | ||||||
| 	OpTrue:             {}, | 	OpTrue:          {}, | ||||||
| 	OpFalse:            {}, | 	OpFalse:         {}, | ||||||
| 	OpAdd:              {}, | 	OpBComplement:   {}, | ||||||
| 	OpSub:              {}, | 	OpEqual:         {}, | ||||||
| 	OpMul:              {}, | 	OpNotEqual:      {}, | ||||||
| 	OpDiv:              {}, | 	OpMinus:         {}, | ||||||
| 	OpRem:              {}, | 	OpLNot:          {}, | ||||||
| 	OpBAnd:             {}, | 	OpJumpFalsy:     {2}, | ||||||
| 	OpBOr:              {}, | 	OpAndJump:       {2}, | ||||||
| 	OpBXor:             {}, | 	OpOrJump:        {2}, | ||||||
| 	OpBAndNot:          {}, | 	OpJump:          {2}, | ||||||
| 	OpBShiftLeft:       {}, | 	OpNull:          {}, | ||||||
| 	OpBShiftRight:      {}, | 	OpGetGlobal:     {2}, | ||||||
| 	OpBComplement:      {}, | 	OpSetGlobal:     {2}, | ||||||
| 	OpEqual:            {}, | 	OpSetSelGlobal:  {2, 1}, | ||||||
| 	OpNotEqual:         {}, | 	OpArray:         {2}, | ||||||
| 	OpGreaterThan:      {}, | 	OpMap:           {2}, | ||||||
| 	OpGreaterThanEqual: {}, | 	OpError:         {}, | ||||||
| 	OpMinus:            {}, | 	OpImmutable:     {}, | ||||||
| 	OpLNot:             {}, | 	OpIndex:         {}, | ||||||
| 	OpJumpFalsy:        {2}, | 	OpSliceIndex:    {}, | ||||||
| 	OpAndJump:          {2}, | 	OpCall:          {1}, | ||||||
| 	OpOrJump:           {2}, | 	OpReturn:        {1}, | ||||||
| 	OpJump:             {2}, | 	OpGetLocal:      {1}, | ||||||
| 	OpNull:             {}, | 	OpSetLocal:      {1}, | ||||||
| 	OpGetGlobal:        {2}, | 	OpDefineLocal:   {1}, | ||||||
| 	OpSetGlobal:        {2}, | 	OpSetSelLocal:   {1, 1}, | ||||||
| 	OpSetSelGlobal:     {2, 1}, | 	OpGetBuiltin:    {1}, | ||||||
| 	OpArray:            {2}, | 	OpClosure:       {2, 1}, | ||||||
| 	OpMap:              {2}, | 	OpGetFreePtr:    {1}, | ||||||
| 	OpError:            {}, | 	OpGetFree:       {1}, | ||||||
| 	OpImmutable:        {}, | 	OpSetFree:       {1}, | ||||||
| 	OpIndex:            {}, | 	OpGetLocalPtr:   {1}, | ||||||
| 	OpSliceIndex:       {}, | 	OpSetSelFree:    {1, 1}, | ||||||
| 	OpCall:             {1}, | 	OpIteratorInit:  {}, | ||||||
| 	OpReturn:           {}, | 	OpIteratorNext:  {}, | ||||||
| 	OpReturnValue:      {}, | 	OpIteratorKey:   {}, | ||||||
| 	OpGetLocal:         {1}, | 	OpIteratorValue: {}, | ||||||
| 	OpSetLocal:         {1}, | 	OpBinaryOp:      {1}, | ||||||
| 	OpDefineLocal:      {1}, |  | ||||||
| 	OpSetSelLocal:      {1, 1}, |  | ||||||
| 	OpGetBuiltin:       {1}, |  | ||||||
| 	OpGetBuiltinModule: {}, |  | ||||||
| 	OpClosure:          {2, 1}, |  | ||||||
| 	OpGetFree:          {1}, |  | ||||||
| 	OpSetFree:          {1}, |  | ||||||
| 	OpSetSelFree:       {1, 1}, |  | ||||||
| 	OpIteratorInit:     {}, |  | ||||||
| 	OpIteratorNext:     {}, |  | ||||||
| 	OpIteratorKey:      {}, |  | ||||||
| 	OpIteratorValue:    {}, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // ReadOperands reads operands from the bytecode. | // 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() | 	fileSet := source.NewFileSet() | ||||||
| 	file := fileSet.AddFile(filename, -1, len(src)) | 	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. | // 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 { | 	if p.trace { | ||||||
| 		defer un(trace(p, "File")) | 		defer un(trace(p, "File")) | ||||||
| 	} | 	} | ||||||
| @@ -71,10 +82,12 @@ func (p *Parser) ParseFile() (*ast.File, error) { | |||||||
| 		return nil, p.errors.Err() | 		return nil, p.errors.Err() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &ast.File{ | 	file = &ast.File{ | ||||||
| 		InputFile: p.file, | 		InputFile: p.file, | ||||||
| 		Stmts:     stmts, | 		Stmts:     stmts, | ||||||
| 	}, nil | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *Parser) parseExpr() ast.Expr { | func (p *Parser) parseExpr() ast.Expr { | ||||||
| @@ -1002,16 +1015,26 @@ func (p *Parser) parseMapElementLit() *ast.MapElementLit { | |||||||
| 		defer un(trace(p, "MapElementLit")) | 		defer un(trace(p, "MapElementLit")) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// key: read identifier token but it's not actually an identifier | 	pos := p.pos | ||||||
| 	ident := p.parseIdent() | 	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) | 	colonPos := p.expect(token.Colon) | ||||||
|  |  | ||||||
| 	valueExpr := p.parseExpr() | 	valueExpr := p.parseExpr() | ||||||
|  |  | ||||||
| 	return &ast.MapElementLit{ | 	return &ast.MapElementLit{ | ||||||
| 		Key:      ident.Name, | 		Key:      name, | ||||||
| 		KeyPos:   ident.NamePos, | 		KeyPos:   pos, | ||||||
| 		ColonPos: colonPos, | 		ColonPos: colonPos, | ||||||
| 		Value:    valueExpr, | 		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. | // SymbolTable represents a symbol table. | ||||||
| type SymbolTable struct { | type SymbolTable struct { | ||||||
| 	parent        *SymbolTable | 	parent         *SymbolTable | ||||||
| 	block         bool | 	block          bool | ||||||
| 	store         map[string]*Symbol | 	store          map[string]*Symbol | ||||||
| 	numDefinition int | 	numDefinition  int | ||||||
| 	maxDefinition int | 	maxDefinition  int | ||||||
| 	freeSymbols   []*Symbol | 	freeSymbols    []*Symbol | ||||||
|  | 	builtinSymbols []*Symbol | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewSymbolTable creates a SymbolTable. | // NewSymbolTable creates a SymbolTable. | ||||||
| @@ -37,6 +38,10 @@ func (t *SymbolTable) Define(name string) *Symbol { | |||||||
|  |  | ||||||
| // DefineBuiltin adds a symbol for builtin function. | // DefineBuiltin adds a symbol for builtin function. | ||||||
| func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol { | func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol { | ||||||
|  | 	if t.parent != nil { | ||||||
|  | 		return t.parent.DefineBuiltin(index, name) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	symbol := &Symbol{ | 	symbol := &Symbol{ | ||||||
| 		Name:  name, | 		Name:  name, | ||||||
| 		Index: index, | 		Index: index, | ||||||
| @@ -45,6 +50,8 @@ func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol { | |||||||
|  |  | ||||||
| 	t.store[name] = symbol | 	t.store[name] = symbol | ||||||
|  |  | ||||||
|  | 	t.builtinSymbols = append(t.builtinSymbols, symbol) | ||||||
|  |  | ||||||
| 	return symbol | 	return symbol | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -57,9 +64,7 @@ func (t *SymbolTable) Resolve(name string) (symbol *Symbol, depth int, ok bool) | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if !t.block { | 		depth++ | ||||||
| 			depth++ |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// if symbol is defined in parent table and if it's not global/builtin | 		// if symbol is defined in parent table and if it's not global/builtin | ||||||
| 		// then it's free variable. | 		// then it's free variable. | ||||||
| @@ -101,6 +106,15 @@ func (t *SymbolTable) FreeSymbols() []*Symbol { | |||||||
| 	return t.freeSymbols | 	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. | // Names returns the name of all the symbols. | ||||||
| func (t *SymbolTable) Names() []string { | func (t *SymbolTable) Names() []string { | ||||||
| 	var 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 | package objects | ||||||
|  |  | ||||||
|  | import "github.com/d5/tengo" | ||||||
|  |  | ||||||
| func builtinString(args ...Object) (Object, error) { | func builtinString(args ...Object) (Object, error) { | ||||||
| 	argsLen := len(args) | 	argsLen := len(args) | ||||||
| 	if !(argsLen == 1 || argsLen == 2) { | 	if !(argsLen == 1 || argsLen == 2) { | ||||||
| @@ -12,6 +14,10 @@ func builtinString(args ...Object) (Object, error) { | |||||||
|  |  | ||||||
| 	v, ok := ToString(args[0]) | 	v, ok := ToString(args[0]) | ||||||
| 	if ok { | 	if ok { | ||||||
|  | 		if len(v) > tengo.MaxStringLen { | ||||||
|  | 			return nil, ErrStringLimit | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return &String{Value: v}, nil | 		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 | 	// bytes(N) => create a new bytes with given size N | ||||||
| 	if n, ok := args[0].(*Int); ok { | 	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 | 		return &Bytes{Value: make([]byte, int(n.Value))}, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	v, ok := ToByteSlice(args[0]) | 	v, ok := ToByteSlice(args[0]) | ||||||
| 	if ok { | 	if ok { | ||||||
|  | 		if len(v) > tengo.MaxBytesLen { | ||||||
|  | 			return nil, ErrBytesLimit | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return &Bytes{Value: v}, nil | 		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 | 	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 | package objects | ||||||
|  |  | ||||||
| // NamedBuiltinFunc is a named builtin function. |  | ||||||
| type NamedBuiltinFunc struct { |  | ||||||
| 	Name string |  | ||||||
| 	Func CallableFunc |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Builtins contains all default builtin functions. | // Builtins contains all default builtin functions. | ||||||
| var Builtins = []NamedBuiltinFunc{ | // Use GetBuiltinFunctions instead of accessing Builtins directly. | ||||||
|  | var Builtins = []*BuiltinFunction{ | ||||||
| 	{ | 	{ | ||||||
| 		Name: "print", | 		Name:  "len", | ||||||
| 		Func: builtinPrint, | 		Value: builtinLen, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "printf", | 		Name:  "copy", | ||||||
| 		Func: builtinPrintf, | 		Value: builtinCopy, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "sprintf", | 		Name:  "append", | ||||||
| 		Func: builtinSprintf, | 		Value: builtinAppend, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "len", | 		Name:  "string", | ||||||
| 		Func: builtinLen, | 		Value: builtinString, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "copy", | 		Name:  "int", | ||||||
| 		Func: builtinCopy, | 		Value: builtinInt, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "append", | 		Name:  "bool", | ||||||
| 		Func: builtinAppend, | 		Value: builtinBool, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "string", | 		Name:  "float", | ||||||
| 		Func: builtinString, | 		Value: builtinFloat, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "int", | 		Name:  "char", | ||||||
| 		Func: builtinInt, | 		Value: builtinChar, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "bool", | 		Name:  "bytes", | ||||||
| 		Func: builtinBool, | 		Value: builtinBytes, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "float", | 		Name:  "time", | ||||||
| 		Func: builtinFloat, | 		Value: builtinTime, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "char", | 		Name:  "is_int", | ||||||
| 		Func: builtinChar, | 		Value: builtinIsInt, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "bytes", | 		Name:  "is_float", | ||||||
| 		Func: builtinBytes, | 		Value: builtinIsFloat, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "time", | 		Name:  "is_string", | ||||||
| 		Func: builtinTime, | 		Value: builtinIsString, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_int", | 		Name:  "is_bool", | ||||||
| 		Func: builtinIsInt, | 		Value: builtinIsBool, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_float", | 		Name:  "is_char", | ||||||
| 		Func: builtinIsFloat, | 		Value: builtinIsChar, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_string", | 		Name:  "is_bytes", | ||||||
| 		Func: builtinIsString, | 		Value: builtinIsBytes, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_bool", | 		Name:  "is_array", | ||||||
| 		Func: builtinIsBool, | 		Value: builtinIsArray, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_char", | 		Name:  "is_immutable_array", | ||||||
| 		Func: builtinIsChar, | 		Value: builtinIsImmutableArray, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_bytes", | 		Name:  "is_map", | ||||||
| 		Func: builtinIsBytes, | 		Value: builtinIsMap, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_array", | 		Name:  "is_immutable_map", | ||||||
| 		Func: builtinIsArray, | 		Value: builtinIsImmutableMap, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_immutable_array", | 		Name:  "is_iterable", | ||||||
| 		Func: builtinIsImmutableArray, | 		Value: builtinIsIterable, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_map", | 		Name:  "is_time", | ||||||
| 		Func: builtinIsMap, | 		Value: builtinIsTime, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_immutable_map", | 		Name:  "is_error", | ||||||
| 		Func: builtinIsImmutableMap, | 		Value: builtinIsError, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_time", | 		Name:  "is_undefined", | ||||||
| 		Func: builtinIsTime, | 		Value: builtinIsUndefined, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_error", | 		Name:  "is_function", | ||||||
| 		Func: builtinIsError, | 		Value: builtinIsFunction, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_undefined", | 		Name:  "is_callable", | ||||||
| 		Func: builtinIsUndefined, | 		Value: builtinIsCallable, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "is_function", | 		Name:  "type_name", | ||||||
| 		Func: builtinIsFunction, | 		Value: builtinTypeName, | ||||||
| 	}, |  | ||||||
| 	{ |  | ||||||
| 		Name: "is_callable", |  | ||||||
| 		Func: builtinIsCallable, |  | ||||||
| 	}, |  | ||||||
| 	{ |  | ||||||
| 		Name: "to_json", |  | ||||||
| 		Func: builtinToJSON, |  | ||||||
| 	}, |  | ||||||
| 	{ |  | ||||||
| 		Name: "from_json", |  | ||||||
| 		Func: builtinFromJSON, |  | ||||||
| 	}, |  | ||||||
| 	{ |  | ||||||
| 		Name: "type_name", |  | ||||||
| 		Func: 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 ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| 	"github.com/d5/tengo/compiler/token" | 	"github.com/d5/tengo/compiler/token" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -27,6 +28,10 @@ func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) { | |||||||
| 	case token.Add: | 	case token.Add: | ||||||
| 		switch rhs := rhs.(type) { | 		switch rhs := rhs.(type) { | ||||||
| 		case *Bytes: | 		case *Bytes: | ||||||
|  | 			if len(o.Value)+len(rhs.Value) > tengo.MaxBytesLen { | ||||||
|  | 				return nil, ErrBytesLimit | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			return &Bytes{Value: append(o.Value, rhs.Value...)}, nil | 			return &Bytes{Value: append(o.Value, rhs.Value...)}, nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -74,3 +79,11 @@ func (o *Bytes) IndexGet(index Object) (res Object, err error) { | |||||||
|  |  | ||||||
| 	return | 	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 | package objects | ||||||
|  |  | ||||||
| // CallableFunc is a function signature for the callable functions. | // 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. | // Closure represents a function closure. | ||||||
| type Closure struct { | type Closure struct { | ||||||
| 	Fn   *CompiledFunction | 	Fn   *CompiledFunction | ||||||
| 	Free []*Object | 	Free []*ObjectPtr | ||||||
| } | } | ||||||
|  |  | ||||||
| // TypeName returns the name of the type. | // 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 { | func (o *Closure) Copy() Object { | ||||||
| 	return &Closure{ | 	return &Closure{ | ||||||
| 		Fn:   o.Fn.Copy().(*CompiledFunction), | 		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 { | func (o *CompiledFunction) Equals(x Object) bool { | ||||||
| 	return false | 	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 | package objects | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ToString will try to convert object o to string value. | // ToString will try to convert object o to string value. | ||||||
| @@ -156,8 +159,8 @@ func ToTime(o Object) (v time.Time, ok bool) { | |||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| // objectToInterface attempts to convert an object o to an interface{} value | // ToInterface attempts to convert an object o to an interface{} value | ||||||
| func objectToInterface(o Object) (res interface{}) { | func ToInterface(o Object) (res interface{}) { | ||||||
| 	switch o := o.(type) { | 	switch o := o.(type) { | ||||||
| 	case *Int: | 	case *Int: | ||||||
| 		res = o.Value | 		res = o.Value | ||||||
| @@ -174,13 +177,29 @@ func objectToInterface(o Object) (res interface{}) { | |||||||
| 	case *Array: | 	case *Array: | ||||||
| 		res = make([]interface{}, len(o.Value)) | 		res = make([]interface{}, len(o.Value)) | ||||||
| 		for i, val := range 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: | 	case *Map: | ||||||
| 		res = make(map[string]interface{}) | 		res = make(map[string]interface{}) | ||||||
| 		for key, v := range o.Value { | 		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: | 	case Object: | ||||||
| 		return o | 		return o | ||||||
| 	} | 	} | ||||||
| @@ -194,6 +213,9 @@ func FromInterface(v interface{}) (Object, error) { | |||||||
| 	case nil: | 	case nil: | ||||||
| 		return UndefinedValue, nil | 		return UndefinedValue, nil | ||||||
| 	case string: | 	case string: | ||||||
|  | 		if len(v) > tengo.MaxStringLen { | ||||||
|  | 			return nil, ErrStringLimit | ||||||
|  | 		} | ||||||
| 		return &String{Value: v}, nil | 		return &String{Value: v}, nil | ||||||
| 	case int64: | 	case int64: | ||||||
| 		return &Int{Value: v}, nil | 		return &Int{Value: v}, nil | ||||||
| @@ -211,6 +233,9 @@ func FromInterface(v interface{}) (Object, error) { | |||||||
| 	case float64: | 	case float64: | ||||||
| 		return &Float{Value: v}, nil | 		return &Float{Value: v}, nil | ||||||
| 	case []byte: | 	case []byte: | ||||||
|  | 		if len(v) > tengo.MaxBytesLen { | ||||||
|  | 			return nil, ErrBytesLimit | ||||||
|  | 		} | ||||||
| 		return &Bytes{Value: v}, nil | 		return &Bytes{Value: v}, nil | ||||||
| 	case error: | 	case error: | ||||||
| 		return &Error{Value: &String{Value: v.Error()}}, nil | 		return &Error{Value: &String{Value: v.Error()}}, nil | ||||||
| @@ -243,6 +268,8 @@ func FromInterface(v interface{}) (Object, error) { | |||||||
| 		return &Time{Value: v}, nil | 		return &Time{Value: v}, nil | ||||||
| 	case Object: | 	case Object: | ||||||
| 		return v, nil | 		return v, nil | ||||||
|  | 	case CallableFunc: | ||||||
|  | 		return &UserFunction{Value: v}, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil, fmt.Errorf("cannot convert to object: %T", v) | 	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. | // ErrWrongNumArguments represents a wrong number of arguments error. | ||||||
| var ErrWrongNumArguments = errors.New("wrong number of arguments") | 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. | // ErrInvalidArgumentType represents an invalid argument value type error. | ||||||
| type ErrInvalidArgumentType struct { | type ErrInvalidArgumentType struct { | ||||||
| 	Name     string | 	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 ( | import ( | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| 	"github.com/d5/tengo/compiler/token" | 	"github.com/d5/tengo/compiler/token" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -28,9 +29,16 @@ func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) { | |||||||
| 	case token.Add: | 	case token.Add: | ||||||
| 		switch rhs := rhs.(type) { | 		switch rhs := rhs.(type) { | ||||||
| 		case *String: | 		case *String: | ||||||
|  | 			if len(o.Value)+len(rhs.Value) > tengo.MaxStringLen { | ||||||
|  | 				return nil, ErrStringLimit | ||||||
|  | 			} | ||||||
| 			return &String{Value: o.Value + rhs.Value}, nil | 			return &String{Value: o.Value + rhs.Value}, nil | ||||||
| 		default: | 		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. | // UserFunction represents a user function. | ||||||
| type UserFunction struct { | type UserFunction struct { | ||||||
| 	Name  string | 	Name       string | ||||||
| 	Value CallableFunc | 	Value      CallableFunc | ||||||
|  | 	EncodingID string | ||||||
| } | } | ||||||
|  |  | ||||||
| // TypeName returns the name of the type. | // 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. | // ErrStackOverflow is a stack overflow error. | ||||||
| var ErrStackOverflow = errors.New("stack overflow") | 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. | // Frame represents a function call frame. | ||||||
| type Frame struct { | type Frame struct { | ||||||
| 	fn          *objects.CompiledFunction | 	fn          *objects.CompiledFunction | ||||||
| 	freeVars    []*objects.Object | 	freeVars    []*objects.ObjectPtr | ||||||
| 	ip          int | 	ip          int | ||||||
| 	basePointer 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 ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
| 	"github.com/d5/tengo/compiler" | 	"github.com/d5/tengo/compiler" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| @@ -12,26 +13,39 @@ import ( | |||||||
| // Compiled is a compiled instance of the user script. | // Compiled is a compiled instance of the user script. | ||||||
| // Use Script.Compile() to create Compiled object. | // Use Script.Compile() to create Compiled object. | ||||||
| type Compiled struct { | type Compiled struct { | ||||||
| 	symbolTable *compiler.SymbolTable | 	globalIndexes map[string]int // global symbol name to index | ||||||
| 	machine     *runtime.VM | 	bytecode      *compiler.Bytecode | ||||||
|  | 	globals       []objects.Object | ||||||
|  | 	maxAllocs     int64 | ||||||
|  | 	lock          sync.RWMutex | ||||||
| } | } | ||||||
|  |  | ||||||
| // Run executes the compiled script in the virtual machine. | // Run executes the compiled script in the virtual machine. | ||||||
| func (c *Compiled) Run() error { | 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. | // RunContext is like Run but includes a context. | ||||||
| func (c *Compiled) RunContext(ctx context.Context) (err error) { | 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) | 	ch := make(chan error, 1) | ||||||
|  |  | ||||||
| 	go func() { | 	go func() { | ||||||
| 		ch <- c.machine.Run() | 		ch <- v.Run() | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	select { | 	select { | ||||||
| 	case <-ctx.Done(): | 	case <-ctx.Done(): | ||||||
| 		c.machine.Abort() | 		v.Abort() | ||||||
| 		<-ch | 		<-ch | ||||||
| 		err = ctx.Err() | 		err = ctx.Err() | ||||||
| 	case err = <-ch: | 	case err = <-ch: | ||||||
| @@ -40,30 +54,58 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) { | |||||||
| 	return | 	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. | // IsDefined returns true if the variable name is defined (has value) before or after the execution. | ||||||
| func (c *Compiled) IsDefined(name string) bool { | 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 { | 	if !ok { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	v := c.machine.Globals()[symbol.Index] | 	v := c.globals[idx] | ||||||
| 	if v == nil { | 	if v == nil { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return *v != objects.UndefinedValue | 	return v != objects.UndefinedValue | ||||||
| } | } | ||||||
|  |  | ||||||
| // Get returns a variable identified by the name. | // Get returns a variable identified by the name. | ||||||
| func (c *Compiled) Get(name string) *Variable { | func (c *Compiled) Get(name string) *Variable { | ||||||
| 	value := &objects.UndefinedValue | 	c.lock.RLock() | ||||||
|  | 	defer c.lock.RUnlock() | ||||||
|  |  | ||||||
| 	symbol, _, ok := c.symbolTable.Resolve(name) | 	value := objects.UndefinedValue | ||||||
| 	if ok && symbol.Scope == compiler.ScopeGlobal { |  | ||||||
| 		value = c.machine.Globals()[symbol.Index] | 	if idx, ok := c.globalIndexes[name]; ok { | ||||||
|  | 		value = c.globals[idx] | ||||||
| 		if value == nil { | 		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. | // GetAll returns all the variables that are defined by the compiled script. | ||||||
| func (c *Compiled) GetAll() []*Variable { | func (c *Compiled) GetAll() []*Variable { | ||||||
| 	var vars []*Variable | 	c.lock.RLock() | ||||||
| 	for _, name := range c.symbolTable.Names() { | 	defer c.lock.RUnlock() | ||||||
| 		symbol, _, ok := c.symbolTable.Resolve(name) |  | ||||||
| 		if ok && symbol.Scope == compiler.ScopeGlobal { |  | ||||||
| 			value := c.machine.Globals()[symbol.Index] |  | ||||||
| 			if value == nil { |  | ||||||
| 				value = &objects.UndefinedValue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			vars = append(vars, &Variable{ | 	var vars []*Variable | ||||||
| 				name:  name, |  | ||||||
| 				value: value, | 	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 | 	return vars | ||||||
| @@ -97,17 +140,20 @@ func (c *Compiled) GetAll() []*Variable { | |||||||
| // Set replaces the value of a global variable identified by the name. | // 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. | // An error will be returned if the name was not defined during compilation. | ||||||
| func (c *Compiled) Set(name string, value interface{}) error { | func (c *Compiled) Set(name string, value interface{}) error { | ||||||
|  | 	c.lock.Lock() | ||||||
|  | 	defer c.lock.Unlock() | ||||||
|  |  | ||||||
| 	obj, err := objects.FromInterface(value) | 	obj, err := objects.FromInterface(value) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	symbol, _, ok := c.symbolTable.Resolve(name) | 	idx, ok := c.globalIndexes[name] | ||||||
| 	if !ok || symbol.Scope != compiler.ScopeGlobal { | 	if !ok { | ||||||
| 		return fmt.Errorf("'%s' is not defined", name) | 		return fmt.Errorf("'%s' is not defined", name) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	c.machine.Globals()[symbol.Index] = &obj | 	c.globals[idx] = obj | ||||||
|  |  | ||||||
| 	return nil | 	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/compiler/source" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| 	"github.com/d5/tengo/runtime" | 	"github.com/d5/tengo/runtime" | ||||||
| 	"github.com/d5/tengo/stdlib" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Script can simplify compilation and execution of embedded scripts. | // Script can simplify compilation and execution of embedded scripts. | ||||||
| type Script struct { | type Script struct { | ||||||
| 	variables         map[string]*Variable | 	variables        map[string]*Variable | ||||||
| 	removedBuiltins   map[string]bool | 	modules          *objects.ModuleMap | ||||||
| 	removedStdModules map[string]bool | 	input            []byte | ||||||
| 	userModuleLoader  compiler.ModuleLoader | 	maxAllocs        int64 | ||||||
| 	input             []byte | 	maxConstObjects  int | ||||||
|  | 	enableFileImport bool | ||||||
| } | } | ||||||
|  |  | ||||||
| // New creates a Script instance with an input script. | // New creates a Script instance with an input script. | ||||||
| func New(input []byte) *Script { | func New(input []byte) *Script { | ||||||
| 	return &Script{ | 	return &Script{ | ||||||
| 		variables: make(map[string]*Variable), | 		variables:       make(map[string]*Variable), | ||||||
| 		input:     input, | 		input:           input, | ||||||
|  | 		maxAllocs:       -1, | ||||||
|  | 		maxConstObjects: -1, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -38,7 +40,7 @@ func (s *Script) Add(name string, value interface{}) error { | |||||||
|  |  | ||||||
| 	s.variables[name] = &Variable{ | 	s.variables[name] = &Variable{ | ||||||
| 		name:  name, | 		name:  name, | ||||||
| 		value: &obj, | 		value: obj, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -56,32 +58,31 @@ func (s *Script) Remove(name string) bool { | |||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
|  |  | ||||||
| // DisableBuiltinFunction disables a builtin function. | // SetImports sets import modules. | ||||||
| func (s *Script) DisableBuiltinFunction(name string) { | func (s *Script) SetImports(modules *objects.ModuleMap) { | ||||||
| 	if s.removedBuiltins == nil { | 	s.modules = modules | ||||||
| 		s.removedBuiltins = make(map[string]bool) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s.removedBuiltins[name] = true |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // DisableStdModule disables a standard library module. | // SetMaxAllocs sets the maximum number of objects allocations during the run time. | ||||||
| func (s *Script) DisableStdModule(name string) { | // Compiled script will return runtime.ErrObjectAllocLimit error if it exceeds this limit. | ||||||
| 	if s.removedStdModules == nil { | func (s *Script) SetMaxAllocs(n int64) { | ||||||
| 		s.removedStdModules = make(map[string]bool) | 	s.maxAllocs = n | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s.removedStdModules[name] = true |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // SetUserModuleLoader sets the user module loader for the compiler. | // SetMaxConstObjects sets the maximum number of objects in the compiled constants. | ||||||
| func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) { | func (s *Script) SetMaxConstObjects(n int) { | ||||||
| 	s.userModuleLoader = loader | 	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. | // Compile compiles the script with all the defined variables, and, returns Compiled object. | ||||||
| func (s *Script) Compile() (*Compiled, error) { | func (s *Script) Compile() (*Compiled, error) { | ||||||
| 	symbolTable, stdModules, globals, err := s.prepCompile() | 	symbolTable, globals, err := s.prepCompile() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -92,22 +93,44 @@ func (s *Script) Compile() (*Compiled, error) { | |||||||
| 	p := parser.NewParser(srcFile, s.input, nil) | 	p := parser.NewParser(srcFile, s.input, nil) | ||||||
| 	file, err := p.ParseFile() | 	file, err := p.ParseFile() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("parse error: %s", err.Error()) | 		return nil, err | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	c := compiler.NewCompiler(srcFile, symbolTable, nil, stdModules, nil) |  | ||||||
|  |  | ||||||
| 	if s.userModuleLoader != nil { |  | ||||||
| 		c.SetModuleLoader(s.userModuleLoader) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	c := compiler.NewCompiler(srcFile, symbolTable, nil, s.modules, nil) | ||||||
|  | 	c.EnableFileImport(s.enableFileImport) | ||||||
| 	if err := c.Compile(file); err != nil { | 	if err := c.Compile(file); err != nil { | ||||||
| 		return nil, err | 		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{ | 	return &Compiled{ | ||||||
| 		symbolTable: symbolTable, | 		globalIndexes: globalIndexes, | ||||||
| 		machine:     runtime.NewVM(c.Bytecode(), globals, nil), | 		bytecode:      bytecode, | ||||||
|  | 		globals:       globals, | ||||||
|  | 		maxAllocs:     s.maxAllocs, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -136,7 +159,7 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error) | |||||||
| 	return | 	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 | 	var names []string | ||||||
| 	for name := range s.variables { | 	for name := range s.variables { | ||||||
| 		names = append(names, name) | 		names = append(names, name) | ||||||
| @@ -144,19 +167,10 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules ma | |||||||
|  |  | ||||||
| 	symbolTable = compiler.NewSymbolTable() | 	symbolTable = compiler.NewSymbolTable() | ||||||
| 	for idx, fn := range objects.Builtins { | 	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) | 	globals = make([]objects.Object, runtime.GlobalsSize) | ||||||
| 	for name := range stdlib.Modules { |  | ||||||
| 		if !s.removedStdModules[name] { |  | ||||||
| 			stdModules[name] = true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	globals = make([]*objects.Object, runtime.GlobalsSize, runtime.GlobalsSize) |  | ||||||
|  |  | ||||||
| 	for idx, name := range names { | 	for idx, name := range names { | ||||||
| 		symbol := symbolTable.Define(name) | 		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. | // Variable is a user-defined variable for the script. | ||||||
| type Variable struct { | type Variable struct { | ||||||
| 	name  string | 	name  string | ||||||
| 	value *objects.Object | 	value objects.Object | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewVariable creates a Variable. | // NewVariable creates a Variable. | ||||||
| @@ -21,7 +21,7 @@ func NewVariable(name string, value interface{}) (*Variable, error) { | |||||||
|  |  | ||||||
| 	return &Variable{ | 	return &Variable{ | ||||||
| 		name:  name, | 		name:  name, | ||||||
| 		value: &obj, | 		value: obj, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -32,18 +32,18 @@ func (v *Variable) Name() string { | |||||||
|  |  | ||||||
| // Value returns an empty interface of the variable value. | // Value returns an empty interface of the variable value. | ||||||
| func (v *Variable) Value() interface{} { | func (v *Variable) Value() interface{} { | ||||||
| 	return objectToInterface(*v.value) | 	return objects.ToInterface(v.value) | ||||||
| } | } | ||||||
|  |  | ||||||
| // ValueType returns the name of the value type. | // ValueType returns the name of the value type. | ||||||
| func (v *Variable) ValueType() string { | func (v *Variable) ValueType() string { | ||||||
| 	return (*v.value).TypeName() | 	return v.value.TypeName() | ||||||
| } | } | ||||||
|  |  | ||||||
| // Int returns int value of the variable value. | // Int returns int value of the variable value. | ||||||
| // It returns 0 if the value is not convertible to int. | // It returns 0 if the value is not convertible to int. | ||||||
| func (v *Variable) Int() int { | func (v *Variable) Int() int { | ||||||
| 	c, _ := objects.ToInt(*v.value) | 	c, _ := objects.ToInt(v.value) | ||||||
|  |  | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| @@ -51,7 +51,7 @@ func (v *Variable) Int() int { | |||||||
| // Int64 returns int64 value of the variable value. | // Int64 returns int64 value of the variable value. | ||||||
| // It returns 0 if the value is not convertible to int64. | // It returns 0 if the value is not convertible to int64. | ||||||
| func (v *Variable) Int64() int64 { | func (v *Variable) Int64() int64 { | ||||||
| 	c, _ := objects.ToInt64(*v.value) | 	c, _ := objects.ToInt64(v.value) | ||||||
|  |  | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| @@ -59,7 +59,7 @@ func (v *Variable) Int64() int64 { | |||||||
| // Float returns float64 value of the variable value. | // Float returns float64 value of the variable value. | ||||||
| // It returns 0.0 if the value is not convertible to float64. | // It returns 0.0 if the value is not convertible to float64. | ||||||
| func (v *Variable) Float() float64 { | func (v *Variable) Float() float64 { | ||||||
| 	c, _ := objects.ToFloat64(*v.value) | 	c, _ := objects.ToFloat64(v.value) | ||||||
|  |  | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| @@ -67,7 +67,7 @@ func (v *Variable) Float() float64 { | |||||||
| // Char returns rune value of the variable value. | // Char returns rune value of the variable value. | ||||||
| // It returns 0 if the value is not convertible to rune. | // It returns 0 if the value is not convertible to rune. | ||||||
| func (v *Variable) Char() rune { | func (v *Variable) Char() rune { | ||||||
| 	c, _ := objects.ToRune(*v.value) | 	c, _ := objects.ToRune(v.value) | ||||||
|  |  | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| @@ -75,7 +75,7 @@ func (v *Variable) Char() rune { | |||||||
| // Bool returns bool value of the variable value. | // Bool returns bool value of the variable value. | ||||||
| // It returns 0 if the value is not convertible to bool. | // It returns 0 if the value is not convertible to bool. | ||||||
| func (v *Variable) Bool() bool { | func (v *Variable) Bool() bool { | ||||||
| 	c, _ := objects.ToBool(*v.value) | 	c, _ := objects.ToBool(v.value) | ||||||
|  |  | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| @@ -83,11 +83,11 @@ func (v *Variable) Bool() bool { | |||||||
| // Array returns []interface value of the variable value. | // Array returns []interface value of the variable value. | ||||||
| // It returns 0 if the value is not convertible to []interface. | // It returns 0 if the value is not convertible to []interface. | ||||||
| func (v *Variable) Array() []interface{} { | func (v *Variable) Array() []interface{} { | ||||||
| 	switch val := (*v.value).(type) { | 	switch val := v.value.(type) { | ||||||
| 	case *objects.Array: | 	case *objects.Array: | ||||||
| 		var arr []interface{} | 		var arr []interface{} | ||||||
| 		for _, e := range val.Value { | 		for _, e := range val.Value { | ||||||
| 			arr = append(arr, objectToInterface(e)) | 			arr = append(arr, objects.ToInterface(e)) | ||||||
| 		} | 		} | ||||||
| 		return arr | 		return arr | ||||||
| 	} | 	} | ||||||
| @@ -98,11 +98,11 @@ func (v *Variable) Array() []interface{} { | |||||||
| // Map returns map[string]interface{} value of the variable value. | // Map returns map[string]interface{} value of the variable value. | ||||||
| // It returns 0 if the value is not convertible to map[string]interface{}. | // It returns 0 if the value is not convertible to map[string]interface{}. | ||||||
| func (v *Variable) Map() map[string]interface{} { | func (v *Variable) Map() map[string]interface{} { | ||||||
| 	switch val := (*v.value).(type) { | 	switch val := v.value.(type) { | ||||||
| 	case *objects.Map: | 	case *objects.Map: | ||||||
| 		kv := make(map[string]interface{}) | 		kv := make(map[string]interface{}) | ||||||
| 		for mk, mv := range val.Value { | 		for mk, mv := range val.Value { | ||||||
| 			kv[mk] = objectToInterface(mv) | 			kv[mk] = objects.ToInterface(mv) | ||||||
| 		} | 		} | ||||||
| 		return kv | 		return kv | ||||||
| 	} | 	} | ||||||
| @@ -113,7 +113,7 @@ func (v *Variable) Map() map[string]interface{} { | |||||||
| // String returns string value of the variable value. | // String returns string value of the variable value. | ||||||
| // It returns 0 if the value is not convertible to string. | // It returns 0 if the value is not convertible to string. | ||||||
| func (v *Variable) String() string { | func (v *Variable) String() string { | ||||||
| 	c, _ := objects.ToString(*v.value) | 	c, _ := objects.ToString(v.value) | ||||||
|  |  | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| @@ -121,7 +121,7 @@ func (v *Variable) String() string { | |||||||
| // Bytes returns a byte slice of the variable value. | // Bytes returns a byte slice of the variable value. | ||||||
| // It returns nil if the value is not convertible to byte slice. | // It returns nil if the value is not convertible to byte slice. | ||||||
| func (v *Variable) Bytes() []byte { | func (v *Variable) Bytes() []byte { | ||||||
| 	c, _ := objects.ToByteSlice(*v.value) | 	c, _ := objects.ToByteSlice(v.value) | ||||||
|  |  | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| @@ -129,7 +129,7 @@ func (v *Variable) Bytes() []byte { | |||||||
| // Error returns an error if the underlying value is error object. | // Error returns an error if the underlying value is error object. | ||||||
| // If not, this returns nil. | // If not, this returns nil. | ||||||
| func (v *Variable) Error() error { | func (v *Variable) Error() error { | ||||||
| 	err, ok := (*v.value).(*objects.Error) | 	err, ok := v.value.(*objects.Error) | ||||||
| 	if ok { | 	if ok { | ||||||
| 		return errors.New(err.String()) | 		return errors.New(err.String()) | ||||||
| 	} | 	} | ||||||
| @@ -140,10 +140,10 @@ func (v *Variable) Error() error { | |||||||
| // Object returns an underlying Object of the variable value. | // Object returns an underlying Object of the variable value. | ||||||
| // Note that returned Object is a copy of an actual Object used in the script. | // Note that returned Object is a copy of an actual Object used in the script. | ||||||
| func (v *Variable) Object() objects.Object { | func (v *Variable) Object() objects.Object { | ||||||
| 	return *v.value | 	return v.value | ||||||
| } | } | ||||||
|  |  | ||||||
| // IsUndefined returns true if the underlying value is undefined. | // IsUndefined returns true if the underlying value is undefined. | ||||||
| func (v *Variable) IsUndefined() bool { | 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 ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -124,7 +125,13 @@ func FuncARS(fn func() string) objects.CallableFunc { | |||||||
| 			return nil, objects.ErrWrongNumArguments | 			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 | 			return wrapError(err), nil | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if len(res) > tengo.MaxStringLen { | ||||||
|  | 			return nil, objects.ErrStringLimit | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return &objects.String{Value: res}, nil | 		return &objects.String{Value: res}, nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -158,6 +169,10 @@ func FuncARYE(fn func() ([]byte, error)) objects.CallableFunc { | |||||||
| 			return wrapError(err), nil | 			return wrapError(err), nil | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if len(res) > tengo.MaxBytesLen { | ||||||
|  | 			return nil, objects.ErrBytesLimit | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return &objects.Bytes{Value: res}, nil | 		return &objects.Bytes{Value: res}, nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -183,8 +198,12 @@ func FuncARSs(fn func() []string) objects.CallableFunc { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		arr := &objects.Array{} | 		arr := &objects.Array{} | ||||||
| 		for _, osArg := range fn() { | 		for _, elem := range fn() { | ||||||
| 			arr.Value = append(arr.Value, &objects.String{Value: osArg}) | 			if len(elem) > tengo.MaxStringLen { | ||||||
|  | 				return nil, objects.ErrStringLimit | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			arr.Value = append(arr.Value, &objects.String{Value: elem}) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return arr, nil | 		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) | 		res := fn(s1) | ||||||
|  |  | ||||||
| 		arr := &objects.Array{} | 		arr := &objects.Array{} | ||||||
| 		for _, osArg := range res { | 		for _, elem := range res { | ||||||
| 			arr.Value = append(arr.Value, &objects.String{Value: osArg}) | 			if len(elem) > tengo.MaxStringLen { | ||||||
|  | 				return nil, objects.ErrStringLimit | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			arr.Value = append(arr.Value, &objects.String{Value: elem}) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return arr, nil | 		return arr, nil | ||||||
| @@ -546,6 +575,10 @@ func FuncASRSE(fn func(string) (string, error)) objects.CallableFunc { | |||||||
| 			return wrapError(err), nil | 			return wrapError(err), nil | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if len(res) > tengo.MaxStringLen { | ||||||
|  | 			return nil, objects.ErrStringLimit | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return &objects.String{Value: res}, nil | 		return &objects.String{Value: res}, nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -628,6 +661,10 @@ func FuncASSRSs(fn func(string, string) []string) objects.CallableFunc { | |||||||
|  |  | ||||||
| 		arr := &objects.Array{} | 		arr := &objects.Array{} | ||||||
| 		for _, res := range fn(s1, s2) { | 		for _, res := range fn(s1, s2) { | ||||||
|  | 			if len(res) > tengo.MaxStringLen { | ||||||
|  | 				return nil, objects.ErrStringLimit | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			arr.Value = append(arr.Value, &objects.String{Value: res}) | 			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{} | 		arr := &objects.Array{} | ||||||
| 		for _, res := range fn(s1, s2, i3) { | 		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}) | 			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{} | 		arr := &objects.Array{} | ||||||
| 		for _, r := range res { | 		for _, r := range res { | ||||||
|  | 			if len(r) > tengo.MaxStringLen { | ||||||
|  | 				return nil, objects.ErrStringLimit | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			arr.Value = append(arr.Value, &objects.String{Value: r}) | 			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" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -39,14 +40,14 @@ var osModule = map[string]objects.Object{ | |||||||
| 	"seek_set":            &objects.Int{Value: int64(io.SeekStart)}, | 	"seek_set":            &objects.Int{Value: int64(io.SeekStart)}, | ||||||
| 	"seek_cur":            &objects.Int{Value: int64(io.SeekCurrent)}, | 	"seek_cur":            &objects.Int{Value: int64(io.SeekCurrent)}, | ||||||
| 	"seek_end":            &objects.Int{Value: int64(io.SeekEnd)}, | 	"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 | 	"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 | 	"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() | 	"clearenv":            &objects.UserFunction{Name: "clearenv", Value: FuncAR(os.Clearenv)},            // clearenv() | ||||||
| 	"environ":             &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)},            // environ() => array(string) | 	"environ":             &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)},            // environ() => array(string) | ||||||
| 	"exit":                &objects.UserFunction{Name: "exit", Value: FuncAIR(os.Exit)},                   // exit(code int) | 	"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 | 	"getegid":             &objects.UserFunction{Name: "getegid", Value: FuncARI(os.Getegid)},             // getegid() => int | ||||||
| 	"getenv":              &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)},              // getenv(s string) => string | 	"getenv":              &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)},              // getenv(s string) => string | ||||||
| 	"geteuid":             &objects.UserFunction{Name: "geteuid", Value: FuncARI(os.Geteuid)},             // geteuid() => int | 	"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 | 	"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 | 	"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 | 	"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 | 	"lookup_env":          &objects.UserFunction{Name: "lookup_env", Value: osLookupEnv},                  // lookup_env(key string) => string/false | ||||||
| 	"mkdir":               osFuncASFmRE(os.Mkdir),                                                         // mkdir(name string, perm int) => error | 	"mkdir":               osFuncASFmRE("mkdir", os.Mkdir),                                                // mkdir(name string, perm int) => error | ||||||
| 	"mkdir_all":           osFuncASFmRE(os.MkdirAll),                                                      // mkdir_all(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 | 	"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":              &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 | 	"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 | 	"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 | 	"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 | 	"unsetenv":            &objects.UserFunction{Name: "unsetenv", Value: FuncASRE(os.Unsetenv)},          // unsetenv(key string) => error | ||||||
| 	"create":              &objects.UserFunction{Value: osCreate},                                         // create(name string) => imap(file)/error | 	"create":              &objects.UserFunction{Name: "create", Value: osCreate},                         // create(name string) => imap(file)/error | ||||||
| 	"open":                &objects.UserFunction{Value: osOpen},                                           // open(name string) => imap(file)/error | 	"open":                &objects.UserFunction{Name: "open", 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 | 	"open_file":           &objects.UserFunction{Name: "open_file", 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 | 	"find_process":        &objects.UserFunction{Name: "find_process", 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 | 	"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_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 | 	"exec":                &objects.UserFunction{Name: "exec", Value: osExec},                             // exec(name, args...) => command | ||||||
| 	"stat":                &objects.UserFunction{Value: osStat},                                           // stat(name) => imap(fileinfo)/error | 	"stat":                &objects.UserFunction{Name: "stat", Value: osStat},                             // stat(name) => imap(fileinfo)/error | ||||||
| 	"read_file":           &objects.UserFunction{Value: osReadFile},                                       // readfile(name) => array(byte)/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) { | 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 | 		return wrapError(err), nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if len(bytes) > tengo.MaxBytesLen { | ||||||
|  | 		return nil, objects.ErrBytesLimit | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return &objects.Bytes{Value: bytes}, nil | 	return &objects.Bytes{Value: bytes}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -233,14 +238,19 @@ func osArgs(args ...objects.Object) (objects.Object, error) { | |||||||
|  |  | ||||||
| 	arr := &objects.Array{} | 	arr := &objects.Array{} | ||||||
| 	for _, osArg := range os.Args { | 	for _, osArg := range os.Args { | ||||||
|  | 		if len(osArg) > tengo.MaxStringLen { | ||||||
|  | 			return nil, objects.ErrStringLimit | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		arr.Value = append(arr.Value, &objects.String{Value: osArg}) | 		arr.Value = append(arr.Value, &objects.String{Value: osArg}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return arr, nil | 	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{ | 	return &objects.UserFunction{ | ||||||
|  | 		Name: name, | ||||||
| 		Value: func(args ...objects.Object) (objects.Object, error) { | 		Value: func(args ...objects.Object) (objects.Object, error) { | ||||||
| 			if len(args) != 2 { | 			if len(args) != 2 { | ||||||
| 				return nil, objects.ErrWrongNumArguments | 				return nil, objects.ErrWrongNumArguments | ||||||
| @@ -287,9 +297,54 @@ func osLookupEnv(args ...objects.Object) (objects.Object, error) { | |||||||
| 		return objects.FalseValue, nil | 		return objects.FalseValue, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if len(res) > tengo.MaxStringLen { | ||||||
|  | 		return nil, objects.ErrStringLimit | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return &objects.String{Value: res}, nil | 	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) { | func osExec(args ...objects.Object) (objects.Object, error) { | ||||||
| 	if len(args) == 0 { | 	if len(args) == 0 { | ||||||
| 		return nil, objects.ErrWrongNumArguments | 		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)}, // | 			"wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, // | ||||||
| 			// set_path(path string) | 			// set_path(path string) | ||||||
| 			"set_path": &objects.UserFunction{ | 			"set_path": &objects.UserFunction{ | ||||||
|  | 				Name: "set_path", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 1 { | 					if len(args) != 1 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						return nil, objects.ErrWrongNumArguments | ||||||
| @@ -42,6 +43,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { | |||||||
| 			}, | 			}, | ||||||
| 			// set_dir(dir string) | 			// set_dir(dir string) | ||||||
| 			"set_dir": &objects.UserFunction{ | 			"set_dir": &objects.UserFunction{ | ||||||
|  | 				Name: "set_dir", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 1 { | 					if len(args) != 1 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						return nil, objects.ErrWrongNumArguments | ||||||
| @@ -63,6 +65,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { | |||||||
| 			}, | 			}, | ||||||
| 			// set_env(env array(string)) | 			// set_env(env array(string)) | ||||||
| 			"set_env": &objects.UserFunction{ | 			"set_env": &objects.UserFunction{ | ||||||
|  | 				Name: "set_env", | ||||||
| 				Value: func(args ...objects.Object) (objects.Object, error) { | 				Value: func(args ...objects.Object) (objects.Object, error) { | ||||||
| 					if len(args) != 1 { | 					if len(args) != 1 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						return nil, objects.ErrWrongNumArguments | ||||||
| @@ -96,6 +99,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { | |||||||
| 			}, | 			}, | ||||||
| 			// process() => imap(process) | 			// process() => imap(process) | ||||||
| 			"process": &objects.UserFunction{ | 			"process": &objects.UserFunction{ | ||||||
|  | 				Name: "process", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 0 { | 					if len(args) != 0 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						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)}, // | 			"read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, // | ||||||
| 			// chmod(mode int) => error | 			// chmod(mode int) => error | ||||||
| 			"chmod": &objects.UserFunction{ | 			"chmod": &objects.UserFunction{ | ||||||
|  | 				Name: "chmod", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 1 { | 					if len(args) != 1 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						return nil, objects.ErrWrongNumArguments | ||||||
| @@ -48,6 +49,7 @@ func makeOSFile(file *os.File) *objects.ImmutableMap { | |||||||
| 			}, | 			}, | ||||||
| 			// seek(offset int, whence int) => int/error | 			// seek(offset int, whence int) => int/error | ||||||
| 			"seek": &objects.UserFunction{ | 			"seek": &objects.UserFunction{ | ||||||
|  | 				Name: "seek", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 2 { | 					if len(args) != 2 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						return nil, objects.ErrWrongNumArguments | ||||||
| @@ -80,6 +82,7 @@ func makeOSFile(file *os.File) *objects.ImmutableMap { | |||||||
| 			}, | 			}, | ||||||
| 			// stat() => imap(fileinfo)/error | 			// stat() => imap(fileinfo)/error | ||||||
| 			"stat": &objects.UserFunction{ | 			"stat": &objects.UserFunction{ | ||||||
|  | 				Name: "start", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 0 { | 					if len(args) != 0 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						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)},       // | 			"kill":    &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)},       // | ||||||
| 			"release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, // | 			"release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, // | ||||||
| 			"signal": &objects.UserFunction{ | 			"signal": &objects.UserFunction{ | ||||||
|  | 				Name: "signal", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 1 { | 					if len(args) != 1 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						return nil, objects.ErrWrongNumArguments | ||||||
| @@ -42,6 +43,7 @@ func makeOSProcess(proc *os.Process) *objects.ImmutableMap { | |||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			"wait": &objects.UserFunction{ | 			"wait": &objects.UserFunction{ | ||||||
|  | 				Name: "wait", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 0 { | 					if len(args) != 0 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						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)}, | 	"perm":       &objects.UserFunction{Name: "perm", Value: FuncAIRIs(rand.Perm)}, | ||||||
| 	"seed":       &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)}, | 	"seed":       &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)}, | ||||||
| 	"read": &objects.UserFunction{ | 	"read": &objects.UserFunction{ | ||||||
|  | 		Name: "read", | ||||||
| 		Value: func(args ...objects.Object) (ret objects.Object, err error) { | 		Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 			if len(args) != 1 { | 			if len(args) != 1 { | ||||||
| 				return nil, objects.ErrWrongNumArguments | 				return nil, objects.ErrWrongNumArguments | ||||||
| @@ -39,6 +40,7 @@ var randModule = map[string]objects.Object{ | |||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	"rand": &objects.UserFunction{ | 	"rand": &objects.UserFunction{ | ||||||
|  | 		Name: "rand", | ||||||
| 		Value: func(args ...objects.Object) (ret objects.Object, err error) { | 		Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 			if len(args) != 1 { | 			if len(args) != 1 { | ||||||
| 				return nil, objects.ErrWrongNumArguments | 				return nil, objects.ErrWrongNumArguments | ||||||
| @@ -71,6 +73,7 @@ func randRand(r *rand.Rand) *objects.ImmutableMap { | |||||||
| 			"perm":       &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)}, | 			"perm":       &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)}, | ||||||
| 			"seed":       &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)}, | 			"seed":       &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)}, | ||||||
| 			"read": &objects.UserFunction{ | 			"read": &objects.UserFunction{ | ||||||
|  | 				Name: "read", | ||||||
| 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | 				Value: func(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 					if len(args) != 1 { | 					if len(args) != 1 { | ||||||
| 						return nil, objects.ErrWrongNumArguments | 						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 | package stdlib | ||||||
|  |  | ||||||
|  | //go:generate go run gensrcmods.go | ||||||
|  |  | ||||||
| import "github.com/d5/tengo/objects" | import "github.com/d5/tengo/objects" | ||||||
|  |  | ||||||
| // Modules contain the standard modules. | // AllModuleNames returns a list of all default module names. | ||||||
| var Modules = map[string]*objects.Object{ | func AllModuleNames() []string { | ||||||
| 	"math":  objectPtr(&objects.ImmutableMap{Value: mathModule}), | 	var names []string | ||||||
| 	"os":    objectPtr(&objects.ImmutableMap{Value: osModule}), | 	for name := range BuiltinModules { | ||||||
| 	"text":  objectPtr(&objects.ImmutableMap{Value: textModule}), | 		names = append(names, name) | ||||||
| 	"times": objectPtr(&objects.ImmutableMap{Value: timesModule}), | 	} | ||||||
| 	"rand":  objectPtr(&objects.ImmutableMap{Value: randModule}), | 	for name := range SourceModules { | ||||||
|  | 		names = append(names, name) | ||||||
|  | 	} | ||||||
|  | 	return names | ||||||
| } | } | ||||||
|  |  | ||||||
| func objectPtr(o objects.Object) *objects.Object { | // GetModuleMap returns the module map that includes all modules | ||||||
| 	return &o | // 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 | package stdlib | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"unicode/utf8" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var textModule = map[string]objects.Object{ | var textModule = map[string]objects.Object{ | ||||||
| 	"re_match":       &objects.UserFunction{Value: textREMatch},                                             // re_match(pattern, text) => bool/error | 	"re_match":       &objects.UserFunction{Name: "re_match", Value: textREMatch},                           // re_match(pattern, text) => bool/error | ||||||
| 	"re_find":        &objects.UserFunction{Value: textREFind},                                              // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined | 	"re_find":        &objects.UserFunction{Name: "re_find", 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_replace":     &objects.UserFunction{Name: "re_replace", Value: textREReplace},                       // re_replace(pattern, text, repl) => string/error | ||||||
| 	"re_split":       &objects.UserFunction{Value: textRESplit},                                             // re_split(pattern, text, count) => [string]/error | 	"re_split":       &objects.UserFunction{Name: "re_split", Value: textRESplit},                           // re_split(pattern, text, count) => [string]/error | ||||||
| 	"re_compile":     &objects.UserFunction{Value: textRECompile},                                           // re_compile(pattern) => Regexp/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 | 	"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":       &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 | 	"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 | 	"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":          &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 | 	"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":     &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 | 	"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 | 	"repeat":         &objects.UserFunction{Name: "repeat", Value: textRepeat},                              // repeat(s, count) => string | ||||||
| 	"replace":        &objects.UserFunction{Value: textReplace},                                             // replace(s, old, new, n) => 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":          &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":    &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] | 	"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_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 | 	"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 | 	"atoi":           &objects.UserFunction{Name: "atoi", Value: FuncASRIE(strconv.Atoi)},                   // atoi(str) => int/error | ||||||
| 	"format_bool":    &objects.UserFunction{Value: textFormatBool},                                          // format_bool(b) => string | 	"format_bool":    &objects.UserFunction{Name: "format_bool", Value: textFormatBool},                     // format_bool(b) => string | ||||||
| 	"format_float":   &objects.UserFunction{Value: textFormatFloat},                                         // format_float(f, fmt, prec, bits) => string | 	"format_float":   &objects.UserFunction{Name: "format_float", Value: textFormatFloat},                   // format_float(f, fmt, prec, bits) => string | ||||||
| 	"format_int":     &objects.UserFunction{Value: textFormatInt},                                           // format_int(i, base) => 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 | 	"itoa":           &objects.UserFunction{Name: "itoa", Value: FuncAIRS(strconv.Itoa)},                    // itoa(i) => string | ||||||
| 	"parse_bool":     &objects.UserFunction{Value: textParseBool},                                           // parse_bool(str) => bool/error | 	"parse_bool":     &objects.UserFunction{Name: "parse_bool", Value: textParseBool},                       // parse_bool(str) => bool/error | ||||||
| 	"parse_float":    &objects.UserFunction{Value: textParseFloat},                                          // parse_float(str, bits) => float/error | 	"parse_float":    &objects.UserFunction{Name: "parse_float", Value: textParseFloat},                     // parse_float(str, bits) => float/error | ||||||
| 	"parse_int":      &objects.UserFunction{Value: textParseInt},                                            // parse_int(str, base, bits) => int/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 | 	"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 | 	"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 { | 	if err != nil { | ||||||
| 		ret = wrapError(err) | 		ret = wrapError(err) | ||||||
| 	} else { | 	} 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 | 	return | ||||||
| @@ -357,11 +365,106 @@ func textReplace(args ...objects.Object) (ret objects.Object, err error) { | |||||||
| 		return | 		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 | 	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) { | func textFormatBool(args ...objects.Object) (ret objects.Object, err error) { | ||||||
| 	if len(args) != 1 { | 	if len(args) != 1 { | ||||||
| 		err = objects.ErrWrongNumArguments | 		err = objects.ErrWrongNumArguments | ||||||
| @@ -583,3 +686,52 @@ func textParseInt(args ...objects.Object) (ret objects.Object, err error) { | |||||||
|  |  | ||||||
| 	return | 	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 ( | import ( | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -141,7 +142,12 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { | |||||||
| 						return | 						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 | 					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 ( | import ( | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/d5/tengo" | ||||||
| 	"github.com/d5/tengo/objects" | 	"github.com/d5/tengo/objects" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -867,7 +868,13 @@ func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) { | |||||||
| 		return | 		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 | 	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/Philipp15b/go-steam/socialcache | ||||||
| # github.com/bwmarrin/discordgo v0.19.0 | # github.com/bwmarrin/discordgo v0.19.0 | ||||||
| github.com/bwmarrin/discordgo | 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/script | ||||||
|  | github.com/d5/tengo/stdlib | ||||||
| github.com/d5/tengo/compiler | github.com/d5/tengo/compiler | ||||||
| github.com/d5/tengo/compiler/parser | github.com/d5/tengo/compiler/parser | ||||||
| github.com/d5/tengo/compiler/source | github.com/d5/tengo/compiler/source | ||||||
| github.com/d5/tengo/objects | github.com/d5/tengo/objects | ||||||
| github.com/d5/tengo/runtime | 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/ast | ||||||
| github.com/d5/tengo/compiler/token | github.com/d5/tengo/compiler/token | ||||||
| github.com/d5/tengo/compiler/scanner | github.com/d5/tengo/compiler/scanner | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user