Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c583d0991b | ||
|
|
4d0fbfd720 | ||
|
|
d0a0759bca | ||
|
|
972f5685e7 | ||
|
|
36b0166de9 | ||
|
|
b44b8cf3f4 | ||
|
|
3f452f14f2 | ||
|
|
7d86bb66fe | ||
|
|
50cdb4e058 | ||
|
|
1fc8dec04d | ||
|
|
1bb93891f4 | ||
|
|
1e28907f7a | ||
|
|
4b94e83723 | ||
|
|
756045ab5e | ||
|
|
2bb9254f26 | ||
|
|
88952938dc | ||
|
|
cabefc2786 | ||
|
|
b8456329aa | ||
|
|
9bcfe68591 | ||
|
|
28bb992796 | ||
|
|
eccb6c7cf3 | ||
|
|
1c611eff63 | ||
|
|
4a52c7709d | ||
|
|
2c51ed2000 | ||
|
|
d52a867157 | ||
|
|
744b85e833 | ||
|
|
ffcea86f47 | ||
|
|
a735812a72 | ||
|
|
0284dca059 | ||
|
|
9a30bf7f45 | ||
|
|
520de6b223 | ||
|
|
468e983e10 | ||
|
|
04d24e4d7c | ||
|
|
41d0924ac6 | ||
|
|
745244309c | ||
|
|
af699a9df0 | ||
|
|
eaec826ac5 | ||
|
|
653444ba98 | ||
|
|
74daa24696 | ||
|
|
af707140e3 | ||
|
|
e1c7cc4c7d | ||
|
|
4ec3949a9c | ||
|
|
2981e87657 | ||
|
|
ad380b8ede | ||
|
|
e62229fc8e | ||
|
|
8a0f825cf4 | ||
|
|
f9c18b1237 | ||
|
|
80c24ef4e1 | ||
|
|
ea87d40cc2 | ||
|
|
21c6bc86cb | ||
|
|
2c5ca9c0e6 | ||
|
|
5f34149d25 |
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Please describe your issue
|
||||
|
||||
## Is this a bug, an improvement, a proposal or something else?
|
||||
|
||||
- [ ] Bug
|
||||
- [ ] Improvement
|
||||
- [ ] Proposal
|
||||
- [ ] Something else
|
||||
|
||||
|
||||
## Briefly explain your issue
|
||||
|
||||
...
|
||||
|
||||
|
||||
## What's the expected behaviour?
|
||||
|
||||
...
|
||||
|
||||
|
||||
## What's the actual behaviour?
|
||||
|
||||
...
|
||||
|
||||
|
||||
## What are the steps to reproduce the actual behaviour?
|
||||
|
||||
...
|
||||
|
||||
|
||||
## Comments
|
||||
|
||||
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Before creating your Pull Request...
|
||||
|
||||
- New Pull Requests should include a good description of what's being merged.
|
||||
- Ideally, all Pull Requests are preceded by a discussion initiated in an Issue on this repository.
|
||||
- For bug fixes is mandatory to have tests that cover and fail when the bug is present and will pass after this Pull Request.
|
||||
- For changes and improvements, new tests have to be provided to cover the new features.
|
||||
|
||||
|
||||
## What does this change implement/fix?
|
||||
|
||||
... *answer here*
|
||||
|
||||
## Is this a fix or an improvement?
|
||||
|
||||
- [ ] Fix
|
||||
- [ ] Improvement
|
||||
|
||||
## Have discussed this change in an issue?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Was some test failing because of this issue or change needed?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Are you including tests to cover this change?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
17
.travis.yml
Normal file
17
.travis.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script: go test -v -race -coverprofile=coverage.txt -covermode=atomic .
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at leonel.quinteros@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
19
CONTRIBUTING.md
Normal file
19
CONTRIBUTING.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
This open source project welcomes everybody that wants to contribute to it by implementing new features, fixing bugs, testing, creating documentation or simply talk about it.
|
||||
|
||||
Most contributions will start by creating a new Issue to discuss what is the contribution about and to agree on the steps to move forward.
|
||||
|
||||
## Issues
|
||||
|
||||
All issues reports are welcome. Open a new Issue whenever you want to report a bug, request a change or make a proposal.
|
||||
|
||||
This should be your start point of contribution.
|
||||
|
||||
|
||||
## Pull Requests
|
||||
|
||||
If you have any changes that can be merged, feel free to send a Pull Request.
|
||||
|
||||
Usually, you'd want to create a new Issue to discuss about the change you want to merge and why it's needed or what it solves.
|
||||
|
||||
15
Gopkg.lock
generated
Normal file
15
Gopkg.lock
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/kinako"
|
||||
packages = ["ast","parser","vm"]
|
||||
revision = "332c0a7e205a29536e672337a4bea6c7a96b04c1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "d3069fabe2d6f79fe33ad88133e861db84aef0400f6b949c4e64395913b3ae97"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
26
Gopkg.toml
Normal file
26
Gopkg.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/kinako"
|
||||
172
README.md
172
README.md
@@ -1,24 +1,39 @@
|
||||
[](https://github.com/leonelquinteros/gotext)
|
||||
[](LICENSE)
|
||||
[](https://godoc.org/github.com/leonelquinteros/gotext)
|
||||
[](https://travis-ci.org/leonelquinteros/gotext)
|
||||
[](https://codecov.io/gh/leonelquinteros/gotext)
|
||||
[](https://goreportcard.com/report/github.com/leonelquinteros/gotext)
|
||||
|
||||
# Gotext
|
||||
|
||||
GNU gettext utilities for Go.
|
||||
|
||||
Version: [0.9.1](https://github.com/leonelquinteros/gotext/releases/tag/v0.9.1)
|
||||
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
|
||||
|
||||
|
||||
#Features
|
||||
# Features
|
||||
|
||||
- Implements GNU gettext support in native Go.
|
||||
- Safe for concurrent use accross multiple goroutines.
|
||||
- Complete support for [PO files](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) including:
|
||||
- Support for multiline strings and headers.
|
||||
- Support for variables inside translation strings using Go's [fmt syntax](https://golang.org/pkg/fmt/).
|
||||
- Support for [pluralization rules](https://www.gnu.org/software/gettext/manual/html_node/Translating-plural-forms.html).
|
||||
- Support for [message contexts](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html).
|
||||
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
|
||||
- It works with UTF-8 encoding as it's the default for Go language.
|
||||
- Unit tests available
|
||||
- Language codes are automatically simplified from the form "en_UK" to "en" if the formed isn't available.
|
||||
- Unit tests available.
|
||||
- Language codes are automatically simplified from the form `en_UK` to `en` if the first isn't available.
|
||||
- Ready to use inside Go templates.
|
||||
- Support for pluralization rules.
|
||||
- Support for variables inside translation strings using Go's [fmt package syntax](https://golang.org/pkg/fmt/)
|
||||
|
||||
|
||||
# License
|
||||
|
||||
[MIT license](LICENSE)
|
||||
|
||||
|
||||
# Documentation
|
||||
|
||||
Refer to the Godoc package documentation at (https://godoc.org/github.com/leonelquinteros/gotext)
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -31,42 +46,62 @@ go get github.com/leonelquinteros/gotext
|
||||
- No need for environment variables. Some naming conventions are applied but not needed.
|
||||
|
||||
|
||||
# License
|
||||
#### Version vendoring
|
||||
|
||||
[MIT license](LICENSE)
|
||||
Stable releases use [semantic versioning](http://semver.org/spec/v2.0.0.html) tagging on this repository.
|
||||
|
||||
You can rely on this to use your preferred vendoring tool or to manually retrieve the corresponding release tag from the GitHub repository.
|
||||
|
||||
|
||||
# Documentation
|
||||
##### Vendoring with [gopkg.in](http://labix.org/gopkg.in)
|
||||
|
||||
Refer to the Godoc package documentation at (https://godoc.org/github.com/leonelquinteros/gotext)
|
||||
[http://gopkg.in/leonelquinteros/gotext.v1](http://gopkg.in/leonelquinteros/gotext.v1)
|
||||
|
||||
To get the latest v1 package stable release, execute:
|
||||
|
||||
```
|
||||
go get gopkg.in/leonelquinteros/gotext.v1
|
||||
```
|
||||
|
||||
To import this package, add the following line to your code:
|
||||
|
||||
```go
|
||||
import "gopkg.in/leonelquinteros/gotext.v1"
|
||||
```
|
||||
|
||||
Refer to it as gotext.
|
||||
|
||||
|
||||
# Locales directories structure
|
||||
|
||||
The package will asume a directories structure starting with a base path that will be provided to the package configuration
|
||||
The package will assume a directories structure starting with a base path that will be provided to the package configuration
|
||||
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
|
||||
|
||||
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
|
||||
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
|
||||
So if the language set is "en_UK", but there is no directory named after that code and there is a directory named "en",
|
||||
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
|
||||
all package functions will be able to resolve this generalization and provide translations for the more general library.
|
||||
|
||||
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
|
||||
That said, most functions will work with any coding standard as long the directory name matches the language code set on the configuration.
|
||||
That said, most functions will work with any coding standard as long the directory name matches the language code set on the configuration.
|
||||
|
||||
A normal library directory structure may look like:
|
||||
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
|
||||
A library directory structure can look like:
|
||||
|
||||
```
|
||||
/path/to/locales
|
||||
/path/to/locales/en_US
|
||||
/path/to/locales/en_US/default.po
|
||||
/path/to/locales/en_US/extras.po
|
||||
/path/to/locales/en_US/LC_MESSAGES
|
||||
/path/to/locales/en_US/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_US/LC_MESSAGES/extras.po
|
||||
/path/to/locales/en_UK
|
||||
/path/to/locales/en_UK/default.po
|
||||
/path/to/locales/en_UK/extras.po
|
||||
/path/to/locales/en_UK/LC_MESSAGES
|
||||
/path/to/locales/en_UK/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_UK/LC_MESSAGES/extras.po
|
||||
/path/to/locales/en_AU
|
||||
/path/to/locales/en_AU/default.po
|
||||
/path/to/locales/en_AU/extras.po
|
||||
/path/to/locales/en_AU/LC_MESSAGES
|
||||
/path/to/locales/en_AU/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_AU/LC_MESSAGES/extras.po
|
||||
/path/to/locales/es
|
||||
/path/to/locales/es/default.po
|
||||
/path/to/locales/es/extras.po
|
||||
@@ -81,39 +116,6 @@ A normal library directory structure may look like:
|
||||
And so on...
|
||||
|
||||
|
||||
|
||||
# About translation function names
|
||||
|
||||
The standard GNU gettext defines helper functions that maps to the gettext() function and it's widely adopted by most implementations.
|
||||
|
||||
The basic translation function is usually _() in the form:
|
||||
|
||||
```
|
||||
_("Translate this")
|
||||
```
|
||||
|
||||
In Go, this can't be implemented by a reusable package as the function name has to start with a capital letter in order to be exported.
|
||||
|
||||
Each implementation of this package can declare this helper functions inside their own packages if this function naming are desired/needed:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
|
||||
func _(str string, vars ...interface{}) string {
|
||||
return gotext.Get(str, vars...)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This is valid and can be used within a package.
|
||||
|
||||
In normal conditions the Go compiler will optimize the calls to _() by replacing its content in place of the function call to reduce the function calling overhead.
|
||||
This is a normal Go compiler behaviour.
|
||||
|
||||
|
||||
|
||||
# Usage examples
|
||||
|
||||
## Using package for single language/domain settings
|
||||
@@ -121,17 +123,20 @@ This is a normal Go compiler behaviour.
|
||||
For quick/simple translations you can use the package level functions directly.
|
||||
|
||||
```go
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Translate text from default domain
|
||||
println(gotext.Get("My text on 'domain-name' domain"))
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
|
||||
```
|
||||
@@ -142,7 +147,10 @@ All translation strings support dynamic variables to be inserted without transla
|
||||
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
||||
|
||||
```go
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
@@ -152,7 +160,7 @@ func main() {
|
||||
name := "John"
|
||||
|
||||
// Translate text with variables
|
||||
println(gotext.Get("Hi, my name is %s", name))
|
||||
fmt.Println(gotext.Get("Hi, my name is %s", name))
|
||||
}
|
||||
|
||||
```
|
||||
@@ -164,7 +172,10 @@ When having multiple languages/domains/libraries at the same time, you can creat
|
||||
so you can handle each settings on their own.
|
||||
|
||||
```go
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create Locale with library path and language code
|
||||
@@ -174,18 +185,18 @@ func main() {
|
||||
l.AddDomain("default")
|
||||
|
||||
// Translate text from default domain
|
||||
println(l.Get("Translate this"))
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
// Load different domain
|
||||
l.AddDomain("translations")
|
||||
|
||||
// Translate text from domain
|
||||
println(l.GetD("translations", "Translate this"))
|
||||
fmt.Println(l.GetD("translations", "Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
This is also helpful for using inside templates (from the "text/template" package), where you can pass the Locale object to the template.
|
||||
If you set the Locale object as "Loc" in the template, then the templace code would look like:
|
||||
If you set the Locale object as "Loc" in the template, then the template code would look like:
|
||||
|
||||
```
|
||||
{{ .Loc.Get "Translate this" }}
|
||||
@@ -198,7 +209,10 @@ For when you need to work with PO files and strings,
|
||||
you can directly use the Po object to parse it and access the translations in there in the same way.
|
||||
|
||||
```go
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set PO content
|
||||
@@ -214,10 +228,10 @@ msgstr "This one sets the var: %s"
|
||||
`
|
||||
|
||||
// Create Po object
|
||||
po := new(Po)
|
||||
po := new(gotext.Po)
|
||||
po.Parse(str)
|
||||
|
||||
println(po.Get("Translate this"))
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
@@ -225,13 +239,26 @@ msgstr "This one sets the var: %s"
|
||||
## Use plural forms of translations
|
||||
|
||||
PO format supports defining one or more plural forms for the same translation.
|
||||
Relying on the PO file headers, a Plural-Forms formula can be set on the translation file
|
||||
as defined in (https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html)
|
||||
|
||||
Plural formulas are parsed and evaluated using [Kinako](https://github.com/mattn/kinako)
|
||||
|
||||
```go
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
# Header below
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Translate this"
|
||||
msgstr "Translated text"
|
||||
|
||||
@@ -242,15 +269,14 @@ msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
msgstr[2] "And this is the second plural form: %s"
|
||||
`
|
||||
|
||||
// Create Po object
|
||||
po := new(Po)
|
||||
po := new(gotext.Po)
|
||||
po.Parse(str)
|
||||
|
||||
println(po.GetN("One with var: %s", "Several with vars: %s", 2, v))
|
||||
// "And this is the second plural form: Variable"
|
||||
fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
|
||||
// "This one is the plural: Variable"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
153
gotext.go
153
gotext.go
@@ -1,86 +1,131 @@
|
||||
/*
|
||||
Package gotext implements GNU gettext utilities.
|
||||
|
||||
Version 0.9.0 (stable)
|
||||
|
||||
For quick/simple translations you can use the package level functions directly.
|
||||
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Translate text from default domain
|
||||
println(gotext.Get("My text on 'domain-name' domain"))
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
|
||||
*/
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Global environment variables
|
||||
var (
|
||||
type config struct {
|
||||
sync.RWMutex
|
||||
|
||||
// Default domain to look at when no domain is specified. Used by package level functions.
|
||||
domain = "default"
|
||||
domain string
|
||||
|
||||
// Language set.
|
||||
language = "en_US"
|
||||
language string
|
||||
|
||||
// Path to library directory where all locale directories and translation files are.
|
||||
library = "/tmp"
|
||||
library string
|
||||
|
||||
// Storage for package level methods
|
||||
storage *Locale
|
||||
)
|
||||
}
|
||||
|
||||
var globalConfig *config
|
||||
|
||||
// Init default configuration
|
||||
func init() {
|
||||
globalConfig = &config{
|
||||
domain: "default",
|
||||
language: "en_US",
|
||||
library: "/usr/local/share/locale",
|
||||
storage: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// loadStorage creates a new Locale object at package level based on the Global variables settings.
|
||||
// It's called automatically when trying to use Get or GetD methods.
|
||||
func loadStorage(force bool) {
|
||||
if storage == nil || force {
|
||||
storage = NewLocale(library, language)
|
||||
globalConfig.Lock()
|
||||
|
||||
if globalConfig.storage == nil || force {
|
||||
globalConfig.storage = NewLocale(globalConfig.library, globalConfig.language)
|
||||
}
|
||||
|
||||
if _, ok := storage.domains[domain]; !ok || force {
|
||||
storage.AddDomain(domain)
|
||||
if _, ok := globalConfig.storage.domains[globalConfig.domain]; !ok || force {
|
||||
globalConfig.storage.AddDomain(globalConfig.domain)
|
||||
}
|
||||
|
||||
globalConfig.Unlock()
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for the package configuration
|
||||
func GetDomain() string {
|
||||
return domain
|
||||
globalConfig.RLock()
|
||||
dom := globalConfig.domain
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return dom
|
||||
}
|
||||
|
||||
// SetDomain sets the name for the domain to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
func SetDomain(dom string) {
|
||||
domain = dom
|
||||
globalConfig.Lock()
|
||||
globalConfig.domain = dom
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// GetLanguage is the language getter for the package configuration
|
||||
func GetLanguage() string {
|
||||
return language
|
||||
globalConfig.RLock()
|
||||
lang := globalConfig.language
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return lang
|
||||
}
|
||||
|
||||
// SetLanguage sets the language code to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
func SetLanguage(lang string) {
|
||||
language = lang
|
||||
globalConfig.Lock()
|
||||
globalConfig.language = lang
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// GetLibrary is the library getter for the package configuration
|
||||
func GetLibrary() string {
|
||||
return library
|
||||
globalConfig.RLock()
|
||||
lib := globalConfig.library
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return lib
|
||||
}
|
||||
|
||||
// SetLibrary sets the root path for the loale directories and files to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
func SetLibrary(lib string) {
|
||||
library = lib
|
||||
globalConfig.Lock()
|
||||
globalConfig.library = lib
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
@@ -89,9 +134,13 @@ func SetLibrary(lib string) {
|
||||
// This function is recommended to be used when changing more than one setting,
|
||||
// as using each setter will introduce a I/O overhead because the translation file will be loaded after each set.
|
||||
func Configure(lib, lang, dom string) {
|
||||
library = lib
|
||||
language = lang
|
||||
domain = dom
|
||||
globalConfig.Lock()
|
||||
|
||||
globalConfig.library = lib
|
||||
globalConfig.language = lang
|
||||
globalConfig.domain = dom
|
||||
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
}
|
||||
@@ -99,28 +148,72 @@ func Configure(lib, lang, dom string) {
|
||||
// Get uses the default domain globally set to return the corresponding translation of a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func Get(str string, vars ...interface{}) string {
|
||||
return GetD(domain, str, vars...)
|
||||
return GetD(GetDomain(), str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form translation for the given string in the "default" domain.
|
||||
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
|
||||
// GetN retrieves the (N)th plural form of translation for the given string in the default domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return GetND("default", str, plural, n, vars...)
|
||||
return GetND(GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetD(dom, str string, vars ...interface{}) string {
|
||||
return GetND(dom, str, str, 0, vars...)
|
||||
return GetND(dom, str, str, 1, vars...)
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form translation in the given domain for a given string.
|
||||
// GetND retrieves the (N)th plural form of translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return translation
|
||||
return storage.GetND(dom, str, plural, n, vars...)
|
||||
globalConfig.RLock()
|
||||
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// GetC uses the default domain globally set to return the corresponding translation of the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetC(str, ctx string, vars ...interface{}) string {
|
||||
return GetDC(GetDomain(), str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the default domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return GetNDC(GetDomain(), str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetDC returns the corresponding translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||
return GetNDC(dom, str, str, 1, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return translation
|
||||
globalConfig.RLock()
|
||||
tr := globalConfig.storage.GetNDC(dom, str, plural, n, ctx, vars...)
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// printf applies text formatting only when needed to parse variables.
|
||||
func printf(str string, vars ...interface{}) string {
|
||||
if len(vars) > 0 {
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
307
gotext_test.go
307
gotext_test.go
@@ -3,12 +3,48 @@ package gotext
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGettersSetters(t *testing.T) {
|
||||
SetDomain("test")
|
||||
dom := GetDomain()
|
||||
|
||||
if dom != "test" {
|
||||
t.Errorf("Expected GetDomain to return 'test', but got '%s'", dom)
|
||||
}
|
||||
|
||||
SetLibrary("/tmp/test")
|
||||
lib := GetLibrary()
|
||||
|
||||
if lib != "/tmp/test" {
|
||||
t.Errorf("Expected GetLibrary to return '/tmp/test', but got '%s'", lib)
|
||||
}
|
||||
|
||||
SetLanguage("es")
|
||||
lang := GetLanguage()
|
||||
|
||||
if lang != "es" {
|
||||
t.Errorf("Expected GetLanguage to return 'es', but got '%s'", lang)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageFunctions(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `# Some comment
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr "Project-Id-Version: %s\n"
|
||||
"Report-Msgid-Bugs-To: %s\n"
|
||||
|
||||
# Initial comment
|
||||
# More Headers below
|
||||
"Language: en\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
@@ -22,17 +58,38 @@ msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
`
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
|
||||
msgid "Untranslated"
|
||||
msgid_plural "Several untranslated"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory on default location
|
||||
dirname := path.Clean(library + string(os.PathSeparator) + "en_US")
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to default domain file
|
||||
filename := path.Clean(dirname + string(os.PathSeparator) + domain + ".po")
|
||||
filename := path.Join(dirname, "default.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -45,6 +102,9 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Set package configuration
|
||||
Configure("/tmp", "en_US", "default")
|
||||
|
||||
// Test translations
|
||||
tr := Get("My text")
|
||||
if tr != "Translated text" {
|
||||
@@ -59,8 +119,200 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
// Test plural
|
||||
tr = GetN("One with var: %s", "Several with vars: %s", 2, v)
|
||||
if tr != "And this is the second plural form: Variable" {
|
||||
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
|
||||
if tr != "This one is the plural: Variable" {
|
||||
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test context translations
|
||||
tr = GetC("Some random in a context", "Ctx")
|
||||
if tr != "Some random translation in a context" {
|
||||
t.Errorf("Expected 'Some random translation in a context' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v = "Variable"
|
||||
tr = GetC("One with var: %s", "Ctx", v)
|
||||
if tr != "This one is the singular in a Ctx context: Variable" {
|
||||
t.Errorf("Expected 'This one is the singular in a Ctx context: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetNC("One with var: %s", "Several with vars: %s", 19, "Ctx", v)
|
||||
if tr != "This one is the plural in a Ctx context: Variable" {
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Variable' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntranslated(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
# Initial comment
|
||||
# Headers below
|
||||
"Language: en\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Untranslated"
|
||||
msgid_plural "Several untranslated"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory on default location
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to default domain file
|
||||
filename := path.Join(dirname, "default.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(str)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Set package configuration
|
||||
Configure("/tmp", "en_US", "default")
|
||||
|
||||
// Test untranslated
|
||||
tr := Get("Untranslated")
|
||||
if tr != "Untranslated" {
|
||||
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
|
||||
}
|
||||
tr = GetN("Untranslated", "Several untranslated", 1)
|
||||
if tr != "Untranslated" {
|
||||
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetN("Untranslated", "Several untranslated", 2)
|
||||
if tr != "Several untranslated" {
|
||||
t.Errorf("Expected 'Several untranslated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetD("default", "Untranslated")
|
||||
if tr != "Untranslated" {
|
||||
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
|
||||
}
|
||||
tr = GetND("default", "Untranslated", "Several untranslated", 1)
|
||||
if tr != "Untranslated" {
|
||||
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = GetND("default", "Untranslated", "Several untranslated", 2)
|
||||
if tr != "Several untranslated" {
|
||||
t.Errorf("Expected 'Several untranslated' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomains(t *testing.T) {
|
||||
// Set PO content
|
||||
strDefault := `
|
||||
msgid ""
|
||||
msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Default text"
|
||||
msgid_plural "Default texts"
|
||||
msgstr[0] "Default translation"
|
||||
msgstr[1] "Default translations"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Default context"
|
||||
msgid_plural "Default contexts"
|
||||
msgstr[0] "Default ctx translation"
|
||||
msgstr[1] "Default ctx translations"
|
||||
`
|
||||
|
||||
strCustom := `
|
||||
msgid ""
|
||||
msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Custom text"
|
||||
msgid_plural "Custom texts"
|
||||
msgstr[0] "Custom translation"
|
||||
msgstr[1] "Custom translations"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Custom context"
|
||||
msgid_plural "Custom contexts"
|
||||
msgstr[0] "Custom ctx translation"
|
||||
msgstr[1] "Custom ctx translations"
|
||||
`
|
||||
|
||||
// Create Locales directory and files on temp location
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
fDefault, err := os.Create(path.Join(dirname, "default.po"))
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer fDefault.Close()
|
||||
|
||||
fCustom, err := os.Create(path.Join(dirname, "custom.po"))
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer fCustom.Close()
|
||||
|
||||
_, err = fDefault.WriteString(strDefault)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
_, err = fCustom.WriteString(strCustom)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
Configure("/tmp", "en_US", "default")
|
||||
|
||||
// Check default domain translation
|
||||
SetDomain("default")
|
||||
tr := Get("Default text")
|
||||
if tr != "Default translation" {
|
||||
t.Errorf("Expected 'Default translation'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetN("Default text", "Default texts", 23)
|
||||
if tr != "Default translations" {
|
||||
t.Errorf("Expected 'Default translations'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetC("Default context", "Ctx")
|
||||
if tr != "Default ctx translation" {
|
||||
t.Errorf("Expected 'Default ctx translation'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetNC("Default context", "Default contexts", 23, "Ctx")
|
||||
if tr != "Default ctx translations" {
|
||||
t.Errorf("Expected 'Default ctx translations'. Got '%s'", tr)
|
||||
}
|
||||
|
||||
SetDomain("custom")
|
||||
tr = Get("Custom text")
|
||||
if tr != "Custom translation" {
|
||||
t.Errorf("Expected 'Custom translation'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetN("Custom text", "Custom texts", 23)
|
||||
if tr != "Custom translations" {
|
||||
t.Errorf("Expected 'Custom translations'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetC("Custom context", "Ctx")
|
||||
if tr != "Custom ctx translation" {
|
||||
t.Errorf("Expected 'Custom ctx translation'. Got '%s'", tr)
|
||||
}
|
||||
tr = GetNC("Custom context", "Custom contexts", 23, "Ctx")
|
||||
if tr != "Custom ctx translations" {
|
||||
t.Errorf("Expected 'Custom ctx translations'. Got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,17 +332,21 @@ msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
`
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory on default location
|
||||
dirname := path.Clean(library + string(os.PathSeparator) + "en_US")
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to default domain file
|
||||
filename := path.Clean(dirname + string(os.PathSeparator) + domain + ".po")
|
||||
filename := path.Join("/tmp", GetDomain()+".po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -103,20 +359,27 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Init sync channels
|
||||
c1 := make(chan bool)
|
||||
c2 := make(chan bool)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Test translations
|
||||
go func(done chan bool) {
|
||||
println(Get("My text"))
|
||||
done <- true
|
||||
}(c1)
|
||||
for i := 0; i < 1000; i++ {
|
||||
wg.Add(1)
|
||||
// Test translations
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
go func(done chan bool) {
|
||||
println(Get("My text"))
|
||||
done <- true
|
||||
}(c2)
|
||||
GetLibrary()
|
||||
SetLibrary(path.Join("/tmp", "gotextlib"))
|
||||
GetDomain()
|
||||
SetDomain("default")
|
||||
GetLanguage()
|
||||
SetLanguage("en_US")
|
||||
Configure("/tmp", "en_US", "default")
|
||||
|
||||
println(Get("My text"))
|
||||
Get("My text")
|
||||
GetN("One with var: %s", "Several with vars: %s", 0, "test")
|
||||
GetC("Some random in a context", "Ctx")
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
100
locale.go
100
locale.go
@@ -1,7 +1,6 @@
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
@@ -14,23 +13,26 @@ multiple languages at the same time by working with this object.
|
||||
|
||||
Example:
|
||||
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create Locale with library path and language code
|
||||
l := gotext.NewLocale("/path/to/i18n/dir", "en_US")
|
||||
|
||||
// Load domain '/path/to/i18n/dir/en_US/default.po'
|
||||
// Load domain '/path/to/i18n/dir/en_US/LC_MESSAGES/default.po'
|
||||
l.AddDomain("default")
|
||||
|
||||
// Translate text from default domain
|
||||
println(l.Get("Translate this"))
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
// Load different domain ('/path/to/i18n/dir/en_US/extras.po')
|
||||
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.po')
|
||||
l.AddDomain("extras")
|
||||
|
||||
// Translate text from domain
|
||||
println(l.GetD("extras", "Translate this"))
|
||||
fmt.Println(l.GetD("extras", "Translate this"))
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -58,23 +60,38 @@ func NewLocale(p, l string) *Locale {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Locale) findPO(dom string) string {
|
||||
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+".po")
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+".po")
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
filename = path.Join(l.path, l.lang, dom+".po")
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Join(l.path, l.lang[:2], dom+".po")
|
||||
}
|
||||
|
||||
return filename
|
||||
}
|
||||
|
||||
// AddDomain creates a new domain for a given locale object and initializes the Po object.
|
||||
// If the domain exists, it gets reloaded.
|
||||
func (l *Locale) AddDomain(dom string) {
|
||||
po := new(Po)
|
||||
|
||||
// Check for file.
|
||||
filename := path.Clean(l.path + string(os.PathSeparator) + l.lang + string(os.PathSeparator) + dom + ".po")
|
||||
|
||||
// Try to use the generic language dir if the provided isn't available
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Clean(l.path + string(os.PathSeparator) + l.lang[:2] + string(os.PathSeparator) + dom + ".po")
|
||||
}
|
||||
}
|
||||
|
||||
// Parse file.
|
||||
po.ParseFile(filename)
|
||||
po.ParseFile(l.findPO(dom))
|
||||
|
||||
// Save new domain
|
||||
l.Lock()
|
||||
@@ -92,21 +109,19 @@ func (l *Locale) Get(str string, vars ...interface{}) string {
|
||||
return l.GetD("default", str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form translation for the given string in the "default" domain.
|
||||
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
|
||||
// GetN retrieves the (N)th plural form of translation for the given string in the "default" domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return l.GetND("default", str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding translation in the given domain for a given string.
|
||||
// GetD returns the corresponding translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
|
||||
return l.GetND(dom, str, str, 0, vars...)
|
||||
return l.GetND(dom, str, str, 1, vars...)
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form translation in the given domain for a given string.
|
||||
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
|
||||
// GetND retrieves the (N)th plural form of translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -122,5 +137,42 @@ func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) stri
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return fmt.Sprintf(plural, vars...)
|
||||
return printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC uses a domain "default" to return the corresponding translation of the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return l.GetDC("default", str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the "default" domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return l.GetNDC("default", str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetDC returns the corresponding translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||
return l.GetNDC(dom, str, str, 1, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.domains != nil {
|
||||
if _, ok := l.domains[dom]; ok {
|
||||
if l.domains[dom] != nil {
|
||||
return l.domains[dom].GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return printf(plural, vars...)
|
||||
}
|
||||
|
||||
195
locale_test.go
195
locale_test.go
@@ -8,7 +8,17 @@ import (
|
||||
|
||||
func TestLocale(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `# Some comment
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
# Initial comment
|
||||
# Headers below
|
||||
"Language: en\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
@@ -22,17 +32,39 @@ msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
`
|
||||
msgid "This one has invalid syntax translations"
|
||||
msgid_plural "Plural index"
|
||||
msgstr[abc] "Wrong index"
|
||||
msgstr[1 "Forgot to close brackets"
|
||||
msgstr[0] "Badly formatted string'
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory with simplified language code
|
||||
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en")
|
||||
dirname := path.Join("/tmp", "en", "LC_MESSAGES")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to file
|
||||
filename := path.Clean(dirname + string(os.PathSeparator) + "my_domain.po")
|
||||
filename := path.Join(dirname, "my_domain.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -48,6 +80,9 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
// Create Locale with full language code
|
||||
l := NewLocale("/tmp", "en_US")
|
||||
|
||||
// Force nil domain storage
|
||||
l.domains = nil
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("my_domain")
|
||||
|
||||
@@ -64,9 +99,145 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
}
|
||||
|
||||
// Test plural
|
||||
tr = l.GetND("my_domain", "One with var: %s", "Several with vars: %s", 2, v)
|
||||
if tr != "And this is the second plural form: Variable" {
|
||||
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
|
||||
tr = l.GetND("my_domain", "One with var: %s", "Several with vars: %s", 7, v)
|
||||
if tr != "This one is the plural: Variable" {
|
||||
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test context translations
|
||||
v = "Test"
|
||||
tr = l.GetDC("my_domain", "One with var: %s", "Ctx", v)
|
||||
if tr != "This one is the singular in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the singular in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test plural
|
||||
tr = l.GetNDC("my_domain", "One with var: %s", "Several with vars: %s", 3, "Ctx", v)
|
||||
if tr != "This one is the plural in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test last translation
|
||||
tr = l.GetD("my_domain", "More")
|
||||
if tr != "More translation" {
|
||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocaleFails(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
# Initial comment
|
||||
# Headers below
|
||||
"Language: en\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
msgid "This one has invalid syntax translations"
|
||||
msgid_plural "Plural index"
|
||||
msgstr[abc] "Wrong index"
|
||||
msgstr[1 "Forgot to close brackets"
|
||||
msgstr[0] "Badly formatted string'
|
||||
|
||||
msgid "Invalid formatted id[] with no translations
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory with simplified language code
|
||||
dirname := path.Join("/tmp", "en", "LC_MESSAGES")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to file
|
||||
filename := path.Join(dirname, "my_domain.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(str)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Create Locale with full language code
|
||||
l := NewLocale("/tmp", "en_US")
|
||||
|
||||
// Force nil domain storage
|
||||
l.domains = nil
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("my_domain")
|
||||
|
||||
// Test non-existent "deafult" domain responses
|
||||
tr := l.Get("My text")
|
||||
if tr != "My text" {
|
||||
t.Errorf("Expected 'My text' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
tr = l.GetN("One with var: %s", "Several with vars: %s", 2, v)
|
||||
if tr != "Several with vars: Variable" {
|
||||
t.Errorf("Expected 'Several with vars: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test inexistent translations
|
||||
tr = l.Get("This is a test")
|
||||
if tr != "This is a test" {
|
||||
t.Errorf("Expected 'This is a test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetN("This is a test", "This are tests", 1)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,17 +257,17 @@ msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
`
|
||||
`
|
||||
|
||||
// Create Locales directory with simplified language code
|
||||
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "es")
|
||||
dirname := path.Join("/tmp", "es")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to file
|
||||
filename := path.Clean(dirname + string(os.PathSeparator) + "race.po")
|
||||
filename := path.Join(dirname, "race.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
@@ -124,12 +295,12 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
// Get translations in goroutine
|
||||
go func(l *Locale, done chan bool) {
|
||||
println(l.GetD("race", "My text"))
|
||||
l.GetD("race", "My text")
|
||||
done <- true
|
||||
}(l, rc)
|
||||
|
||||
// Get translations at top level
|
||||
println(l.GetD("race", "My text"))
|
||||
l.GetD("race", "My text")
|
||||
|
||||
// Wait for goroutines to finish
|
||||
<-ac
|
||||
|
||||
430
po.go
430
po.go
@@ -1,17 +1,20 @@
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"bufio"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/kinako/vm"
|
||||
)
|
||||
|
||||
type translation struct {
|
||||
id string
|
||||
pluralId string
|
||||
pluralID string
|
||||
trs map[int]string
|
||||
}
|
||||
|
||||
@@ -25,7 +28,9 @@ func newTranslation() *translation {
|
||||
func (t *translation) get() string {
|
||||
// Look for translation index 0
|
||||
if _, ok := t.trs[0]; ok {
|
||||
return t.trs[0]
|
||||
if t.trs[0] != "" {
|
||||
return t.trs[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Return unstranlated id by default
|
||||
@@ -35,42 +40,80 @@ func (t *translation) get() string {
|
||||
func (t *translation) getN(n int) string {
|
||||
// Look for translation index
|
||||
if _, ok := t.trs[n]; ok {
|
||||
return t.trs[n]
|
||||
if t.trs[n] != "" {
|
||||
return t.trs[n]
|
||||
}
|
||||
}
|
||||
|
||||
// Return unstranlated plural by default
|
||||
return t.pluralId
|
||||
// Return unstranlated singular if corresponding
|
||||
if n == 0 {
|
||||
return t.id
|
||||
}
|
||||
|
||||
// Return untranslated plural by default
|
||||
return t.pluralID
|
||||
}
|
||||
|
||||
/*
|
||||
Po parses the content of any PO file and provides all the translation functions needed.
|
||||
It's the base object used by all packafe methods.
|
||||
And it's safe for concurrent use by multiple goroutines by using the sync package for write locking.
|
||||
It's the base object used by all package methods.
|
||||
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||
|
||||
Example:
|
||||
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create po object
|
||||
po := new(gotext.Po)
|
||||
func main() {
|
||||
// Create po object
|
||||
po := new(gotext.Po)
|
||||
|
||||
// Parse .po file
|
||||
po.ParseFile("/path/to/po/file/translations.po")
|
||||
// Parse .po file
|
||||
po.ParseFile("/path/to/po/file/translations.po")
|
||||
|
||||
// Get translation
|
||||
println(po.Get("Translate this"))
|
||||
}
|
||||
// Get translation
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
|
||||
*/
|
||||
type Po struct {
|
||||
// Headers storage
|
||||
Headers textproto.MIMEHeader
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
nplurals int
|
||||
plural string
|
||||
|
||||
// Storage
|
||||
translations map[string]*translation
|
||||
contexts map[string]map[string]*translation
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *translation
|
||||
ctxBuffer string
|
||||
}
|
||||
|
||||
type parseState int
|
||||
|
||||
const (
|
||||
head parseState = iota
|
||||
msgCtxt
|
||||
msgID
|
||||
msgIDPlural
|
||||
msgStr
|
||||
)
|
||||
|
||||
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
||||
func (po *Po) ParseFile(f string) {
|
||||
// Check if file exists
|
||||
@@ -95,92 +138,284 @@ func (po *Po) ParseFile(f string) {
|
||||
|
||||
// Parse loads the translations specified in the provided string (str)
|
||||
func (po *Po) Parse(str string) {
|
||||
// Lock while parsing
|
||||
po.Lock()
|
||||
|
||||
// Init storage
|
||||
if po.translations == nil {
|
||||
po.Lock()
|
||||
po.translations = make(map[string]*translation)
|
||||
po.Unlock()
|
||||
po.contexts = make(map[string]map[string]*translation)
|
||||
}
|
||||
|
||||
// Get lines
|
||||
lines := strings.Split(str, "\n")
|
||||
|
||||
tr := newTranslation()
|
||||
// Init buffer
|
||||
po.trBuffer = newTranslation()
|
||||
po.ctxBuffer = ""
|
||||
|
||||
state := head
|
||||
for _, l := range lines {
|
||||
// Trim spaces
|
||||
l = strings.TrimSpace(l)
|
||||
|
||||
// Skip empty lines
|
||||
if l == "" {
|
||||
// Skip invalid lines
|
||||
if !po.isValidLine(l) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip invalid lines
|
||||
if !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") {
|
||||
// Buffer context and continue
|
||||
if strings.HasPrefix(l, "msgctxt") {
|
||||
po.parseContext(l)
|
||||
state = msgCtxt
|
||||
continue
|
||||
}
|
||||
|
||||
// Buffer msgid and continue
|
||||
if strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") {
|
||||
// Save current translation buffer.
|
||||
po.Lock()
|
||||
po.translations[tr.id] = tr
|
||||
po.Unlock()
|
||||
|
||||
// Flush buffer
|
||||
tr = newTranslation()
|
||||
|
||||
// Set id
|
||||
tr.id, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
|
||||
// Loop
|
||||
po.parseID(l)
|
||||
state = msgID
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for plural form
|
||||
if strings.HasPrefix(l, "msgid_plural") {
|
||||
tr.pluralId, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
|
||||
// Loop
|
||||
po.parsePluralID(l)
|
||||
state = msgIDPlural
|
||||
continue
|
||||
}
|
||||
|
||||
// Save translation
|
||||
if strings.HasPrefix(l, "msgstr") {
|
||||
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
||||
po.parseMessage(l)
|
||||
state = msgStr
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for indexed translation forms
|
||||
if strings.HasPrefix(l, "[") {
|
||||
in := strings.Index(l, "]")
|
||||
if in == -1 {
|
||||
// Skip wrong index formatting
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse index
|
||||
i, err := strconv.Atoi(l[1:in])
|
||||
if err != nil {
|
||||
// Skip wrong index formatting
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse translation string
|
||||
tr.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[in+1:]))
|
||||
|
||||
// Loop
|
||||
continue
|
||||
}
|
||||
|
||||
// Save single translation form under 0 index
|
||||
tr.trs[0], _ = strconv.Unquote(l)
|
||||
// Multi line strings and headers
|
||||
if strings.HasPrefix(l, "\"") && strings.HasSuffix(l, "\"") {
|
||||
po.parseString(l, state)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Save last translation buffer.
|
||||
if tr.id != "" {
|
||||
po.Lock()
|
||||
po.translations[tr.id] = tr
|
||||
po.Unlock()
|
||||
po.saveBuffer()
|
||||
|
||||
// Unlock to parse headers
|
||||
po.Unlock()
|
||||
|
||||
// Parse headers
|
||||
po.parseHeaders()
|
||||
}
|
||||
|
||||
// saveBuffer takes the context and translation buffers
|
||||
// and saves it on the translations collection
|
||||
func (po *Po) saveBuffer() {
|
||||
// With no context...
|
||||
if po.ctxBuffer == "" {
|
||||
po.translations[po.trBuffer.id] = po.trBuffer
|
||||
} else {
|
||||
// With context...
|
||||
if _, ok := po.contexts[po.ctxBuffer]; !ok {
|
||||
po.contexts[po.ctxBuffer] = make(map[string]*translation)
|
||||
}
|
||||
po.contexts[po.ctxBuffer][po.trBuffer.id] = po.trBuffer
|
||||
|
||||
// Cleanup current context buffer if needed
|
||||
if po.trBuffer.id != "" {
|
||||
po.ctxBuffer = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Flush translation buffer
|
||||
po.trBuffer = newTranslation()
|
||||
}
|
||||
|
||||
// parseContext takes a line starting with "msgctxt",
|
||||
// saves the current translation buffer and creates a new context.
|
||||
func (po *Po) parseContext(l string) {
|
||||
// Save current translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Buffer context
|
||||
po.ctxBuffer, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgctxt")))
|
||||
}
|
||||
|
||||
// parseID takes a line starting with "msgid",
|
||||
// saves the current translation and creates a new msgid buffer.
|
||||
func (po *Po) parseID(l string) {
|
||||
// Save current translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Set id
|
||||
po.trBuffer.id, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
}
|
||||
|
||||
// parsePluralID saves the plural id buffer from a line starting with "msgid_plural"
|
||||
func (po *Po) parsePluralID(l string) {
|
||||
po.trBuffer.pluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
}
|
||||
|
||||
// parseMessage takes a line starting with "msgstr" and saves it into the current buffer.
|
||||
func (po *Po) parseMessage(l string) {
|
||||
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
||||
|
||||
// Check for indexed translation forms
|
||||
if strings.HasPrefix(l, "[") {
|
||||
idx := strings.Index(l, "]")
|
||||
if idx == -1 {
|
||||
// Skip wrong index formatting
|
||||
return
|
||||
}
|
||||
|
||||
// Parse index
|
||||
i, err := strconv.Atoi(l[1:idx])
|
||||
if err != nil {
|
||||
// Skip wrong index formatting
|
||||
return
|
||||
}
|
||||
|
||||
// Parse translation string
|
||||
po.trBuffer.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
|
||||
// Loop
|
||||
return
|
||||
}
|
||||
|
||||
// Save single translation form under 0 index
|
||||
po.trBuffer.trs[0], _ = strconv.Unquote(l)
|
||||
}
|
||||
|
||||
// parseString takes a well formatted string without prefix
|
||||
// and creates headers or attach multi-line strings when corresponding
|
||||
func (po *Po) parseString(l string, state parseState) {
|
||||
clean, _ := strconv.Unquote(l)
|
||||
|
||||
switch state {
|
||||
case msgStr:
|
||||
// Append to last translation found
|
||||
po.trBuffer.trs[len(po.trBuffer.trs)-1] += clean
|
||||
|
||||
case msgID:
|
||||
// Multiline msgid - Append to current id
|
||||
po.trBuffer.id += clean
|
||||
|
||||
case msgIDPlural:
|
||||
// Multiline msgid - Append to current id
|
||||
po.trBuffer.pluralID += clean
|
||||
|
||||
case msgCtxt:
|
||||
// Multiline context - Append to current context
|
||||
po.ctxBuffer += clean
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// isValidLine checks for line prefixes to detect valid syntax.
|
||||
func (po *Po) isValidLine(l string) bool {
|
||||
// Check prefix
|
||||
valid := []string{
|
||||
"\"",
|
||||
"msgctxt",
|
||||
"msgid",
|
||||
"msgid_plural",
|
||||
"msgstr",
|
||||
}
|
||||
|
||||
for _, v := range valid {
|
||||
if strings.HasPrefix(l, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseHeaders retrieves data from previously parsed headers
|
||||
func (po *Po) parseHeaders() {
|
||||
// Make sure we end with 2 carriage returns.
|
||||
raw := po.Get("") + "\n\n"
|
||||
|
||||
// Read
|
||||
reader := bufio.NewReader(strings.NewReader(raw))
|
||||
tp := textproto.NewReader(reader)
|
||||
|
||||
var err error
|
||||
|
||||
// Sync Headers write.
|
||||
po.Lock()
|
||||
defer po.Unlock()
|
||||
|
||||
po.Headers, err = tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get/save needed headers
|
||||
po.Language = po.Headers.Get("Language")
|
||||
po.PluralForms = po.Headers.Get("Plural-Forms")
|
||||
|
||||
// Parse Plural-Forms formula
|
||||
if po.PluralForms == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Split plural form header value
|
||||
pfs := strings.Split(po.PluralForms, ";")
|
||||
|
||||
// Parse values
|
||||
for _, i := range pfs {
|
||||
vs := strings.SplitN(i, "=", 2)
|
||||
if len(vs) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(vs[0]) {
|
||||
case "nplurals":
|
||||
po.nplurals, _ = strconv.Atoi(vs[1])
|
||||
|
||||
case "plural":
|
||||
po.plural = vs[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pluralForm calculates the plural form index corresponding to n.
|
||||
// Returns 0 on error
|
||||
func (po *Po) pluralForm(n int) int {
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
// Failsafe
|
||||
if po.nplurals < 1 {
|
||||
return 0
|
||||
}
|
||||
if po.plural == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Init compiler
|
||||
env := vm.NewEnv()
|
||||
env.Define("n", n)
|
||||
|
||||
plural, err := env.Execute(po.plural)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
if plural.Type().Name() == "bool" {
|
||||
if plural.Bool() {
|
||||
return 1
|
||||
}
|
||||
// Else
|
||||
return 0
|
||||
}
|
||||
|
||||
if int(plural.Int()) > po.nplurals {
|
||||
return 0
|
||||
}
|
||||
|
||||
return int(plural.Int())
|
||||
}
|
||||
|
||||
// Get retrieves the corresponding translation for the given string.
|
||||
@@ -192,16 +427,15 @@ func (po *Po) Get(str string, vars ...interface{}) string {
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return fmt.Sprintf(po.translations[str].get(), vars...)
|
||||
return printf(po.translations[str].get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return fmt.Sprintf(str, vars...)
|
||||
return printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form translation for the given string.
|
||||
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
|
||||
// GetN retrieves the (N)th plural form of translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -210,10 +444,56 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return fmt.Sprintf(po.translations[str].getN(n), vars...)
|
||||
return printf(po.translations[str].getN(po.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the plural string we received by default
|
||||
return fmt.Sprintf(plural, vars...)
|
||||
if n == 1 {
|
||||
return printf(str, vars...)
|
||||
}
|
||||
return printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.contexts != nil {
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
return printf(po.contexts[ctx][str].get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.contexts != nil {
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
return printf(po.contexts[ctx][str].getN(po.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return printf(str, vars...)
|
||||
}
|
||||
return printf(plural, vars...)
|
||||
}
|
||||
|
||||
438
po_test.go
438
po_test.go
@@ -8,7 +8,18 @@ import (
|
||||
|
||||
func TestPo(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `# Some comment
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
"Language: en\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
@@ -16,13 +27,56 @@ msgstr "Translated text"
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid "multi"
|
||||
"line"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid "multi"
|
||||
"line"
|
||||
"plural"
|
||||
"id"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
#Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr "Multi "
|
||||
"line"
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
`
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
|
||||
`
|
||||
|
||||
// Write PO content to file
|
||||
filename := path.Clean(os.TempDir() + string(os.PathSeparator) + "default.po")
|
||||
|
||||
@@ -37,8 +91,13 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Parse po file
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Try to parse a directory
|
||||
po.ParseFile(path.Clean(os.TempDir()))
|
||||
|
||||
// Parse file
|
||||
po.ParseFile(filename)
|
||||
|
||||
// Test translations
|
||||
@@ -53,10 +112,373 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line id
|
||||
tr = po.Get("multilineid")
|
||||
if tr != "id with multiline content" {
|
||||
t.Errorf("Expected 'id with multiline content' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line plural id
|
||||
tr = po.Get("multilinepluralid")
|
||||
if tr != "plural id with multiline content" {
|
||||
t.Errorf("Expected 'plural id with multiline content' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line
|
||||
tr = po.Get("Multi-line")
|
||||
if tr != "Multi line" {
|
||||
t.Errorf("Expected 'Multi line' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test plural
|
||||
tr = po.GetN("One with var: %s", "Several with vars: %s", 2, v)
|
||||
if tr != "And this is the second plural form: Variable" {
|
||||
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
|
||||
if tr != "This one is the plural: Variable" {
|
||||
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test inexistent translations
|
||||
tr = po.Get("This is a test")
|
||||
if tr != "This is a test" {
|
||||
t.Errorf("Expected 'This is a test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.GetN("This is a test", "This are tests", 100)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test context translations
|
||||
v = "Test"
|
||||
tr = po.GetC("One with var: %s", "Ctx", v)
|
||||
if tr != "This one is the singular in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the singular in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test plural
|
||||
tr = po.GetNC("One with var: %s", "Several with vars: %s", 17, "Ctx", v)
|
||||
if tr != "This one is the plural in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test default plural vs singular return responses
|
||||
tr = po.GetN("Original", "Original plural", 4)
|
||||
if tr != "Original plural" {
|
||||
t.Errorf("Expected 'Original plural' but got '%s'", tr)
|
||||
}
|
||||
tr = po.GetN("Original", "Original plural", 1)
|
||||
if tr != "Original" {
|
||||
t.Errorf("Expected 'Original' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test empty translation strings
|
||||
tr = po.Get("Empty translation")
|
||||
if tr != "Empty translation" {
|
||||
t.Errorf("Expected 'Empty translation' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.Get("Empty plural form singular")
|
||||
if tr != "Singular translated" {
|
||||
t.Errorf("Expected 'Singular translated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.GetN("Empty plural form singular", "Empty plural form", 1)
|
||||
if tr != "Singular translated" {
|
||||
t.Errorf("Expected 'Singular translated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.GetN("Empty plural form singular", "Empty plural form", 2)
|
||||
if tr != "Empty plural form" {
|
||||
t.Errorf("Expected 'Empty plural form' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test last translation
|
||||
tr = po.Get("More")
|
||||
if tr != "More translation" {
|
||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlural(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Singular: %s"
|
||||
msgid_plural "Plural: %s"
|
||||
msgstr[0] "TR Singular: %s"
|
||||
msgstr[1] "TR Plural: %s"
|
||||
msgstr[2] "TR Plural 2: %s"
|
||||
|
||||
|
||||
`
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
po.Parse(str)
|
||||
|
||||
v := "Var"
|
||||
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
||||
if tr != "TR Plural: Var" {
|
||||
t.Errorf("Expected 'TR Plural: Var' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.GetN("Singular: %s", "Plural: %s", 1, v)
|
||||
if tr != "TR Singular: Var" {
|
||||
t.Errorf("Expected 'TR Singular: Var' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoHeaders(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
# Initial comment
|
||||
# Headers below
|
||||
"Language: en\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
# Some comment
|
||||
msgid "Example"
|
||||
msgstr "Translated example"
|
||||
`
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
|
||||
// Check headers expected
|
||||
if po.Language != "en" {
|
||||
t.Errorf("Expected 'Language: en' but got '%s'", po.Language)
|
||||
}
|
||||
|
||||
// Check headers expected
|
||||
if po.PluralForms != "nplurals=2; plural=(n != 1);" {
|
||||
t.Errorf("Expected 'Plural-Forms: nplurals=2; plural=(n != 1);' but got '%s'", po.PluralForms)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingPoHeadersSupport(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid "Example"
|
||||
msgstr "Translated example"
|
||||
`
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
|
||||
// Check translation expected
|
||||
if po.Get("Example") != "Translated example" {
|
||||
t.Errorf("Expected 'Translated example' but got '%s'", po.Get("Example"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluralFormsSingle(t *testing.T) {
|
||||
// Single form
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;"
|
||||
|
||||
# Some comment
|
||||
msgid "Singular"
|
||||
msgid_plural "Plural"
|
||||
msgstr[0] "Singular form"
|
||||
msgstr[1] "Plural form 1"
|
||||
msgstr[2] "Plural form 2"
|
||||
msgstr[3] "Plural form 3"
|
||||
`
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 for pluralForm(0), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(1)
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 for pluralForm(1), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(2)
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 for pluralForm(2), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(3)
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 for pluralForm(3), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(50)
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 for pluralForm(50), got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluralForms2(t *testing.T) {
|
||||
// 2 forms
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;"
|
||||
|
||||
# Some comment
|
||||
msgid "Singular"
|
||||
msgid_plural "Plural"
|
||||
msgstr[0] "Singular form"
|
||||
msgstr[1] "Plural form 1"
|
||||
msgstr[2] "Plural form 2"
|
||||
msgstr[3] "Plural form 3"
|
||||
`
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 1 for pluralForm(0), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(1)
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 for pluralForm(1), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(2)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 1 for pluralForm(2), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(3)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 1 for pluralForm(3), got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluralForms3(t *testing.T) {
|
||||
// 3 forms
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;"
|
||||
|
||||
# Some comment
|
||||
msgid "Singular"
|
||||
msgid_plural "Plural"
|
||||
msgstr[0] "Singular form"
|
||||
msgstr[1] "Plural form 1"
|
||||
msgstr[2] "Plural form 2"
|
||||
msgstr[3] "Plural form 3"
|
||||
`
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
if n != 2 {
|
||||
t.Errorf("Expected 2 for pluralForm(0), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(1)
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 for pluralForm(1), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(2)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 1 for pluralForm(2), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(3)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 1 for pluralForm(3), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(100)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 1 for pluralForm(100), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(49)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 1 for pluralForm(3), got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluralFormsSpecial(t *testing.T) {
|
||||
// 3 forms special
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=3;"
|
||||
"plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"
|
||||
|
||||
# Some comment
|
||||
msgid "Singular"
|
||||
msgid_plural "Plural"
|
||||
msgstr[0] "Singular form"
|
||||
msgstr[1] "Plural form 1"
|
||||
msgstr[2] "Plural form 2"
|
||||
msgstr[3] "Plural form 3"
|
||||
`
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(1)
|
||||
if n != 0 {
|
||||
t.Errorf("Expected 0 for pluralForm(1), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(2)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 1 for pluralForm(2), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(4)
|
||||
if n != 1 {
|
||||
t.Errorf("Expected 4 for pluralForm(4), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(0)
|
||||
if n != 2 {
|
||||
t.Errorf("Expected 2 for pluralForm(2), got %d", n)
|
||||
}
|
||||
n = po.pluralForm(1000)
|
||||
if n != 2 {
|
||||
t.Errorf("Expected 2 for pluralForm(1000), got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslationObject(t *testing.T) {
|
||||
tr := newTranslation()
|
||||
str := tr.get()
|
||||
|
||||
if str != "" {
|
||||
t.Errorf("Expected '' but got '%s'", str)
|
||||
}
|
||||
|
||||
// Set id
|
||||
tr.id = "Text"
|
||||
|
||||
// Get again
|
||||
str = tr.get()
|
||||
|
||||
if str != "Text" {
|
||||
t.Errorf("Expected 'Text' but got '%s'", str)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +498,7 @@ msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
`
|
||||
`
|
||||
|
||||
// Create Po object
|
||||
po := new(Po)
|
||||
@@ -93,12 +515,12 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
// Read some translation on a goroutine
|
||||
go func(po *Po, done chan bool) {
|
||||
println(po.Get("My text"))
|
||||
po.Get("My text")
|
||||
done <- true
|
||||
}(po, rc)
|
||||
|
||||
// Read something at top level
|
||||
println(po.Get("My text"))
|
||||
po.Get("My text")
|
||||
|
||||
// Wait for goroutines to finish
|
||||
<-pc
|
||||
|
||||
37
vendor/github.com/mattn/kinako/README.md
generated
vendored
Normal file
37
vendor/github.com/mattn/kinako/README.md
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# kinako
|
||||
|
||||
Kinako is small VM written in Go.
|
||||
|
||||

|
||||
|
||||
(Picture licensed under CC BY-SA 3.0 by wikipedia)
|
||||
|
||||
## Installation
|
||||
Requires Go.
|
||||
```
|
||||
$ go get -u github.com/mattn/kinako
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Embedding the interpreter into your own program:
|
||||
|
||||
```Go
|
||||
var env = vm.NewEnv()
|
||||
|
||||
env.Define("foo", 1)
|
||||
val, err := env.Execute(`foo + 3`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(val)
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
18
vendor/github.com/mattn/kinako/_example/main.go
generated
vendored
Normal file
18
vendor/github.com/mattn/kinako/_example/main.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/mattn/kinako/vm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
env := vm.NewEnv()
|
||||
v, err := env.Execute(`foo=1; foo+3`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(v)
|
||||
}
|
||||
112
vendor/github.com/mattn/kinako/ast/expr.go
generated
vendored
Normal file
112
vendor/github.com/mattn/kinako/ast/expr.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
package ast
|
||||
|
||||
type Token struct {
|
||||
Tok int
|
||||
Lit string
|
||||
}
|
||||
|
||||
// Position provides interface to store code locations.
|
||||
type Position struct {
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// Expr provides all of interfaces for expression.
|
||||
type Expr interface {
|
||||
expr()
|
||||
}
|
||||
|
||||
// ExprImpl provide commonly implementations for Expr.
|
||||
type ExprImpl struct {
|
||||
}
|
||||
|
||||
// expr provide restraint interface.
|
||||
func (x *ExprImpl) expr() {}
|
||||
|
||||
// NumberExpr provide Number expression.
|
||||
type NumberExpr struct {
|
||||
ExprImpl
|
||||
Lit string
|
||||
}
|
||||
|
||||
// UnaryExpr provide unary minus expression. ex: -1, ^1, ~1.
|
||||
type UnaryExpr struct {
|
||||
ExprImpl
|
||||
Operator string
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// IdentExpr provide identity expression.
|
||||
type IdentExpr struct {
|
||||
ExprImpl
|
||||
Lit string
|
||||
}
|
||||
|
||||
// Stmt provides all of interfaces for statement.
|
||||
type Stmt interface {
|
||||
stmt()
|
||||
}
|
||||
|
||||
// StmtImpl provide commonly implementations for Stmt..
|
||||
type StmtImpl struct {
|
||||
}
|
||||
|
||||
// stmt provide restraint interface.
|
||||
func (x *StmtImpl) stmt() {}
|
||||
|
||||
// LetsStmt provide multiple statement of let.
|
||||
type LetsStmt struct {
|
||||
StmtImpl
|
||||
Lhss []Expr
|
||||
Operator string
|
||||
Rhss []Expr
|
||||
}
|
||||
|
||||
// StringExpr provide String expression.
|
||||
type StringExpr struct {
|
||||
ExprImpl
|
||||
Lit string
|
||||
}
|
||||
|
||||
type TernaryOpExpr struct {
|
||||
ExprImpl
|
||||
Expr Expr
|
||||
Lhs Expr
|
||||
Rhs Expr
|
||||
}
|
||||
|
||||
// CallExpr provide calling expression.
|
||||
type CallExpr struct {
|
||||
ExprImpl
|
||||
Func interface{}
|
||||
Name string
|
||||
SubExprs []Expr
|
||||
}
|
||||
|
||||
// ParenExpr provide parent block expression.
|
||||
type ParenExpr struct {
|
||||
ExprImpl
|
||||
SubExpr Expr
|
||||
}
|
||||
|
||||
// BinOpExpr provide binary operator expression.
|
||||
type BinOpExpr struct {
|
||||
ExprImpl
|
||||
Lhs Expr
|
||||
Operator string
|
||||
Rhs Expr
|
||||
}
|
||||
|
||||
// ExprStmt provide expression statement.
|
||||
type ExprStmt struct {
|
||||
StmtImpl
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// LetStmt provide statement of let.
|
||||
type LetStmt struct {
|
||||
StmtImpl
|
||||
Lhs Expr
|
||||
Operator string
|
||||
Rhs Expr
|
||||
}
|
||||
BIN
vendor/github.com/mattn/kinako/kinako.png
generated
vendored
Normal file
BIN
vendor/github.com/mattn/kinako/kinako.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 450 KiB |
4
vendor/github.com/mattn/kinako/parser/Makefile
generated
vendored
Normal file
4
vendor/github.com/mattn/kinako/parser/Makefile
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
all : parser.go
|
||||
|
||||
parser.go : parser.go.y
|
||||
goyacc -o $@ parser.go.y
|
||||
427
vendor/github.com/mattn/kinako/parser/lexer.go
generated
vendored
Normal file
427
vendor/github.com/mattn/kinako/parser/lexer.go
generated
vendored
Normal file
@@ -0,0 +1,427 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unicode"
|
||||
|
||||
"github.com/mattn/kinako/ast"
|
||||
)
|
||||
|
||||
const (
|
||||
EOF = -1 // End of file.
|
||||
EOL = '\n' // End of line.
|
||||
)
|
||||
|
||||
// Error provides a convenient interface for handling runtime error.
|
||||
// It can be Error inteface with type cast which can call Pos().
|
||||
type Error struct {
|
||||
Message string
|
||||
Filename string
|
||||
Fatal bool
|
||||
}
|
||||
|
||||
// Error returns the error message.
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Scanner stores informations for lexer.
|
||||
type Scanner struct {
|
||||
src []rune
|
||||
offset int
|
||||
lineHead int
|
||||
line int
|
||||
}
|
||||
|
||||
// Init resets code to scan.
|
||||
func (s *Scanner) Init(src string) {
|
||||
s.src = []rune(src)
|
||||
}
|
||||
|
||||
// Scan analyses token, and decide identify or literals.
|
||||
func (s *Scanner) Scan() (tok int, lit string, pos ast.Position, err error) {
|
||||
retry:
|
||||
s.skipBlank()
|
||||
pos = s.pos()
|
||||
switch ch := s.peek(); {
|
||||
case isLetter(ch):
|
||||
tok = IDENT
|
||||
lit, err = s.scanIdentifier()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case isDigit(ch):
|
||||
tok = NUMBER
|
||||
lit, err = s.scanNumber()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case ch == '"':
|
||||
tok = STRING
|
||||
lit, err = s.scanString('"')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case ch == '\'':
|
||||
tok = STRING
|
||||
lit, err = s.scanString('\'')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case ch == '`':
|
||||
tok = STRING
|
||||
lit, err = s.scanRawString()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
switch ch {
|
||||
case EOF:
|
||||
tok = EOF
|
||||
case '#':
|
||||
for !isEOL(s.peek()) {
|
||||
s.next()
|
||||
}
|
||||
goto retry
|
||||
case '!':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '=':
|
||||
tok = NEQ
|
||||
lit = "!="
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '=':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '=':
|
||||
tok = EQEQ
|
||||
lit = "=="
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '+':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '-':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '*':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '/':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '>':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '=':
|
||||
tok = GE
|
||||
lit = ">="
|
||||
case '>':
|
||||
tok = SHIFTRIGHT
|
||||
lit = ">>"
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '<':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '=':
|
||||
tok = LE
|
||||
lit = "<="
|
||||
case '<':
|
||||
tok = SHIFTLEFT
|
||||
lit = "<<"
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '|':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '|':
|
||||
tok = OROR
|
||||
lit = "||"
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '&':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case '&':
|
||||
tok = ANDAND
|
||||
lit = "&&"
|
||||
default:
|
||||
s.back()
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
}
|
||||
case '.':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '\n':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
case '(', ')', ':', ';', '%', '?', '{', '}', ',', '[', ']', '^':
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
default:
|
||||
err = fmt.Errorf(`syntax error "%s"`, string(ch))
|
||||
tok = int(ch)
|
||||
lit = string(ch)
|
||||
return
|
||||
}
|
||||
s.next()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isLetter returns true if the rune is a letter for identity.
|
||||
func isLetter(ch rune) bool {
|
||||
return unicode.IsLetter(ch) || ch == '_'
|
||||
}
|
||||
|
||||
// isDigit returns true if the rune is a number.
|
||||
func isDigit(ch rune) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
// isHex returns true if the rune is a hex digits.
|
||||
func isHex(ch rune) bool {
|
||||
return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
|
||||
}
|
||||
|
||||
// isEOL returns true if the rune is at end-of-line or end-of-file.
|
||||
func isEOL(ch rune) bool {
|
||||
return ch == '\n' || ch == -1
|
||||
}
|
||||
|
||||
// isBlank returns true if the rune is empty character..
|
||||
func isBlank(ch rune) bool {
|
||||
return ch == ' ' || ch == '\t' || ch == '\r'
|
||||
}
|
||||
|
||||
// peek returns current rune in the code.
|
||||
func (s *Scanner) peek() rune {
|
||||
if s.reachEOF() {
|
||||
return EOF
|
||||
}
|
||||
return s.src[s.offset]
|
||||
}
|
||||
|
||||
// next moves offset to next.
|
||||
func (s *Scanner) next() {
|
||||
if !s.reachEOF() {
|
||||
if s.peek() == '\n' {
|
||||
s.lineHead = s.offset + 1
|
||||
s.line++
|
||||
}
|
||||
s.offset++
|
||||
}
|
||||
}
|
||||
|
||||
// current returns the current offset.
|
||||
func (s *Scanner) current() int {
|
||||
return s.offset
|
||||
}
|
||||
|
||||
// offset sets the offset value.
|
||||
func (s *Scanner) set(o int) {
|
||||
s.offset = o
|
||||
}
|
||||
|
||||
// back moves back offset once to top.
|
||||
func (s *Scanner) back() {
|
||||
s.offset--
|
||||
}
|
||||
|
||||
// reachEOF returns true if offset is at end-of-file.
|
||||
func (s *Scanner) reachEOF() bool {
|
||||
return len(s.src) <= s.offset
|
||||
}
|
||||
|
||||
// pos returns the position of current.
|
||||
func (s *Scanner) pos() ast.Position {
|
||||
return ast.Position{Line: s.line + 1, Column: s.offset - s.lineHead + 1}
|
||||
}
|
||||
|
||||
// skipBlank moves position into non-black character.
|
||||
func (s *Scanner) skipBlank() {
|
||||
for isBlank(s.peek()) {
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
|
||||
// scanIdentifier returns identifier begining at current position.
|
||||
func (s *Scanner) scanIdentifier() (string, error) {
|
||||
var ret []rune
|
||||
for {
|
||||
if !isLetter(s.peek()) && !isDigit(s.peek()) {
|
||||
break
|
||||
}
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// scanNumber returns number begining at current position.
|
||||
func (s *Scanner) scanNumber() (string, error) {
|
||||
var ret []rune
|
||||
ch := s.peek()
|
||||
ret = append(ret, ch)
|
||||
s.next()
|
||||
if ch == '0' && s.peek() == 'x' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
for isHex(s.peek()) {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
} else {
|
||||
for isDigit(s.peek()) || s.peek() == '.' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
if s.peek() == 'e' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
if isDigit(s.peek()) || s.peek() == '+' || s.peek() == '-' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
for isDigit(s.peek()) || s.peek() == '.' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
for isDigit(s.peek()) || s.peek() == '.' {
|
||||
ret = append(ret, s.peek())
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
if isLetter(s.peek()) {
|
||||
return "", errors.New("identifier starts immediately after numeric literal")
|
||||
}
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// scanRawString returns raw-string starting at current position.
|
||||
func (s *Scanner) scanRawString() (string, error) {
|
||||
var ret []rune
|
||||
for {
|
||||
s.next()
|
||||
if s.peek() == EOF {
|
||||
return "", errors.New("unexpected EOF")
|
||||
break
|
||||
}
|
||||
if s.peek() == '`' {
|
||||
s.next()
|
||||
break
|
||||
}
|
||||
ret = append(ret, s.peek())
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// scanString returns string starting at current position.
|
||||
// This handles backslash escaping.
|
||||
func (s *Scanner) scanString(l rune) (string, error) {
|
||||
var ret []rune
|
||||
eos:
|
||||
for {
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case EOL:
|
||||
return "", errors.New("unexpected EOL")
|
||||
case EOF:
|
||||
return "", errors.New("unexpected EOF")
|
||||
case l:
|
||||
s.next()
|
||||
break eos
|
||||
case '\\':
|
||||
s.next()
|
||||
switch s.peek() {
|
||||
case 'b':
|
||||
ret = append(ret, '\b')
|
||||
continue
|
||||
case 'f':
|
||||
ret = append(ret, '\f')
|
||||
continue
|
||||
case 'r':
|
||||
ret = append(ret, '\r')
|
||||
continue
|
||||
case 'n':
|
||||
ret = append(ret, '\n')
|
||||
continue
|
||||
case 't':
|
||||
ret = append(ret, '\t')
|
||||
continue
|
||||
}
|
||||
ret = append(ret, s.peek())
|
||||
continue
|
||||
default:
|
||||
ret = append(ret, s.peek())
|
||||
}
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
// Lexer provides inteface to parse codes.
|
||||
type Lexer struct {
|
||||
s *Scanner
|
||||
lit string
|
||||
pos ast.Position
|
||||
e error
|
||||
stmts []ast.Stmt
|
||||
}
|
||||
|
||||
// Lex scans the token and literals.
|
||||
func (l *Lexer) Lex(lval *yySymType) int {
|
||||
tok, lit, pos, err := l.s.Scan()
|
||||
if err != nil {
|
||||
l.e = &Error{Message: fmt.Sprintf("%s", err.Error()), Fatal: true}
|
||||
}
|
||||
lval.tok = ast.Token{Tok: tok, Lit: lit}
|
||||
l.lit = lit
|
||||
l.pos = pos
|
||||
return tok
|
||||
}
|
||||
|
||||
// Error sets parse error.
|
||||
func (l *Lexer) Error(msg string) {
|
||||
l.e = &Error{Message: msg, Fatal: false}
|
||||
}
|
||||
|
||||
// Parser provides way to parse the code using Scanner.
|
||||
func Parse(s *Scanner) ([]ast.Stmt, error) {
|
||||
l := Lexer{s: s}
|
||||
if yyParse(&l) != 0 {
|
||||
return nil, l.e
|
||||
}
|
||||
return l.stmts, l.e
|
||||
}
|
||||
|
||||
func EnableErrorVerbose() {
|
||||
yyErrorVerbose = true
|
||||
}
|
||||
|
||||
// ParserSrc provides way to parse the code from source.
|
||||
func ParseSrc(src string) ([]ast.Stmt, error) {
|
||||
scanner := &Scanner{
|
||||
src: []rune(src),
|
||||
}
|
||||
return Parse(scanner)
|
||||
}
|
||||
778
vendor/github.com/mattn/kinako/parser/parser.go
generated
vendored
Normal file
778
vendor/github.com/mattn/kinako/parser/parser.go
generated
vendored
Normal file
@@ -0,0 +1,778 @@
|
||||
//line parser.go.y:2
|
||||
package parser
|
||||
|
||||
import __yyfmt__ "fmt"
|
||||
|
||||
//line parser.go.y:2
|
||||
import (
|
||||
"github.com/mattn/kinako/ast"
|
||||
)
|
||||
|
||||
//line parser.go.y:16
|
||||
type yySymType struct {
|
||||
yys int
|
||||
compstmt []ast.Stmt
|
||||
stmts []ast.Stmt
|
||||
stmt ast.Stmt
|
||||
expr ast.Expr
|
||||
exprs []ast.Expr
|
||||
tok ast.Token
|
||||
term ast.Token
|
||||
terms ast.Token
|
||||
opt_terms ast.Token
|
||||
}
|
||||
|
||||
const IDENT = 57346
|
||||
const NUMBER = 57347
|
||||
const STRING = 57348
|
||||
const EQEQ = 57349
|
||||
const NEQ = 57350
|
||||
const GE = 57351
|
||||
const LE = 57352
|
||||
const OROR = 57353
|
||||
const ANDAND = 57354
|
||||
const POW = 57355
|
||||
const SHIFTLEFT = 57356
|
||||
const SHIFTRIGHT = 57357
|
||||
const PLUSPLUS = 57358
|
||||
const MINUSMINUS = 57359
|
||||
const UNARY = 57360
|
||||
|
||||
var yyToknames = [...]string{
|
||||
"$end",
|
||||
"error",
|
||||
"$unk",
|
||||
"IDENT",
|
||||
"NUMBER",
|
||||
"STRING",
|
||||
"EQEQ",
|
||||
"NEQ",
|
||||
"GE",
|
||||
"LE",
|
||||
"OROR",
|
||||
"ANDAND",
|
||||
"POW",
|
||||
"'='",
|
||||
"'?'",
|
||||
"':'",
|
||||
"','",
|
||||
"'>'",
|
||||
"'<'",
|
||||
"SHIFTLEFT",
|
||||
"SHIFTRIGHT",
|
||||
"'+'",
|
||||
"'-'",
|
||||
"PLUSPLUS",
|
||||
"MINUSMINUS",
|
||||
"'*'",
|
||||
"'/'",
|
||||
"'%'",
|
||||
"UNARY",
|
||||
"'!'",
|
||||
"'^'",
|
||||
"'('",
|
||||
"')'",
|
||||
"'|'",
|
||||
"'&'",
|
||||
"';'",
|
||||
"'\\n'",
|
||||
}
|
||||
var yyStatenames = [...]string{}
|
||||
|
||||
const yyEofCode = 1
|
||||
const yyErrCode = 2
|
||||
const yyInitialStackSize = 16
|
||||
|
||||
//line parser.go.y:213
|
||||
|
||||
//line yacctab:1
|
||||
var yyExca = [...]int{
|
||||
-1, 1,
|
||||
1, -1,
|
||||
-2, 0,
|
||||
-1, 50,
|
||||
7, 0,
|
||||
8, 0,
|
||||
-2, 20,
|
||||
-1, 51,
|
||||
7, 0,
|
||||
8, 0,
|
||||
-2, 21,
|
||||
}
|
||||
|
||||
const yyNprod = 40
|
||||
const yyPrivate = 57344
|
||||
|
||||
var yyTokenNames []string
|
||||
var yyStates []string
|
||||
|
||||
const yyLast = 251
|
||||
|
||||
var yyAct = [...]int{
|
||||
|
||||
9, 6, 7, 33, 35, 37, 22, 23, 60, 3,
|
||||
24, 25, 26, 38, 39, 40, 1, 41, 33, 35,
|
||||
8, 43, 44, 45, 46, 47, 48, 49, 50, 51,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 61, 42,
|
||||
27, 28, 30, 32, 34, 36, 65, 0, 21, 63,
|
||||
4, 29, 31, 2, 18, 22, 23, 17, 0, 24,
|
||||
25, 26, 64, 0, 66, 0, 67, 33, 35, 27,
|
||||
28, 30, 32, 34, 36, 0, 0, 21, 0, 0,
|
||||
29, 31, 0, 0, 22, 23, 0, 0, 24, 25,
|
||||
26, 0, 0, 0, 0, 62, 33, 35, 27, 28,
|
||||
30, 32, 34, 36, 0, 20, 21, 0, 0, 29,
|
||||
31, 0, 0, 22, 23, 5, 0, 24, 25, 26,
|
||||
19, 0, 0, 0, 0, 33, 35, 27, 28, 30,
|
||||
32, 34, 36, 0, 19, 21, 0, 0, 29, 31,
|
||||
0, 0, 22, 23, 0, 0, 24, 25, 26, 0,
|
||||
0, 0, 0, 0, 33, 35, 27, 28, 30, 32,
|
||||
0, 36, 0, 0, 0, 0, 0, 29, 31, 0,
|
||||
0, 22, 23, 0, 0, 24, 25, 26, 27, 28,
|
||||
30, 32, 0, 33, 35, 0, 0, 0, 0, 29,
|
||||
31, 0, 0, 22, 23, 0, 0, 24, 25, 26,
|
||||
30, 32, 10, 11, 15, 33, 35, 0, 0, 29,
|
||||
31, 0, 0, 22, 23, 0, 0, 24, 25, 26,
|
||||
0, 12, 10, 11, 15, 33, 35, 0, 13, 14,
|
||||
16, 24, 25, 26, 6, 7, 0, 0, 0, 33,
|
||||
35, 12, 0, 0, 0, 0, 0, 0, 13, 14,
|
||||
16,
|
||||
}
|
||||
var yyPact = [...]int{
|
||||
|
||||
-35, -1000, 218, -35, -35, -1000, -1000, -1000, -1000, 91,
|
||||
-27, -1000, 218, 218, 218, -1000, 218, -1000, 198, -1000,
|
||||
218, 218, 218, 218, 218, 218, 218, 218, 218, 218,
|
||||
218, 218, 218, 218, 218, 218, 218, 218, -31, -31,
|
||||
-31, 62, -1000, 120, 33, 205, 205, -31, -31, -31,
|
||||
191, 191, -16, -16, -16, -16, 120, 149, 120, 171,
|
||||
29, 120, -1000, 218, -1000, 218, 120, 120,
|
||||
}
|
||||
var yyPgo = [...]int{
|
||||
|
||||
0, 16, 9, 20, 0, 8, 53, 50, 115,
|
||||
}
|
||||
var yyR1 = [...]int{
|
||||
|
||||
0, 1, 1, 2, 2, 3, 3, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 5, 5, 5, 6, 6, 7, 7, 8, 8,
|
||||
}
|
||||
var yyR2 = [...]int{
|
||||
|
||||
0, 1, 2, 2, 3, 3, 1, 1, 1, 2,
|
||||
2, 2, 1, 5, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
4, 0, 1, 3, 0, 1, 1, 2, 1, 1,
|
||||
}
|
||||
var yyChk = [...]int{
|
||||
|
||||
-1000, -1, -6, -2, -7, -8, 36, 37, -3, -4,
|
||||
4, 5, 23, 30, 31, 6, 32, -6, -7, -8,
|
||||
14, 15, 22, 23, 26, 27, 28, 7, 8, 18,
|
||||
9, 19, 10, 34, 11, 35, 12, 32, -4, -4,
|
||||
-4, -4, -3, -4, -4, -4, -4, -4, -4, -4,
|
||||
-4, -4, -4, -4, -4, -4, -4, -4, -4, -4,
|
||||
-5, -4, 33, 16, 33, 17, -4, -4,
|
||||
}
|
||||
var yyDef = [...]int{
|
||||
|
||||
34, -2, 1, 34, 35, 36, 38, 39, 3, 6,
|
||||
7, 8, 0, 0, 0, 12, 0, 2, 35, 37,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 31, 9, 10,
|
||||
11, 0, 4, 5, 0, 15, 16, 17, 18, 19,
|
||||
-2, -2, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
0, 32, 14, 0, 30, 0, 13, 33,
|
||||
}
|
||||
var yyTok1 = [...]int{
|
||||
|
||||
1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
37, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 30, 3, 3, 3, 28, 35, 3,
|
||||
32, 33, 26, 22, 17, 23, 3, 27, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 16, 36,
|
||||
19, 14, 18, 15, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 31, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 34,
|
||||
}
|
||||
var yyTok2 = [...]int{
|
||||
|
||||
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
12, 13, 20, 21, 24, 25, 29,
|
||||
}
|
||||
var yyTok3 = [...]int{
|
||||
0,
|
||||
}
|
||||
|
||||
var yyErrorMessages = [...]struct {
|
||||
state int
|
||||
token int
|
||||
msg string
|
||||
}{}
|
||||
|
||||
//line yaccpar:1
|
||||
|
||||
/* parser for yacc output */
|
||||
|
||||
var (
|
||||
yyDebug = 0
|
||||
yyErrorVerbose = false
|
||||
)
|
||||
|
||||
type yyLexer interface {
|
||||
Lex(lval *yySymType) int
|
||||
Error(s string)
|
||||
}
|
||||
|
||||
type yyParser interface {
|
||||
Parse(yyLexer) int
|
||||
Lookahead() int
|
||||
}
|
||||
|
||||
type yyParserImpl struct {
|
||||
lval yySymType
|
||||
stack [yyInitialStackSize]yySymType
|
||||
char int
|
||||
}
|
||||
|
||||
func (p *yyParserImpl) Lookahead() int {
|
||||
return p.char
|
||||
}
|
||||
|
||||
func yyNewParser() yyParser {
|
||||
return &yyParserImpl{}
|
||||
}
|
||||
|
||||
const yyFlag = -1000
|
||||
|
||||
func yyTokname(c int) string {
|
||||
if c >= 1 && c-1 < len(yyToknames) {
|
||||
if yyToknames[c-1] != "" {
|
||||
return yyToknames[c-1]
|
||||
}
|
||||
}
|
||||
return __yyfmt__.Sprintf("tok-%v", c)
|
||||
}
|
||||
|
||||
func yyStatname(s int) string {
|
||||
if s >= 0 && s < len(yyStatenames) {
|
||||
if yyStatenames[s] != "" {
|
||||
return yyStatenames[s]
|
||||
}
|
||||
}
|
||||
return __yyfmt__.Sprintf("state-%v", s)
|
||||
}
|
||||
|
||||
func yyErrorMessage(state, lookAhead int) string {
|
||||
const TOKSTART = 4
|
||||
|
||||
if !yyErrorVerbose {
|
||||
return "syntax error"
|
||||
}
|
||||
|
||||
for _, e := range yyErrorMessages {
|
||||
if e.state == state && e.token == lookAhead {
|
||||
return "syntax error: " + e.msg
|
||||
}
|
||||
}
|
||||
|
||||
res := "syntax error: unexpected " + yyTokname(lookAhead)
|
||||
|
||||
// To match Bison, suggest at most four expected tokens.
|
||||
expected := make([]int, 0, 4)
|
||||
|
||||
// Look for shiftable tokens.
|
||||
base := yyPact[state]
|
||||
for tok := TOKSTART; tok-1 < len(yyToknames); tok++ {
|
||||
if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok {
|
||||
if len(expected) == cap(expected) {
|
||||
return res
|
||||
}
|
||||
expected = append(expected, tok)
|
||||
}
|
||||
}
|
||||
|
||||
if yyDef[state] == -2 {
|
||||
i := 0
|
||||
for yyExca[i] != -1 || yyExca[i+1] != state {
|
||||
i += 2
|
||||
}
|
||||
|
||||
// Look for tokens that we accept or reduce.
|
||||
for i += 2; yyExca[i] >= 0; i += 2 {
|
||||
tok := yyExca[i]
|
||||
if tok < TOKSTART || yyExca[i+1] == 0 {
|
||||
continue
|
||||
}
|
||||
if len(expected) == cap(expected) {
|
||||
return res
|
||||
}
|
||||
expected = append(expected, tok)
|
||||
}
|
||||
|
||||
// If the default action is to accept or reduce, give up.
|
||||
if yyExca[i+1] != 0 {
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
for i, tok := range expected {
|
||||
if i == 0 {
|
||||
res += ", expecting "
|
||||
} else {
|
||||
res += " or "
|
||||
}
|
||||
res += yyTokname(tok)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func yylex1(lex yyLexer, lval *yySymType) (char, token int) {
|
||||
token = 0
|
||||
char = lex.Lex(lval)
|
||||
if char <= 0 {
|
||||
token = yyTok1[0]
|
||||
goto out
|
||||
}
|
||||
if char < len(yyTok1) {
|
||||
token = yyTok1[char]
|
||||
goto out
|
||||
}
|
||||
if char >= yyPrivate {
|
||||
if char < yyPrivate+len(yyTok2) {
|
||||
token = yyTok2[char-yyPrivate]
|
||||
goto out
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(yyTok3); i += 2 {
|
||||
token = yyTok3[i+0]
|
||||
if token == char {
|
||||
token = yyTok3[i+1]
|
||||
goto out
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if token == 0 {
|
||||
token = yyTok2[1] /* unknown char */
|
||||
}
|
||||
if yyDebug >= 3 {
|
||||
__yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char))
|
||||
}
|
||||
return char, token
|
||||
}
|
||||
|
||||
func yyParse(yylex yyLexer) int {
|
||||
return yyNewParser().Parse(yylex)
|
||||
}
|
||||
|
||||
func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int {
|
||||
var yyn int
|
||||
var yyVAL yySymType
|
||||
var yyDollar []yySymType
|
||||
_ = yyDollar // silence set and not used
|
||||
yyS := yyrcvr.stack[:]
|
||||
|
||||
Nerrs := 0 /* number of errors */
|
||||
Errflag := 0 /* error recovery flag */
|
||||
yystate := 0
|
||||
yyrcvr.char = -1
|
||||
yytoken := -1 // yyrcvr.char translated into internal numbering
|
||||
defer func() {
|
||||
// Make sure we report no lookahead when not parsing.
|
||||
yystate = -1
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
}()
|
||||
yyp := -1
|
||||
goto yystack
|
||||
|
||||
ret0:
|
||||
return 0
|
||||
|
||||
ret1:
|
||||
return 1
|
||||
|
||||
yystack:
|
||||
/* put a state and value onto the stack */
|
||||
if yyDebug >= 4 {
|
||||
__yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate))
|
||||
}
|
||||
|
||||
yyp++
|
||||
if yyp >= len(yyS) {
|
||||
nyys := make([]yySymType, len(yyS)*2)
|
||||
copy(nyys, yyS)
|
||||
yyS = nyys
|
||||
}
|
||||
yyS[yyp] = yyVAL
|
||||
yyS[yyp].yys = yystate
|
||||
|
||||
yynewstate:
|
||||
yyn = yyPact[yystate]
|
||||
if yyn <= yyFlag {
|
||||
goto yydefault /* simple state */
|
||||
}
|
||||
if yyrcvr.char < 0 {
|
||||
yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)
|
||||
}
|
||||
yyn += yytoken
|
||||
if yyn < 0 || yyn >= yyLast {
|
||||
goto yydefault
|
||||
}
|
||||
yyn = yyAct[yyn]
|
||||
if yyChk[yyn] == yytoken { /* valid shift */
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
yyVAL = yyrcvr.lval
|
||||
yystate = yyn
|
||||
if Errflag > 0 {
|
||||
Errflag--
|
||||
}
|
||||
goto yystack
|
||||
}
|
||||
|
||||
yydefault:
|
||||
/* default state action */
|
||||
yyn = yyDef[yystate]
|
||||
if yyn == -2 {
|
||||
if yyrcvr.char < 0 {
|
||||
yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)
|
||||
}
|
||||
|
||||
/* look through exception table */
|
||||
xi := 0
|
||||
for {
|
||||
if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate {
|
||||
break
|
||||
}
|
||||
xi += 2
|
||||
}
|
||||
for xi += 2; ; xi += 2 {
|
||||
yyn = yyExca[xi+0]
|
||||
if yyn < 0 || yyn == yytoken {
|
||||
break
|
||||
}
|
||||
}
|
||||
yyn = yyExca[xi+1]
|
||||
if yyn < 0 {
|
||||
goto ret0
|
||||
}
|
||||
}
|
||||
if yyn == 0 {
|
||||
/* error ... attempt to resume parsing */
|
||||
switch Errflag {
|
||||
case 0: /* brand new error */
|
||||
yylex.Error(yyErrorMessage(yystate, yytoken))
|
||||
Nerrs++
|
||||
if yyDebug >= 1 {
|
||||
__yyfmt__.Printf("%s", yyStatname(yystate))
|
||||
__yyfmt__.Printf(" saw %s\n", yyTokname(yytoken))
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 1, 2: /* incompletely recovered error ... try again */
|
||||
Errflag = 3
|
||||
|
||||
/* find a state where "error" is a legal shift action */
|
||||
for yyp >= 0 {
|
||||
yyn = yyPact[yyS[yyp].yys] + yyErrCode
|
||||
if yyn >= 0 && yyn < yyLast {
|
||||
yystate = yyAct[yyn] /* simulate a shift of "error" */
|
||||
if yyChk[yystate] == yyErrCode {
|
||||
goto yystack
|
||||
}
|
||||
}
|
||||
|
||||
/* the current p has no shift on "error", pop stack */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys)
|
||||
}
|
||||
yyp--
|
||||
}
|
||||
/* there is no state on the stack with an error shift ... abort */
|
||||
goto ret1
|
||||
|
||||
case 3: /* no shift yet; clobber input char */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken))
|
||||
}
|
||||
if yytoken == yyEofCode {
|
||||
goto ret1
|
||||
}
|
||||
yyrcvr.char = -1
|
||||
yytoken = -1
|
||||
goto yynewstate /* try again in the same state */
|
||||
}
|
||||
}
|
||||
|
||||
/* reduction by production yyn */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate))
|
||||
}
|
||||
|
||||
yynt := yyn
|
||||
yypt := yyp
|
||||
_ = yypt // guard against "declared and not used"
|
||||
|
||||
yyp -= yyR2[yyn]
|
||||
// yyp is now the index of $0. Perform the default action. Iff the
|
||||
// reduced production is ε, $1 is possibly out of range.
|
||||
if yyp+1 >= len(yyS) {
|
||||
nyys := make([]yySymType, len(yyS)*2)
|
||||
copy(nyys, yyS)
|
||||
yyS = nyys
|
||||
}
|
||||
yyVAL = yyS[yyp+1]
|
||||
|
||||
/* consult goto table to find next state */
|
||||
yyn = yyR1[yyn]
|
||||
yyg := yyPgo[yyn]
|
||||
yyj := yyg + yyS[yyp].yys + 1
|
||||
|
||||
if yyj >= yyLast {
|
||||
yystate = yyAct[yyg]
|
||||
} else {
|
||||
yystate = yyAct[yyj]
|
||||
if yyChk[yystate] != -yyn {
|
||||
yystate = yyAct[yyg]
|
||||
}
|
||||
}
|
||||
// dummy call; replaced with literal code
|
||||
switch yynt {
|
||||
|
||||
case 1:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:45
|
||||
{
|
||||
yyVAL.compstmt = nil
|
||||
}
|
||||
case 2:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:49
|
||||
{
|
||||
yyVAL.compstmt = yyDollar[1].stmts
|
||||
}
|
||||
case 3:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:55
|
||||
{
|
||||
yyVAL.stmts = []ast.Stmt{yyDollar[2].stmt}
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.stmts = yyVAL.stmts
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:62
|
||||
{
|
||||
if yyDollar[3].stmt != nil {
|
||||
yyVAL.stmts = append(yyDollar[1].stmts, yyDollar[3].stmt)
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.stmts = yyVAL.stmts
|
||||
}
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:73
|
||||
{
|
||||
yyVAL.stmt = &ast.LetStmt{Lhs: yyDollar[1].expr, Operator: "=", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 6:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:77
|
||||
{
|
||||
yyVAL.stmt = &ast.ExprStmt{Expr: yyDollar[1].expr}
|
||||
}
|
||||
case 7:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:83
|
||||
{
|
||||
yyVAL.expr = &ast.IdentExpr{Lit: yyDollar[1].tok.Lit}
|
||||
}
|
||||
case 8:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:87
|
||||
{
|
||||
yyVAL.expr = &ast.NumberExpr{Lit: yyDollar[1].tok.Lit}
|
||||
}
|
||||
case 9:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:91
|
||||
{
|
||||
yyVAL.expr = &ast.UnaryExpr{Operator: "-", Expr: yyDollar[2].expr}
|
||||
}
|
||||
case 10:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:95
|
||||
{
|
||||
yyVAL.expr = &ast.UnaryExpr{Operator: "!", Expr: yyDollar[2].expr}
|
||||
}
|
||||
case 11:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:99
|
||||
{
|
||||
yyVAL.expr = &ast.UnaryExpr{Operator: "^", Expr: yyDollar[2].expr}
|
||||
}
|
||||
case 12:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:103
|
||||
{
|
||||
yyVAL.expr = &ast.StringExpr{Lit: yyDollar[1].tok.Lit}
|
||||
}
|
||||
case 13:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line parser.go.y:107
|
||||
{
|
||||
yyVAL.expr = &ast.TernaryOpExpr{Expr: yyDollar[1].expr, Lhs: yyDollar[3].expr, Rhs: yyDollar[5].expr}
|
||||
}
|
||||
case 14:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:111
|
||||
{
|
||||
yyVAL.expr = &ast.ParenExpr{SubExpr: yyDollar[2].expr}
|
||||
}
|
||||
case 15:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:115
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "+", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 16:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:119
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "-", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 17:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:123
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "*", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 18:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:127
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "/", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 19:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:131
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "%", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 20:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:135
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "==", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 21:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:139
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "!=", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 22:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:143
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 23:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:147
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">=", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 24:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:151
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 25:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:155
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<=", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 26:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:159
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "|", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 27:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:163
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "||", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 28:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:167
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 29:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:171
|
||||
{
|
||||
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&&", Rhs: yyDollar[3].expr}
|
||||
}
|
||||
case 30:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line parser.go.y:175
|
||||
{
|
||||
yyVAL.expr = &ast.CallExpr{Name: yyDollar[1].tok.Lit, SubExprs: yyDollar[3].exprs}
|
||||
}
|
||||
case 31:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
//line parser.go.y:180
|
||||
{
|
||||
yyVAL.exprs = nil
|
||||
}
|
||||
case 32:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:184
|
||||
{
|
||||
yyVAL.exprs = []ast.Expr{yyDollar[1].expr}
|
||||
}
|
||||
case 33:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line parser.go.y:188
|
||||
{
|
||||
yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[3].expr)
|
||||
}
|
||||
case 36:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:198
|
||||
{
|
||||
}
|
||||
case 37:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line parser.go.y:201
|
||||
{
|
||||
}
|
||||
case 38:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:206
|
||||
{
|
||||
}
|
||||
case 39:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line parser.go.y:209
|
||||
{
|
||||
}
|
||||
}
|
||||
goto yystack /* stack new state and value */
|
||||
}
|
||||
214
vendor/github.com/mattn/kinako/parser/parser.go.y
generated
vendored
Normal file
214
vendor/github.com/mattn/kinako/parser/parser.go.y
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
%{
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/mattn/kinako/ast"
|
||||
)
|
||||
|
||||
%}
|
||||
|
||||
%type<compstmt> compstmt
|
||||
%type<stmts> stmts
|
||||
%type<stmt> stmt
|
||||
%type<expr> expr
|
||||
%type<exprs> exprs
|
||||
|
||||
%union{
|
||||
compstmt []ast.Stmt
|
||||
stmts []ast.Stmt
|
||||
stmt ast.Stmt
|
||||
expr ast.Expr
|
||||
exprs []ast.Expr
|
||||
tok ast.Token
|
||||
term ast.Token
|
||||
terms ast.Token
|
||||
opt_terms ast.Token
|
||||
}
|
||||
|
||||
%token<tok> IDENT NUMBER STRING EQEQ NEQ GE LE OROR ANDAND POW
|
||||
|
||||
%right '='
|
||||
%right '?' ':'
|
||||
%left OROR
|
||||
%left ANDAND
|
||||
%left IDENT
|
||||
%nonassoc EQEQ NEQ ','
|
||||
%left '>' GE '<' LE SHIFTLEFT SHIFTRIGHT
|
||||
|
||||
%left '+' '-' PLUSPLUS MINUSMINUS
|
||||
%left '*' '/' '%'
|
||||
%right UNARY
|
||||
|
||||
%%
|
||||
|
||||
compstmt : opt_terms
|
||||
{
|
||||
$$ = nil
|
||||
}
|
||||
| stmts opt_terms
|
||||
{
|
||||
$$ = $1
|
||||
}
|
||||
|
||||
stmts :
|
||||
opt_terms stmt
|
||||
{
|
||||
$$ = []ast.Stmt{$2}
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.stmts = $$
|
||||
}
|
||||
}
|
||||
| stmts terms stmt
|
||||
{
|
||||
if $3 != nil {
|
||||
$$ = append($1, $3)
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.stmts = $$
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stmt :
|
||||
expr '=' expr
|
||||
{
|
||||
$$ = &ast.LetStmt{Lhs: $1, Operator: "=", Rhs: $3}
|
||||
}
|
||||
| expr
|
||||
{
|
||||
$$ = &ast.ExprStmt{Expr: $1}
|
||||
}
|
||||
|
||||
expr :
|
||||
IDENT
|
||||
{
|
||||
$$ = &ast.IdentExpr{Lit: $1.Lit}
|
||||
}
|
||||
| NUMBER
|
||||
{
|
||||
$$ = &ast.NumberExpr{Lit: $1.Lit}
|
||||
}
|
||||
| '-' expr %prec UNARY
|
||||
{
|
||||
$$ = &ast.UnaryExpr{Operator: "-", Expr: $2}
|
||||
}
|
||||
| '!' expr %prec UNARY
|
||||
{
|
||||
$$ = &ast.UnaryExpr{Operator: "!", Expr: $2}
|
||||
}
|
||||
| '^' expr %prec UNARY
|
||||
{
|
||||
$$ = &ast.UnaryExpr{Operator: "^", Expr: $2}
|
||||
}
|
||||
| STRING
|
||||
{
|
||||
$$ = &ast.StringExpr{Lit: $1.Lit}
|
||||
}
|
||||
| expr '?' expr ':' expr
|
||||
{
|
||||
$$ = &ast.TernaryOpExpr{Expr: $1, Lhs: $3, Rhs: $5}
|
||||
}
|
||||
| '(' expr ')'
|
||||
{
|
||||
$$ = &ast.ParenExpr{SubExpr: $2}
|
||||
}
|
||||
| expr '+' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "+", Rhs: $3}
|
||||
}
|
||||
| expr '-' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "-", Rhs: $3}
|
||||
}
|
||||
| expr '*' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "*", Rhs: $3}
|
||||
}
|
||||
| expr '/' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "/", Rhs: $3}
|
||||
}
|
||||
| expr '%' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "%", Rhs: $3}
|
||||
}
|
||||
| expr EQEQ expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "==", Rhs: $3}
|
||||
}
|
||||
| expr NEQ expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "!=", Rhs: $3}
|
||||
}
|
||||
| expr '>' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">", Rhs: $3}
|
||||
}
|
||||
| expr GE expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">=", Rhs: $3}
|
||||
}
|
||||
| expr '<' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<", Rhs: $3}
|
||||
}
|
||||
| expr LE expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<=", Rhs: $3}
|
||||
}
|
||||
| expr '|' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "|", Rhs: $3}
|
||||
}
|
||||
| expr OROR expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "||", Rhs: $3}
|
||||
}
|
||||
| expr '&' expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&", Rhs: $3}
|
||||
}
|
||||
| expr ANDAND expr
|
||||
{
|
||||
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&&", Rhs: $3}
|
||||
}
|
||||
| IDENT '(' exprs ')'
|
||||
{
|
||||
$$ = &ast.CallExpr{Name: $1.Lit, SubExprs: $3}
|
||||
}
|
||||
|
||||
exprs :
|
||||
{
|
||||
$$ = nil
|
||||
}
|
||||
| expr
|
||||
{
|
||||
$$ = []ast.Expr{$1}
|
||||
}
|
||||
| exprs ',' expr
|
||||
{
|
||||
$$ = append($1, $3)
|
||||
}
|
||||
|
||||
opt_terms : /* none */
|
||||
| terms
|
||||
;
|
||||
|
||||
|
||||
terms : term
|
||||
{
|
||||
}
|
||||
| terms term
|
||||
{
|
||||
}
|
||||
;
|
||||
|
||||
term : ';'
|
||||
{
|
||||
}
|
||||
| '\n'
|
||||
{
|
||||
}
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
1381
vendor/github.com/mattn/kinako/parser/y.output
generated
vendored
Normal file
1381
vendor/github.com/mattn/kinako/parser/y.output
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
258
vendor/github.com/mattn/kinako/vm/env.go
generated
vendored
Normal file
258
vendor/github.com/mattn/kinako/vm/env.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/kinako/parser"
|
||||
)
|
||||
|
||||
// Env provides interface to run VM. This mean function scope and blocked-scope.
|
||||
// If stack goes to blocked-scope, it will make new Env.
|
||||
type Env struct {
|
||||
name string
|
||||
env map[string]reflect.Value
|
||||
typ map[string]reflect.Type
|
||||
parent *Env
|
||||
interrupt *bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewEnv creates new global scope.
|
||||
func NewEnv() *Env {
|
||||
b := false
|
||||
|
||||
return &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
typ: make(map[string]reflect.Type),
|
||||
parent: nil,
|
||||
interrupt: &b,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEnv creates new child scope.
|
||||
func (e *Env) NewEnv() *Env {
|
||||
return &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
typ: make(map[string]reflect.Type),
|
||||
parent: e,
|
||||
name: e.name,
|
||||
interrupt: e.interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewPackage(n string) *Env {
|
||||
b := false
|
||||
|
||||
return &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
typ: make(map[string]reflect.Type),
|
||||
parent: nil,
|
||||
name: n,
|
||||
interrupt: &b,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Env) NewPackage(n string) *Env {
|
||||
return &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
typ: make(map[string]reflect.Type),
|
||||
parent: e,
|
||||
name: n,
|
||||
interrupt: e.interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy deletes current scope.
|
||||
func (e *Env) Destroy() {
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
|
||||
if e.parent == nil {
|
||||
return
|
||||
}
|
||||
for k, v := range e.parent.env {
|
||||
if v.IsValid() && v.Interface() == e {
|
||||
delete(e.parent.env, k)
|
||||
}
|
||||
}
|
||||
e.parent = nil
|
||||
e.env = nil
|
||||
}
|
||||
|
||||
// NewModule creates new module scope as global.
|
||||
func (e *Env) NewModule(n string) *Env {
|
||||
m := &Env{
|
||||
env: make(map[string]reflect.Value),
|
||||
parent: e,
|
||||
name: n,
|
||||
}
|
||||
e.Define(n, m)
|
||||
return m
|
||||
}
|
||||
|
||||
// SetName sets a name of the scope. This means that the scope is module.
|
||||
func (e *Env) SetName(n string) {
|
||||
e.Lock()
|
||||
e.name = n
|
||||
e.Unlock()
|
||||
}
|
||||
|
||||
// GetName returns module name.
|
||||
func (e *Env) GetName() string {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
return e.name
|
||||
}
|
||||
|
||||
// Addr returns pointer value which specified symbol. It goes to upper scope until
|
||||
// found or returns error.
|
||||
func (e *Env) Addr(k string) (reflect.Value, error) {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
if v, ok := e.env[k]; ok {
|
||||
return v.Addr(), nil
|
||||
}
|
||||
if e.parent == nil {
|
||||
return NilValue, fmt.Errorf("Undefined symbol '%s'", k)
|
||||
}
|
||||
return e.parent.Addr(k)
|
||||
}
|
||||
|
||||
// Type returns type which specified symbol. It goes to upper scope until
|
||||
// found or returns error.
|
||||
func (e *Env) Type(k string) (reflect.Type, error) {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
if v, ok := e.typ[k]; ok {
|
||||
return v, nil
|
||||
}
|
||||
if e.parent == nil {
|
||||
return NilType, fmt.Errorf("Undefined type '%s'", k)
|
||||
}
|
||||
return e.parent.Type(k)
|
||||
}
|
||||
|
||||
// Get returns value which specified symbol. It goes to upper scope until
|
||||
// found or returns error.
|
||||
func (e *Env) Get(k string) (reflect.Value, error) {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
if v, ok := e.env[k]; ok {
|
||||
return v, nil
|
||||
}
|
||||
if e.parent == nil {
|
||||
return NilValue, fmt.Errorf("Undefined symbol '%s'", k)
|
||||
}
|
||||
return e.parent.Get(k)
|
||||
}
|
||||
|
||||
// Set modifies value which specified as symbol. It goes to upper scope until
|
||||
// found or returns error.
|
||||
func (e *Env) Set(k string, v interface{}) error {
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
|
||||
if _, ok := e.env[k]; ok {
|
||||
val, ok := v.(reflect.Value)
|
||||
if !ok {
|
||||
val = reflect.ValueOf(v)
|
||||
}
|
||||
e.env[k] = val
|
||||
return nil
|
||||
}
|
||||
if e.parent == nil {
|
||||
return fmt.Errorf("Unknown symbol '%s'", k)
|
||||
}
|
||||
return e.parent.Set(k, v)
|
||||
}
|
||||
|
||||
// DefineGlobal defines symbol in global scope.
|
||||
func (e *Env) DefineGlobal(k string, v interface{}) error {
|
||||
if e.parent == nil {
|
||||
return e.Define(k, v)
|
||||
}
|
||||
return e.parent.DefineGlobal(k, v)
|
||||
}
|
||||
|
||||
// DefineType defines type which specifis symbol in global scope.
|
||||
func (e *Env) DefineType(k string, t interface{}) error {
|
||||
if strings.Contains(k, ".") {
|
||||
return fmt.Errorf("Unknown symbol '%s'", k)
|
||||
}
|
||||
global := e
|
||||
keys := []string{k}
|
||||
|
||||
e.RLock()
|
||||
for global.parent != nil {
|
||||
if global.name != "" {
|
||||
keys = append(keys, global.name)
|
||||
}
|
||||
global = global.parent
|
||||
}
|
||||
e.RUnlock()
|
||||
|
||||
for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
|
||||
keys[i], keys[j] = keys[j], keys[i]
|
||||
}
|
||||
|
||||
typ, ok := t.(reflect.Type)
|
||||
if !ok {
|
||||
typ = reflect.TypeOf(t)
|
||||
}
|
||||
|
||||
global.Lock()
|
||||
global.typ[strings.Join(keys, ".")] = typ
|
||||
global.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Define defines symbol in current scope.
|
||||
func (e *Env) Define(k string, v interface{}) error {
|
||||
if strings.Contains(k, ".") {
|
||||
return fmt.Errorf("Unknown symbol '%s'", k)
|
||||
}
|
||||
val, ok := v.(reflect.Value)
|
||||
if !ok {
|
||||
val = reflect.ValueOf(v)
|
||||
}
|
||||
|
||||
e.Lock()
|
||||
e.env[k] = val
|
||||
e.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String return the name of current scope.
|
||||
func (e *Env) String() string {
|
||||
e.RLock()
|
||||
defer e.RUnlock()
|
||||
|
||||
return e.name
|
||||
}
|
||||
|
||||
// Dump show symbol values in the scope.
|
||||
func (e *Env) Dump() {
|
||||
e.RLock()
|
||||
for k, v := range e.env {
|
||||
fmt.Printf("%v = %#v\n", k, v)
|
||||
}
|
||||
e.RUnlock()
|
||||
}
|
||||
|
||||
// Execute parses and runs source in current scope.
|
||||
func (e *Env) Execute(src string) (reflect.Value, error) {
|
||||
stmts, err := parser.ParseSrc(src)
|
||||
if err != nil {
|
||||
return NilValue, err
|
||||
}
|
||||
return Run(stmts, e)
|
||||
}
|
||||
476
vendor/github.com/mattn/kinako/vm/vm.go
generated
vendored
Normal file
476
vendor/github.com/mattn/kinako/vm/vm.go
generated
vendored
Normal file
@@ -0,0 +1,476 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/kinako/ast"
|
||||
)
|
||||
|
||||
var (
|
||||
NilValue = reflect.ValueOf((*interface{})(nil))
|
||||
NilType = reflect.TypeOf((*interface{})(nil))
|
||||
TrueValue = reflect.ValueOf(true)
|
||||
FalseValue = reflect.ValueOf(false)
|
||||
)
|
||||
|
||||
// Error provides a convenient interface for handling runtime error.
|
||||
// It can be Error interface with type cast which can call Pos().
|
||||
type Error struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
var (
|
||||
BreakError = errors.New("Unexpected break statement")
|
||||
ContinueError = errors.New("Unexpected continue statement")
|
||||
ReturnError = errors.New("Unexpected return statement")
|
||||
InterruptError = errors.New("Execution interrupted")
|
||||
)
|
||||
|
||||
// Error returns the error message.
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Func is function interface to reflect functions internaly.
|
||||
type Func func(args ...reflect.Value) (reflect.Value, error)
|
||||
|
||||
// Run executes statements in the specified environment.
|
||||
func Run(stmts []ast.Stmt, env *Env) (reflect.Value, error) {
|
||||
rv := NilValue
|
||||
var err error
|
||||
for _, stmt := range stmts {
|
||||
rv, err = RunSingleStmt(stmt, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// Interrupts the execution of any running statements in the specified environment.
|
||||
//
|
||||
// Note that the execution is not instantly aborted: after a call to Interrupt,
|
||||
// the current running statement will finish, but the next statement will not run,
|
||||
// and instead will return a NilValue and an InterruptError.
|
||||
func Interrupt(env *Env) {
|
||||
env.Lock()
|
||||
*(env.interrupt) = true
|
||||
env.Unlock()
|
||||
}
|
||||
|
||||
// RunSingleStmt executes one statement in the specified environment.
|
||||
func RunSingleStmt(stmt ast.Stmt, env *Env) (reflect.Value, error) {
|
||||
env.Lock()
|
||||
if *(env.interrupt) {
|
||||
*(env.interrupt) = false
|
||||
env.Unlock()
|
||||
|
||||
return NilValue, InterruptError
|
||||
}
|
||||
env.Unlock()
|
||||
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.ExprStmt:
|
||||
rv, err := invokeExpr(stmt.Expr, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
return rv, nil
|
||||
case *ast.LetStmt:
|
||||
rv := NilValue
|
||||
var err error
|
||||
rv, err = invokeExpr(stmt.Rhs, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
_, err = invokeLetExpr(stmt.Lhs, rv, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
return rv, nil
|
||||
default:
|
||||
return NilValue, errors.New("unknown statement")
|
||||
}
|
||||
}
|
||||
|
||||
// toString converts all reflect.Value-s into string.
|
||||
func toString(v reflect.Value) string {
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() == reflect.String {
|
||||
return v.String()
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return "nil"
|
||||
}
|
||||
return fmt.Sprint(v.Interface())
|
||||
}
|
||||
|
||||
// toBool converts all reflect.Value-s into bool.
|
||||
func toBool(v reflect.Value) bool {
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() != 0.0
|
||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||
return v.Int() != 0
|
||||
case reflect.Bool:
|
||||
return v.Bool()
|
||||
case reflect.String:
|
||||
if v.String() == "true" {
|
||||
return true
|
||||
}
|
||||
if toInt64(v) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// toFloat64 converts all reflect.Value-s into float64.
|
||||
func toFloat64(v reflect.Value) float64 {
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float()
|
||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||
return float64(v.Int())
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func isNil(v reflect.Value) bool {
|
||||
if !v.IsValid() || v.Kind().String() == "unsafe.Pointer" {
|
||||
return true
|
||||
}
|
||||
if (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isNum(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// equal returns true when lhsV and rhsV is same value.
|
||||
func equal(lhsV, rhsV reflect.Value) bool {
|
||||
lhsIsNil, rhsIsNil := isNil(lhsV), isNil(rhsV)
|
||||
if lhsIsNil && rhsIsNil {
|
||||
return true
|
||||
}
|
||||
if (!lhsIsNil && rhsIsNil) || (lhsIsNil && !rhsIsNil) {
|
||||
return false
|
||||
}
|
||||
if lhsV.Kind() == reflect.Interface || lhsV.Kind() == reflect.Ptr {
|
||||
lhsV = lhsV.Elem()
|
||||
}
|
||||
if rhsV.Kind() == reflect.Interface || rhsV.Kind() == reflect.Ptr {
|
||||
rhsV = rhsV.Elem()
|
||||
}
|
||||
if !lhsV.IsValid() || !rhsV.IsValid() {
|
||||
return true
|
||||
}
|
||||
if isNum(lhsV) && isNum(rhsV) {
|
||||
if rhsV.Type().ConvertibleTo(lhsV.Type()) {
|
||||
rhsV = rhsV.Convert(lhsV.Type())
|
||||
}
|
||||
}
|
||||
if lhsV.CanInterface() && rhsV.CanInterface() {
|
||||
return reflect.DeepEqual(lhsV.Interface(), rhsV.Interface())
|
||||
}
|
||||
return reflect.DeepEqual(lhsV, rhsV)
|
||||
}
|
||||
|
||||
// toInt64 converts all reflect.Value-s into int64.
|
||||
func toInt64(v reflect.Value) int64 {
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return int64(v.Float())
|
||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||
return v.Int()
|
||||
case reflect.String:
|
||||
s := v.String()
|
||||
var i int64
|
||||
var err error
|
||||
if strings.HasPrefix(s, "0x") {
|
||||
i, err = strconv.ParseInt(s, 16, 64)
|
||||
} else {
|
||||
i, err = strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
if err == nil {
|
||||
return int64(i)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func invokeLetExpr(expr ast.Expr, rv reflect.Value, env *Env) (reflect.Value, error) {
|
||||
switch lhs := expr.(type) {
|
||||
case *ast.IdentExpr:
|
||||
if env.Set(lhs.Lit, rv) != nil {
|
||||
if strings.Contains(lhs.Lit, ".") {
|
||||
return NilValue, fmt.Errorf("Undefined symbol '%s'", lhs.Lit)
|
||||
}
|
||||
env.Define(lhs.Lit, rv)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
return NilValue, errors.New("Invalid operation")
|
||||
}
|
||||
|
||||
// invokeExpr evaluates one expression.
|
||||
func invokeExpr(expr ast.Expr, env *Env) (reflect.Value, error) {
|
||||
switch e := expr.(type) {
|
||||
case *ast.NumberExpr:
|
||||
if strings.Contains(e.Lit, ".") || strings.Contains(e.Lit, "e") {
|
||||
v, err := strconv.ParseFloat(e.Lit, 64)
|
||||
if err != nil {
|
||||
return NilValue, err
|
||||
}
|
||||
return reflect.ValueOf(float64(v)), nil
|
||||
}
|
||||
var i int64
|
||||
var err error
|
||||
if strings.HasPrefix(e.Lit, "0x") {
|
||||
i, err = strconv.ParseInt(e.Lit[2:], 16, 64)
|
||||
} else {
|
||||
i, err = strconv.ParseInt(e.Lit, 10, 64)
|
||||
}
|
||||
if err != nil {
|
||||
return NilValue, err
|
||||
}
|
||||
return reflect.ValueOf(i), nil
|
||||
case *ast.IdentExpr:
|
||||
return env.Get(e.Lit)
|
||||
case *ast.StringExpr:
|
||||
return reflect.ValueOf(e.Lit), nil
|
||||
case *ast.UnaryExpr:
|
||||
v, err := invokeExpr(e.Expr, env)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
switch e.Operator {
|
||||
case "-":
|
||||
if v.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(-v.Float()), nil
|
||||
}
|
||||
return reflect.ValueOf(-v.Int()), nil
|
||||
case "^":
|
||||
return reflect.ValueOf(^toInt64(v)), nil
|
||||
case "!":
|
||||
return reflect.ValueOf(!toBool(v)), nil
|
||||
default:
|
||||
return NilValue, errors.New("Unknown operator ''")
|
||||
}
|
||||
case *ast.ParenExpr:
|
||||
v, err := invokeExpr(e.SubExpr, env)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
case *ast.BinOpExpr:
|
||||
lhsV := NilValue
|
||||
rhsV := NilValue
|
||||
var err error
|
||||
|
||||
lhsV, err = invokeExpr(e.Lhs, env)
|
||||
if err != nil {
|
||||
return lhsV, err
|
||||
}
|
||||
if lhsV.Kind() == reflect.Interface {
|
||||
lhsV = lhsV.Elem()
|
||||
}
|
||||
if e.Rhs != nil {
|
||||
rhsV, err = invokeExpr(e.Rhs, env)
|
||||
if err != nil {
|
||||
return rhsV, err
|
||||
}
|
||||
if rhsV.Kind() == reflect.Interface {
|
||||
rhsV = rhsV.Elem()
|
||||
}
|
||||
}
|
||||
switch e.Operator {
|
||||
case "+":
|
||||
if lhsV.Kind() == reflect.String || rhsV.Kind() == reflect.String {
|
||||
return reflect.ValueOf(toString(lhsV) + toString(rhsV)), nil
|
||||
}
|
||||
if (lhsV.Kind() == reflect.Array || lhsV.Kind() == reflect.Slice) && (rhsV.Kind() != reflect.Array && rhsV.Kind() != reflect.Slice) {
|
||||
return reflect.Append(lhsV, rhsV), nil
|
||||
}
|
||||
if (lhsV.Kind() == reflect.Array || lhsV.Kind() == reflect.Slice) && (rhsV.Kind() == reflect.Array || rhsV.Kind() == reflect.Slice) {
|
||||
return reflect.AppendSlice(lhsV, rhsV), nil
|
||||
}
|
||||
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(toFloat64(lhsV) + toFloat64(rhsV)), nil
|
||||
}
|
||||
return reflect.ValueOf(toInt64(lhsV) + toInt64(rhsV)), nil
|
||||
case "-":
|
||||
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(toFloat64(lhsV) - toFloat64(rhsV)), nil
|
||||
}
|
||||
return reflect.ValueOf(toInt64(lhsV) - toInt64(rhsV)), nil
|
||||
case "*":
|
||||
if lhsV.Kind() == reflect.String && (rhsV.Kind() == reflect.Int || rhsV.Kind() == reflect.Int32 || rhsV.Kind() == reflect.Int64) {
|
||||
return reflect.ValueOf(strings.Repeat(toString(lhsV), int(toInt64(rhsV)))), nil
|
||||
}
|
||||
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(toFloat64(lhsV) * toFloat64(rhsV)), nil
|
||||
}
|
||||
return reflect.ValueOf(toInt64(lhsV) * toInt64(rhsV)), nil
|
||||
case "/":
|
||||
return reflect.ValueOf(toFloat64(lhsV) / toFloat64(rhsV)), nil
|
||||
case "%":
|
||||
return reflect.ValueOf(toInt64(lhsV) % toInt64(rhsV)), nil
|
||||
case "==":
|
||||
return reflect.ValueOf(equal(lhsV, rhsV)), nil
|
||||
case "!=":
|
||||
return reflect.ValueOf(equal(lhsV, rhsV) == false), nil
|
||||
case ">":
|
||||
return reflect.ValueOf(toFloat64(lhsV) > toFloat64(rhsV)), nil
|
||||
case ">=":
|
||||
return reflect.ValueOf(toFloat64(lhsV) >= toFloat64(rhsV)), nil
|
||||
case "<":
|
||||
return reflect.ValueOf(toFloat64(lhsV) < toFloat64(rhsV)), nil
|
||||
case "<=":
|
||||
return reflect.ValueOf(toFloat64(lhsV) <= toFloat64(rhsV)), nil
|
||||
case "|":
|
||||
return reflect.ValueOf(toInt64(lhsV) | toInt64(rhsV)), nil
|
||||
case "||":
|
||||
if toBool(lhsV) {
|
||||
return lhsV, nil
|
||||
}
|
||||
return rhsV, nil
|
||||
case "&":
|
||||
return reflect.ValueOf(toInt64(lhsV) & toInt64(rhsV)), nil
|
||||
case "&&":
|
||||
if toBool(lhsV) {
|
||||
return rhsV, nil
|
||||
}
|
||||
return lhsV, nil
|
||||
case "**":
|
||||
if lhsV.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(math.Pow(toFloat64(lhsV), toFloat64(rhsV))), nil
|
||||
}
|
||||
return reflect.ValueOf(int64(math.Pow(toFloat64(lhsV), toFloat64(rhsV)))), nil
|
||||
case ">>":
|
||||
return reflect.ValueOf(toInt64(lhsV) >> uint64(toInt64(rhsV))), nil
|
||||
case "<<":
|
||||
return reflect.ValueOf(toInt64(lhsV) << uint64(toInt64(rhsV))), nil
|
||||
default:
|
||||
return NilValue, errors.New("Unknown operator")
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
f, err := env.Get(e.Name)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
|
||||
args := []reflect.Value{}
|
||||
for i, expr := range e.SubExprs {
|
||||
arg, err := invokeExpr(expr, env)
|
||||
if err != nil {
|
||||
return arg, err
|
||||
}
|
||||
|
||||
if i < f.Type().NumIn() {
|
||||
if !f.Type().IsVariadic() {
|
||||
it := f.Type().In(i)
|
||||
if arg.Kind().String() == "unsafe.Pointer" {
|
||||
arg = reflect.New(it).Elem()
|
||||
}
|
||||
if arg.Kind() != it.Kind() && arg.IsValid() && arg.Type().ConvertibleTo(it) {
|
||||
arg = arg.Convert(it)
|
||||
} else if arg.Kind() == reflect.Func {
|
||||
if _, isFunc := arg.Interface().(Func); isFunc {
|
||||
rfunc := arg
|
||||
arg = reflect.MakeFunc(it, func(args []reflect.Value) []reflect.Value {
|
||||
for i := range args {
|
||||
args[i] = reflect.ValueOf(args[i])
|
||||
}
|
||||
return rfunc.Call(args)[:it.NumOut()]
|
||||
})
|
||||
}
|
||||
} else if !arg.IsValid() {
|
||||
arg = reflect.Zero(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !arg.IsValid() {
|
||||
arg = NilValue
|
||||
}
|
||||
|
||||
args = append(args, arg)
|
||||
}
|
||||
ret := NilValue
|
||||
fnc := func() {
|
||||
defer func() {
|
||||
if os.Getenv("KINAKO_DEBUG") == "" {
|
||||
if ex := recover(); ex != nil {
|
||||
if e, ok := ex.(error); ok {
|
||||
err = e
|
||||
} else {
|
||||
err = errors.New(fmt.Sprint(ex))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
if f.Kind() == reflect.Interface {
|
||||
f = f.Elem()
|
||||
}
|
||||
rets := f.Call(args)
|
||||
if f.Type().NumOut() == 1 {
|
||||
ret = rets[0]
|
||||
} else {
|
||||
var result []interface{}
|
||||
for _, r := range rets {
|
||||
result = append(result, r.Interface())
|
||||
}
|
||||
ret = reflect.ValueOf(result)
|
||||
}
|
||||
}
|
||||
fnc()
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
return ret, nil
|
||||
case *ast.TernaryOpExpr:
|
||||
rv, err := invokeExpr(e.Expr, env)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
if toBool(rv) {
|
||||
lhsV, err := invokeExpr(e.Lhs, env)
|
||||
if err != nil {
|
||||
return lhsV, err
|
||||
}
|
||||
return lhsV, nil
|
||||
}
|
||||
rhsV, err := invokeExpr(e.Rhs, env)
|
||||
if err != nil {
|
||||
return rhsV, err
|
||||
}
|
||||
return rhsV, nil
|
||||
default:
|
||||
return NilValue, errors.New("Unknown expression")
|
||||
}
|
||||
}
|
||||
54
vendor/github.com/mattn/kinako/vm/vm_test.go
generated
vendored
Normal file
54
vendor/github.com/mattn/kinako/vm/vm_test.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExecute(t *testing.T) {
|
||||
e := NewEnv()
|
||||
e.Define("foo", int64(1))
|
||||
e.Define("bar", int64(2))
|
||||
e.Define("baz", int64(3))
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
want interface{}
|
||||
}{
|
||||
{
|
||||
input: "foo+bar",
|
||||
want: int64(3),
|
||||
},
|
||||
{
|
||||
input: "foo-bar",
|
||||
want: int64(-1),
|
||||
},
|
||||
{
|
||||
input: "foo*bar",
|
||||
want: int64(2),
|
||||
},
|
||||
{
|
||||
input: "foo/bar",
|
||||
want: float64(0.5),
|
||||
},
|
||||
{
|
||||
input: "baz*(foo+bar)",
|
||||
want: int64(9),
|
||||
},
|
||||
{
|
||||
input: "baz > 2 ? foo : bar",
|
||||
want: int64(1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
r, err := e.Execute(tt.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := r.Interface()
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("want %v, but %v:", tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user