Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
language: go
|
||||||
|
script: go test -v -race ./...
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.5
|
||||||
|
- 1.6
|
||||||
|
- tip
|
||||||
22
Godeps/Godeps.json
generated
Normal file
22
Godeps/Godeps.json
generated
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/leonelquinteros/gotext",
|
||||||
|
"GoVersion": "go1.6",
|
||||||
|
"GodepVersion": "v74",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mattn/anko/ast",
|
||||||
|
"Rev": "a8c68fa2983e7dd5d3472992b1fbe2f7c44261f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mattn/anko/parser",
|
||||||
|
"Rev": "a8c68fa2983e7dd5d3472992b1fbe2f7c44261f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mattn/anko/vm",
|
||||||
|
"Rev": "a8c68fa2983e7dd5d3472992b1fbe2f7c44261f0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
||||||
103
README.md
103
README.md
@@ -1,24 +1,38 @@
|
|||||||
[](https://godoc.org/github.com/leonelquinteros/gotext)
|
[](https://godoc.org/github.com/leonelquinteros/gotext)
|
||||||
|
[](https://travis-ci.org/leonelquinteros/gotext)
|
||||||
|
[](https://goreportcard.com/report/github.com/leonelquinteros/gotext)
|
||||||
|
|
||||||
# Gotext
|
# Gotext
|
||||||
|
|
||||||
GNU gettext utilities for Go.
|
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
|
||||||
|
|
||||||
Version: [0.9.1](https://github.com/leonelquinteros/gotext/releases/tag/v0.9.1)
|
Version: [v1.1.1](https://github.com/leonelquinteros/gotext/releases/tag/v1.1.1)
|
||||||
|
|
||||||
|
|
||||||
#Features
|
# Features
|
||||||
|
|
||||||
- Implements GNU gettext support in native Go.
|
- 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.
|
- It works with UTF-8 encoding as it's the default for Go language.
|
||||||
- Unit tests available
|
- Unit tests available. See coverage: https://gocover.io/github.com/leonelquinteros/gotext
|
||||||
- Language codes are automatically simplified from the form "en_UK" to "en" if the formed isn't 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.
|
- 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
|
# Installation
|
||||||
|
|
||||||
@@ -31,42 +45,62 @@ go get github.com/leonelquinteros/gotext
|
|||||||
- No need for environment variables. Some naming conventions are applied but not needed.
|
- 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
|
# 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.
|
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, ...).
|
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.
|
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.
|
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).
|
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
|
||||||
/path/to/locales/en_US
|
/path/to/locales/en_US
|
||||||
/path/to/locales/en_US/default.po
|
/path/to/locales/en_US/LC_MESSAGES
|
||||||
/path/to/locales/en_US/extras.po
|
/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
|
||||||
/path/to/locales/en_UK/default.po
|
/path/to/locales/en_UK/LC_MESSAGES
|
||||||
/path/to/locales/en_UK/extras.po
|
/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
|
||||||
/path/to/locales/en_AU/default.po
|
/path/to/locales/en_AU/LC_MESSAGES
|
||||||
/path/to/locales/en_AU/extras.po
|
/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
|
||||||
/path/to/locales/es/default.po
|
/path/to/locales/es/default.po
|
||||||
/path/to/locales/es/extras.po
|
/path/to/locales/es/extras.po
|
||||||
@@ -84,9 +118,9 @@ And so on...
|
|||||||
|
|
||||||
# About translation function names
|
# 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 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:
|
The basic translation function is usually `_()` in the form:
|
||||||
|
|
||||||
```
|
```
|
||||||
_("Translate this")
|
_("Translate this")
|
||||||
@@ -109,8 +143,8 @@ func _(str string, vars ...interface{}) string {
|
|||||||
|
|
||||||
This is valid and can be used within a package.
|
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.
|
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.
|
This is a normal Go compiler behavior.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -185,7 +219,7 @@ func main() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
This is also helpful for using inside templates (from the "text/template" package), where you can pass the Locale object to the template.
|
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" }}
|
{{ .Loc.Get "Translate this" }}
|
||||||
@@ -225,6 +259,10 @@ msgstr "This one sets the var: %s"
|
|||||||
## Use plural forms of translations
|
## Use plural forms of translations
|
||||||
|
|
||||||
PO format supports defining one or more plural forms for the same translation.
|
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 [Anko](https://github.com/mattn/anko)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/leonelquinteros/gotext"
|
import "github.com/leonelquinteros/gotext"
|
||||||
@@ -232,6 +270,12 @@ import "github.com/leonelquinteros/gotext"
|
|||||||
func main() {
|
func main() {
|
||||||
// Set PO content
|
// Set PO content
|
||||||
str := `
|
str := `
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
# Header below
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
msgid "Translate this"
|
msgid "Translate this"
|
||||||
msgstr "Translated text"
|
msgstr "Translated text"
|
||||||
|
|
||||||
@@ -242,15 +286,14 @@ msgid "One with var: %s"
|
|||||||
msgid_plural "Several with vars: %s"
|
msgid_plural "Several with vars: %s"
|
||||||
msgstr[0] "This one is the singular: %s"
|
msgstr[0] "This one is the singular: %s"
|
||||||
msgstr[1] "This one is the plural: %s"
|
msgstr[1] "This one is the plural: %s"
|
||||||
msgstr[2] "And this is the second plural form: %s"
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// Create Po object
|
// Create Po object
|
||||||
po := new(Po)
|
po := new(Po)
|
||||||
po.Parse(str)
|
po.Parse(str)
|
||||||
|
|
||||||
println(po.GetN("One with var: %s", "Several with vars: %s", 2, v))
|
println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
|
||||||
// "And this is the second plural form: Variable"
|
// "This one is the plural: Variable"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
39
gotext.go
39
gotext.go
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Package gotext implements GNU gettext utilities.
|
Package gotext implements GNU gettext utilities.
|
||||||
|
|
||||||
Version 0.9.0 (stable)
|
|
||||||
|
|
||||||
For quick/simple translations you can use the package level functions directly.
|
For quick/simple translations you can use the package level functions directly.
|
||||||
|
|
||||||
import "github.com/leonelquinteros/gotext"
|
import "github.com/leonelquinteros/gotext"
|
||||||
@@ -30,7 +28,7 @@ var (
|
|||||||
language = "en_US"
|
language = "en_US"
|
||||||
|
|
||||||
// Path to library directory where all locale directories and translation files are.
|
// Path to library directory where all locale directories and translation files are.
|
||||||
library = "/tmp"
|
library = "/usr/local/share/locale"
|
||||||
|
|
||||||
// Storage for package level methods
|
// Storage for package level methods
|
||||||
storage *Locale
|
storage *Locale
|
||||||
@@ -102,8 +100,7 @@ func Get(str string, vars ...interface{}) string {
|
|||||||
return GetD(domain, str, vars...)
|
return GetD(domain, str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetN retrieves the (N)th plural form translation for the given string in the "default" domain.
|
// GetN retrieves the (N)th plural form of 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.
|
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// 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 {
|
func GetN(str, plural string, n int, vars ...interface{}) string {
|
||||||
return GetND("default", str, plural, n, vars...)
|
return GetND("default", str, plural, n, vars...)
|
||||||
@@ -112,10 +109,10 @@ func GetN(str, plural string, n int, vars ...interface{}) string {
|
|||||||
// GetD returns the corresponding translation in the given domain for a given string.
|
// 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.
|
// 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 {
|
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.
|
// 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 {
|
func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||||
// Try to load default package Locale storage
|
// Try to load default package Locale storage
|
||||||
@@ -124,3 +121,31 @@ func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
|||||||
// Return translation
|
// Return translation
|
||||||
return storage.GetND(dom, str, plural, n, vars...)
|
return storage.GetND(dom, str, plural, n, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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(domain, 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("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 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
|
||||||
|
return storage.GetNDC(dom, str, plural, n, ctx, vars...)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,9 +6,42 @@ import (
|
|||||||
"testing"
|
"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) {
|
func TestPackageFunctions(t *testing.T) {
|
||||||
// Set PO content
|
// 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"
|
msgid "My text"
|
||||||
msgstr "Translated text"
|
msgstr "Translated text"
|
||||||
|
|
||||||
@@ -22,17 +55,41 @@ msgstr[0] "This one is the singular: %s"
|
|||||||
msgstr[1] "This one is the plural: %s"
|
msgstr[1] "This one is the plural: %s"
|
||||||
msgstr[2] "And this is the second plural form: %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 on default location
|
// Create Locales directory on default location
|
||||||
dirname := path.Clean(library + string(os.PathSeparator) + "en_US")
|
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en_US")
|
||||||
err := os.MkdirAll(dirname, os.ModePerm)
|
err := os.MkdirAll(dirname, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write PO content to default domain file
|
// Write PO content to default domain file
|
||||||
filename := path.Clean(dirname + string(os.PathSeparator) + domain + ".po")
|
filename := path.Clean(dirname + string(os.PathSeparator) + "default.po")
|
||||||
|
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
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())
|
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set package configuration
|
||||||
|
Configure("/tmp", "en_US", "default")
|
||||||
|
|
||||||
// Test translations
|
// Test translations
|
||||||
tr := Get("My text")
|
tr := Get("My text")
|
||||||
if tr != "Translated text" {
|
if tr != "Translated text" {
|
||||||
@@ -59,8 +119,25 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
|
|
||||||
// Test plural
|
// Test plural
|
||||||
tr = GetN("One with var: %s", "Several with vars: %s", 2, v)
|
tr = GetN("One with var: %s", "Several with vars: %s", 2, v)
|
||||||
if tr != "And this is the second plural form: Variable" {
|
if tr != "This one is the plural: Variable" {
|
||||||
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,14 +186,14 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
|
|
||||||
// Test translations
|
// Test translations
|
||||||
go func(done chan bool) {
|
go func(done chan bool) {
|
||||||
println(Get("My text"))
|
Get("My text")
|
||||||
done <- true
|
done <- true
|
||||||
}(c1)
|
}(c1)
|
||||||
|
|
||||||
go func(done chan bool) {
|
go func(done chan bool) {
|
||||||
println(Get("My text"))
|
Get("My text")
|
||||||
done <- true
|
done <- true
|
||||||
}(c2)
|
}(c2)
|
||||||
|
|
||||||
println(Get("My text"))
|
Get("My text")
|
||||||
}
|
}
|
||||||
|
|||||||
88
locale.go
88
locale.go
@@ -20,13 +20,13 @@ Example:
|
|||||||
// Create Locale with library path and language code
|
// Create Locale with library path and language code
|
||||||
l := gotext.NewLocale("/path/to/i18n/dir", "en_US")
|
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")
|
l.AddDomain("default")
|
||||||
|
|
||||||
// Translate text from default domain
|
// Translate text from default domain
|
||||||
println(l.Get("Translate this"))
|
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")
|
l.AddDomain("extras")
|
||||||
|
|
||||||
// Translate text from domain
|
// Translate text from domain
|
||||||
@@ -58,23 +58,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.
|
// AddDomain creates a new domain for a given locale object and initializes the Po object.
|
||||||
// If the domain exists, it gets reloaded.
|
// If the domain exists, it gets reloaded.
|
||||||
func (l *Locale) AddDomain(dom string) {
|
func (l *Locale) AddDomain(dom string) {
|
||||||
po := new(Po)
|
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.
|
// Parse file.
|
||||||
po.ParseFile(filename)
|
po.ParseFile(l.findPO(dom))
|
||||||
|
|
||||||
// Save new domain
|
// Save new domain
|
||||||
l.Lock()
|
l.Lock()
|
||||||
@@ -92,21 +107,19 @@ func (l *Locale) Get(str string, vars ...interface{}) string {
|
|||||||
return l.GetD("default", str, vars...)
|
return l.GetD("default", str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetN retrieves the (N)th plural form translation for the given string in the "default" domain.
|
// GetN retrieves the (N)th plural form of 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.
|
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// 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 {
|
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||||
return l.GetND("default", str, plural, n, vars...)
|
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.
|
// 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 {
|
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.
|
// GetND retrieves the (N)th plural form of translation in the given domain for the given string.
|
||||||
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
|
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// 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 {
|
func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||||
// Sync read
|
// Sync read
|
||||||
@@ -124,3 +137,40 @@ func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) stri
|
|||||||
// Return the same we received by default
|
// Return the same we received by default
|
||||||
return fmt.Sprintf(plural, vars...)
|
return fmt.Sprintf(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 fmt.Sprintf(plural, vars...)
|
||||||
|
}
|
||||||
|
|||||||
193
locale_test.go
193
locale_test.go
@@ -8,7 +8,17 @@ import (
|
|||||||
|
|
||||||
func TestLocale(t *testing.T) {
|
func TestLocale(t *testing.T) {
|
||||||
// Set PO content
|
// 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"
|
msgid "My text"
|
||||||
msgstr "Translated text"
|
msgstr "Translated text"
|
||||||
|
|
||||||
@@ -22,17 +32,41 @@ msgstr[0] "This one is the singular: %s"
|
|||||||
msgstr[1] "This one is the plural: %s"
|
msgstr[1] "This one is the plural: %s"
|
||||||
msgstr[2] "And this is the second plural form: %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
|
// 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)
|
err := os.MkdirAll(dirname, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write PO content to file
|
// 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)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,6 +82,9 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
// Create Locale with full language code
|
// Create Locale with full language code
|
||||||
l := NewLocale("/tmp", "en_US")
|
l := NewLocale("/tmp", "en_US")
|
||||||
|
|
||||||
|
// Force nil domain storage
|
||||||
|
l.domains = nil
|
||||||
|
|
||||||
// Add domain
|
// Add domain
|
||||||
l.AddDomain("my_domain")
|
l.AddDomain("my_domain")
|
||||||
|
|
||||||
@@ -64,9 +101,145 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test plural
|
// Test plural
|
||||||
tr = l.GetND("my_domain", "One with var: %s", "Several with vars: %s", 2, v)
|
tr = l.GetND("my_domain", "One with var: %s", "Several with vars: %s", 7, v)
|
||||||
if tr != "And this is the second plural form: Variable" {
|
if tr != "This one is the plural: Variable" {
|
||||||
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,14 +262,14 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
`
|
`
|
||||||
|
|
||||||
// Create Locales directory with simplified language code
|
// 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)
|
err := os.MkdirAll(dirname, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write PO content to file
|
// 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)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,12 +297,12 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
|
|
||||||
// Get translations in goroutine
|
// Get translations in goroutine
|
||||||
go func(l *Locale, done chan bool) {
|
go func(l *Locale, done chan bool) {
|
||||||
println(l.GetD("race", "My text"))
|
l.GetD("race", "My text")
|
||||||
done <- true
|
done <- true
|
||||||
}(l, rc)
|
}(l, rc)
|
||||||
|
|
||||||
// Get translations at top level
|
// Get translations at top level
|
||||||
println(l.GetD("race", "My text"))
|
l.GetD("race", "My text")
|
||||||
|
|
||||||
// Wait for goroutines to finish
|
// Wait for goroutines to finish
|
||||||
<-ac
|
<-ac
|
||||||
|
|||||||
347
po.go
347
po.go
@@ -1,8 +1,11 @@
|
|||||||
package gotext
|
package gotext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/mattn/anko/vm"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,7 +14,7 @@ import (
|
|||||||
|
|
||||||
type translation struct {
|
type translation struct {
|
||||||
id string
|
id string
|
||||||
pluralId string
|
pluralID string
|
||||||
trs map[int]string
|
trs map[int]string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,13 +42,13 @@ func (t *translation) getN(n int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return unstranlated plural by default
|
// Return unstranlated plural by default
|
||||||
return t.pluralId
|
return t.pluralID
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Po parses the content of any PO file and provides all the translation functions needed.
|
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.
|
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 write locking.
|
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -64,11 +67,29 @@ Example:
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
type Po struct {
|
type Po struct {
|
||||||
|
// Headers
|
||||||
|
RawHeaders string
|
||||||
|
|
||||||
|
// Language header
|
||||||
|
Language string
|
||||||
|
|
||||||
|
// Plural-Forms header
|
||||||
|
PluralForms string
|
||||||
|
|
||||||
|
// Parsed Plural-Forms header values
|
||||||
|
nplurals int
|
||||||
|
plural string
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
translations map[string]*translation
|
translations map[string]*translation
|
||||||
|
contexts map[string]map[string]*translation
|
||||||
|
|
||||||
// Sync Mutex
|
// Sync Mutex
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
|
// Parsing buffers
|
||||||
|
trBuffer *translation
|
||||||
|
ctxBuffer string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
||||||
@@ -95,94 +116,263 @@ func (po *Po) ParseFile(f string) {
|
|||||||
|
|
||||||
// Parse loads the translations specified in the provided string (str)
|
// Parse loads the translations specified in the provided string (str)
|
||||||
func (po *Po) Parse(str string) {
|
func (po *Po) Parse(str string) {
|
||||||
|
// Lock while parsing
|
||||||
|
po.Lock()
|
||||||
|
defer po.Unlock()
|
||||||
|
|
||||||
|
// Init storage
|
||||||
if po.translations == nil {
|
if po.translations == nil {
|
||||||
po.Lock()
|
|
||||||
po.translations = make(map[string]*translation)
|
po.translations = make(map[string]*translation)
|
||||||
po.Unlock()
|
po.contexts = make(map[string]map[string]*translation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get lines
|
||||||
lines := strings.Split(str, "\n")
|
lines := strings.Split(str, "\n")
|
||||||
|
|
||||||
tr := newTranslation()
|
// Init buffer
|
||||||
|
po.trBuffer = newTranslation()
|
||||||
|
po.ctxBuffer = ""
|
||||||
|
|
||||||
for _, l := range lines {
|
for _, l := range lines {
|
||||||
// Trim spaces
|
// Trim spaces
|
||||||
l = strings.TrimSpace(l)
|
l = strings.TrimSpace(l)
|
||||||
|
|
||||||
// Skip empty lines
|
// Skip invalid lines
|
||||||
if l == "" {
|
if !po.isValidLine(l) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip invalid lines
|
// Buffer context and continue
|
||||||
if !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") {
|
if strings.HasPrefix(l, "msgctxt") {
|
||||||
|
po.parseContext(l)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffer msgid and continue
|
// Buffer msgid and continue
|
||||||
if strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") {
|
if strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") {
|
||||||
// Save current translation buffer.
|
po.parseID(l)
|
||||||
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
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for plural form
|
// Check for plural form
|
||||||
if strings.HasPrefix(l, "msgid_plural") {
|
if strings.HasPrefix(l, "msgid_plural") {
|
||||||
tr.pluralId, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
po.parsePluralID(l)
|
||||||
|
|
||||||
// Loop
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save translation
|
// Save translation
|
||||||
if strings.HasPrefix(l, "msgstr") {
|
if strings.HasPrefix(l, "msgstr") {
|
||||||
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
po.parseMessage(l)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Check for indexed translation forms
|
// Multi line strings and headers
|
||||||
if strings.HasPrefix(l, "[") {
|
if strings.HasPrefix(l, "\"") && strings.HasSuffix(l, "\"") {
|
||||||
in := strings.Index(l, "]")
|
po.parseString(l)
|
||||||
if in == -1 {
|
continue
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save last translation buffer.
|
// Save last translation buffer.
|
||||||
if tr.id != "" {
|
po.saveBuffer()
|
||||||
po.Lock()
|
|
||||||
po.translations[tr.id] = tr
|
// Parse headers
|
||||||
po.Unlock()
|
po.parseHeaders()
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveBuffer takes the context and translation buffers
|
||||||
|
// and saves it on the translations collection
|
||||||
|
func (po *Po) saveBuffer() {
|
||||||
|
// If we have something to save...
|
||||||
|
if po.trBuffer.id != "" {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush buffer
|
||||||
|
po.trBuffer = newTranslation()
|
||||||
|
po.ctxBuffer = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// Check for multiline from previously set msgid
|
||||||
|
if po.trBuffer.id != "" {
|
||||||
|
// Append to last translation found
|
||||||
|
uq, _ := strconv.Unquote(l)
|
||||||
|
po.trBuffer.trs[len(po.trBuffer.trs)-1] += uq
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise is a header
|
||||||
|
h, err := strconv.Unquote(strings.TrimSpace(l))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
po.RawHeaders += h
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidLine checks for line prefixes to detect valid syntax.
|
||||||
|
func (po *Po) isValidLine(l string) bool {
|
||||||
|
// Skip empty lines
|
||||||
|
if l == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check prefix
|
||||||
|
if !strings.HasPrefix(l, "\"") && !strings.HasPrefix(l, "msgctxt") && !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHeaders retrieves data from previously parsed headers
|
||||||
|
func (po *Po) parseHeaders() {
|
||||||
|
// Make sure we end with 2 carriage returns.
|
||||||
|
po.RawHeaders += "\n\n"
|
||||||
|
|
||||||
|
// Read
|
||||||
|
reader := bufio.NewReader(strings.NewReader(po.RawHeaders))
|
||||||
|
tp := textproto.NewReader(reader)
|
||||||
|
|
||||||
|
mimeHeader, err := tp.ReadMIMEHeader()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get/save needed headers
|
||||||
|
po.Language = mimeHeader.Get("Language")
|
||||||
|
po.PluralForms = mimeHeader.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.
|
// Get retrieves the corresponding translation for the given string.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func (po *Po) Get(str string, vars ...interface{}) string {
|
func (po *Po) Get(str string, vars ...interface{}) string {
|
||||||
@@ -200,8 +390,7 @@ func (po *Po) Get(str string, vars ...interface{}) string {
|
|||||||
return fmt.Sprintf(str, vars...)
|
return fmt.Sprintf(str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetN retrieves the (N)th plural form translation for the given string.
|
// GetN retrieves the (N)th plural form of translation for the given string.
|
||||||
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
|
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// 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 {
|
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||||
// Sync read
|
// Sync read
|
||||||
@@ -210,7 +399,49 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
|||||||
|
|
||||||
if po.translations != nil {
|
if po.translations != nil {
|
||||||
if _, ok := po.translations[str]; ok {
|
if _, ok := po.translations[str]; ok {
|
||||||
return fmt.Sprintf(po.translations[str].getN(n), vars...)
|
return fmt.Sprintf(po.translations[str].getN(po.pluralForm(n)), vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the plural string we received by default
|
||||||
|
return fmt.Sprintf(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 fmt.Sprintf(po.contexts[ctx][str].get(), vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the string we received by default
|
||||||
|
return fmt.Sprintf(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 fmt.Sprintf(po.contexts[ctx][str].getN(po.pluralForm(n)), vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
330
po_test.go
330
po_test.go
@@ -8,7 +8,17 @@ import (
|
|||||||
|
|
||||||
func TestPo(t *testing.T) {
|
func TestPo(t *testing.T) {
|
||||||
// Set PO content
|
// 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"
|
msgid "My text"
|
||||||
msgstr "Translated text"
|
msgstr "Translated text"
|
||||||
|
|
||||||
@@ -16,13 +26,42 @@ msgstr "Translated text"
|
|||||||
msgid "Another string"
|
msgid "Another string"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#Multi-line string
|
||||||
|
msgid "Multi-line"
|
||||||
|
msgstr "Multi "
|
||||||
|
"line"
|
||||||
|
|
||||||
msgid "One with var: %s"
|
msgid "One with var: %s"
|
||||||
msgid_plural "Several with vars: %s"
|
msgid_plural "Several with vars: %s"
|
||||||
msgstr[0] "This one is the singular: %s"
|
msgstr[0] "This one is the singular: %s"
|
||||||
msgstr[1] "This one is the plural: %s"
|
msgstr[1] "This one is the plural: %s"
|
||||||
msgstr[2] "And this is the second plural form: %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"
|
||||||
`
|
`
|
||||||
|
|
||||||
// Write PO content to file
|
// Write PO content to file
|
||||||
filename := path.Clean(os.TempDir() + string(os.PathSeparator) + "default.po")
|
filename := path.Clean(os.TempDir() + string(os.PathSeparator) + "default.po")
|
||||||
|
|
||||||
@@ -37,8 +76,13 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse po file
|
// Create po object
|
||||||
po := new(Po)
|
po := new(Po)
|
||||||
|
|
||||||
|
// Try to parse a directory
|
||||||
|
po.ParseFile(path.Clean(os.TempDir()))
|
||||||
|
|
||||||
|
// Parse file
|
||||||
po.ParseFile(filename)
|
po.ParseFile(filename)
|
||||||
|
|
||||||
// Test translations
|
// Test translations
|
||||||
@@ -53,10 +97,284 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
|
t.Errorf("Expected 'This one is the singular: Variable' 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
|
// Test plural
|
||||||
tr = po.GetN("One with var: %s", "Several with vars: %s", 2, v)
|
tr = po.GetN("One with var: %s", "Several with vars: %s", 2, v)
|
||||||
if tr != "And this is the second plural form: Variable" {
|
if tr != "This one is the plural: Variable" {
|
||||||
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
|
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 syntax error parsed translations
|
||||||
|
tr = po.Get("This one has invalid syntax translations")
|
||||||
|
if tr != "" {
|
||||||
|
t.Errorf("Expected '' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = po.GetN("This one has invalid syntax translations", "This are tests", 4)
|
||||||
|
if tr != "Plural index" {
|
||||||
|
t.Errorf("Expected 'Plural index' 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 last translation
|
||||||
|
tr = po.Get("More")
|
||||||
|
if tr != "More translation" {
|
||||||
|
t.Errorf("Expected 'More translation' 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 TestPluralFormsSingle(t *testing.T) {
|
||||||
|
// Single form
|
||||||
|
str := `
|
||||||
|
"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 := `
|
||||||
|
"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 := `
|
||||||
|
"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 := `
|
||||||
|
"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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,12 +411,12 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
|
|
||||||
// Read some translation on a goroutine
|
// Read some translation on a goroutine
|
||||||
go func(po *Po, done chan bool) {
|
go func(po *Po, done chan bool) {
|
||||||
println(po.Get("My text"))
|
po.Get("My text")
|
||||||
done <- true
|
done <- true
|
||||||
}(po, rc)
|
}(po, rc)
|
||||||
|
|
||||||
// Read something at top level
|
// Read something at top level
|
||||||
println(po.Get("My text"))
|
po.Get("My text")
|
||||||
|
|
||||||
// Wait for goroutines to finish
|
// Wait for goroutines to finish
|
||||||
<-pc
|
<-pc
|
||||||
|
|||||||
2
vendor/github.com/mattn/anko/ast/doc.go
generated
vendored
Normal file
2
vendor/github.com/mattn/anko/ast/doc.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Package ast implements abstruct-syntax-tree for anko.
|
||||||
|
package ast
|
||||||
201
vendor/github.com/mattn/anko/ast/expr.go
generated
vendored
Normal file
201
vendor/github.com/mattn/anko/ast/expr.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
// Expr provides all of interfaces for expression.
|
||||||
|
type Expr interface {
|
||||||
|
Pos
|
||||||
|
expr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExprImpl provide commonly implementations for Expr.
|
||||||
|
type ExprImpl struct {
|
||||||
|
PosImpl // ExprImpl provide Pos() function.
|
||||||
|
}
|
||||||
|
|
||||||
|
// expr provide restraint interface.
|
||||||
|
func (x *ExprImpl) expr() {}
|
||||||
|
|
||||||
|
// NumberExpr provide Number expression.
|
||||||
|
type NumberExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Lit string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringExpr provide String expression.
|
||||||
|
type StringExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Lit string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayExpr provide Array expression.
|
||||||
|
type ArrayExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Exprs []Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// PairExpr provide one of Map key/value pair.
|
||||||
|
type PairExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Key string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapExpr provide Map expression.
|
||||||
|
type MapExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
MapExpr map[string]Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdentExpr provide identity expression.
|
||||||
|
type IdentExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Lit string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnaryExpr provide unary minus expression. ex: -1, ^1, ~1.
|
||||||
|
type UnaryExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Operator string
|
||||||
|
Expr Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddrExpr provide referencing address expression.
|
||||||
|
type AddrExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Expr Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// DerefExpr provide dereferencing address expression.
|
||||||
|
type DerefExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Expr 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
|
||||||
|
}
|
||||||
|
|
||||||
|
type TernaryOpExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Expr Expr
|
||||||
|
Lhs Expr
|
||||||
|
Rhs Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallExpr provide calling expression.
|
||||||
|
type CallExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Func interface{}
|
||||||
|
Name string
|
||||||
|
SubExprs []Expr
|
||||||
|
VarArg bool
|
||||||
|
Go bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnonCallExpr provide anonymous calling expression. ex: func(){}().
|
||||||
|
type AnonCallExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Expr Expr
|
||||||
|
SubExprs []Expr
|
||||||
|
VarArg bool
|
||||||
|
Go bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemberExpr provide expression to refer menber.
|
||||||
|
type MemberExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Expr Expr
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ItemExpr provide expression to refer Map/Array item.
|
||||||
|
type ItemExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Value Expr
|
||||||
|
Index Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceExpr provide expression to refer slice of Array.
|
||||||
|
type SliceExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Value Expr
|
||||||
|
Begin Expr
|
||||||
|
End Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncExpr provide function expression.
|
||||||
|
type FuncExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Name string
|
||||||
|
Stmts []Stmt
|
||||||
|
Args []string
|
||||||
|
VarArg bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// LetExpr provide expression to let variable.
|
||||||
|
type LetExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Lhs Expr
|
||||||
|
Rhs Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// LetsExpr provide multiple expression of let.
|
||||||
|
type LetsExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Lhss []Expr
|
||||||
|
Operator string
|
||||||
|
Rhss []Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssocExpr provide expression to assoc operation.
|
||||||
|
type AssocExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Lhs Expr
|
||||||
|
Operator string
|
||||||
|
Rhs Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExpr provide expression to make new instance.
|
||||||
|
type NewExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Name string
|
||||||
|
SubExprs []Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstExpr provide expression for constant variable.
|
||||||
|
type ConstExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChanExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Lhs Expr
|
||||||
|
Rhs Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Type struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MakeChanExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Type string
|
||||||
|
SizeExpr Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type MakeArrayExpr struct {
|
||||||
|
ExprImpl
|
||||||
|
Type string
|
||||||
|
LenExpr Expr
|
||||||
|
CapExpr Expr
|
||||||
|
}
|
||||||
28
vendor/github.com/mattn/anko/ast/pos.go
generated
vendored
Normal file
28
vendor/github.com/mattn/anko/ast/pos.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
// Position provides interface to store code locations.
|
||||||
|
type Position struct {
|
||||||
|
Line int
|
||||||
|
Column int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pos interface provies two functions to get/set the position for expression or statement.
|
||||||
|
type Pos interface {
|
||||||
|
Position() Position
|
||||||
|
SetPosition(Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PosImpl provies commonly implementations for Pos.
|
||||||
|
type PosImpl struct {
|
||||||
|
pos Position
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position return the position of the expression or statement.
|
||||||
|
func (x *PosImpl) Position() Position {
|
||||||
|
return x.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPosition is a function to specify position of the expression or statement.
|
||||||
|
func (x *PosImpl) SetPosition(pos Position) {
|
||||||
|
x.pos = pos
|
||||||
|
}
|
||||||
127
vendor/github.com/mattn/anko/ast/stmt.go
generated
vendored
Normal file
127
vendor/github.com/mattn/anko/ast/stmt.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
// Stmt provides all of interfaces for statement.
|
||||||
|
type Stmt interface {
|
||||||
|
Pos
|
||||||
|
stmt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StmtImpl provide commonly implementations for Stmt..
|
||||||
|
type StmtImpl struct {
|
||||||
|
PosImpl // StmtImpl provide Pos() function.
|
||||||
|
}
|
||||||
|
|
||||||
|
// stmt provide restraint interface.
|
||||||
|
func (x *StmtImpl) stmt() {}
|
||||||
|
|
||||||
|
// ExprStmt provide expression statement.
|
||||||
|
type ExprStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Expr Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfStmt provide "if/else" statement.
|
||||||
|
type IfStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
If Expr
|
||||||
|
Then []Stmt
|
||||||
|
ElseIf []Stmt // This is array of IfStmt
|
||||||
|
Else []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryStmt provide "try/catch/finally" statement.
|
||||||
|
type TryStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Try []Stmt
|
||||||
|
Var string
|
||||||
|
Catch []Stmt
|
||||||
|
Finally []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForStmt provide "for in" expression statement.
|
||||||
|
type ForStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Var string
|
||||||
|
Value Expr
|
||||||
|
Stmts []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// CForStmt provide C-style "for (;;)" expression statement.
|
||||||
|
type CForStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Expr1 Expr
|
||||||
|
Expr2 Expr
|
||||||
|
Expr3 Expr
|
||||||
|
Stmts []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoopStmt provide "for expr" expression statement.
|
||||||
|
type LoopStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Expr Expr
|
||||||
|
Stmts []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// BreakStmt provide "break" expression statement.
|
||||||
|
type BreakStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContinueStmt provide "continue" expression statement.
|
||||||
|
type ContinueStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForStmt provide "return" expression statement.
|
||||||
|
type ReturnStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Exprs []Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThrowStmt provide "throw" expression statement.
|
||||||
|
type ThrowStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Expr Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModuleStmt provide "module" expression statement.
|
||||||
|
type ModuleStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Name string
|
||||||
|
Stmts []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarStmt provide statement to let variables in current scope.
|
||||||
|
type VarStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Names []string
|
||||||
|
Exprs []Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchStmt provide switch statement.
|
||||||
|
type SwitchStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Expr Expr
|
||||||
|
Cases []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaseStmt provide switch/case statement.
|
||||||
|
type CaseStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Expr Expr
|
||||||
|
Stmts []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultStmt provide switch/default statement.
|
||||||
|
type DefaultStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Stmts []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// LetsStmt provide multiple statement of let.
|
||||||
|
type LetsStmt struct {
|
||||||
|
StmtImpl
|
||||||
|
Lhss []Expr
|
||||||
|
Operator string
|
||||||
|
Rhss []Expr
|
||||||
|
}
|
||||||
7
vendor/github.com/mattn/anko/ast/token.go
generated
vendored
Normal file
7
vendor/github.com/mattn/anko/ast/token.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
PosImpl // StmtImpl provide Pos() function.
|
||||||
|
Tok int
|
||||||
|
Lit string
|
||||||
|
}
|
||||||
4
vendor/github.com/mattn/anko/parser/Makefile
generated
vendored
Normal file
4
vendor/github.com/mattn/anko/parser/Makefile
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
all : parser.go
|
||||||
|
|
||||||
|
parser.go : parser.go.y
|
||||||
|
go tool yacc -o $@ parser.go.y
|
||||||
530
vendor/github.com/mattn/anko/parser/lexer.go
generated
vendored
Normal file
530
vendor/github.com/mattn/anko/parser/lexer.go
generated
vendored
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
// Package parser implements parser for anko.
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mattn/anko/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
|
||||||
|
Pos ast.Position
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// opName is correction of operation names.
|
||||||
|
var opName = map[string]int{
|
||||||
|
"func": FUNC,
|
||||||
|
"return": RETURN,
|
||||||
|
"var": VAR,
|
||||||
|
"throw": THROW,
|
||||||
|
"if": IF,
|
||||||
|
"for": FOR,
|
||||||
|
"break": BREAK,
|
||||||
|
"continue": CONTINUE,
|
||||||
|
"in": IN,
|
||||||
|
"else": ELSE,
|
||||||
|
"new": NEW,
|
||||||
|
"true": TRUE,
|
||||||
|
"false": FALSE,
|
||||||
|
"nil": NIL,
|
||||||
|
"module": MODULE,
|
||||||
|
"try": TRY,
|
||||||
|
"catch": CATCH,
|
||||||
|
"finally": FINALLY,
|
||||||
|
"switch": SWITCH,
|
||||||
|
"case": CASE,
|
||||||
|
"default": DEFAULT,
|
||||||
|
"go": GO,
|
||||||
|
"chan": CHAN,
|
||||||
|
"make": MAKE,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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):
|
||||||
|
lit, err = s.scanIdentifier()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if name, ok := opName[lit]; ok {
|
||||||
|
tok = name
|
||||||
|
} else {
|
||||||
|
tok = IDENT
|
||||||
|
}
|
||||||
|
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 '+':
|
||||||
|
s.next()
|
||||||
|
switch s.peek() {
|
||||||
|
case '+':
|
||||||
|
tok = PLUSPLUS
|
||||||
|
lit = "++"
|
||||||
|
case '=':
|
||||||
|
tok = PLUSEQ
|
||||||
|
lit = "+="
|
||||||
|
default:
|
||||||
|
s.back()
|
||||||
|
tok = int(ch)
|
||||||
|
lit = string(ch)
|
||||||
|
}
|
||||||
|
case '-':
|
||||||
|
s.next()
|
||||||
|
switch s.peek() {
|
||||||
|
case '-':
|
||||||
|
tok = MINUSMINUS
|
||||||
|
lit = "--"
|
||||||
|
case '=':
|
||||||
|
tok = MINUSEQ
|
||||||
|
lit = "-="
|
||||||
|
default:
|
||||||
|
s.back()
|
||||||
|
tok = int(ch)
|
||||||
|
lit = string(ch)
|
||||||
|
}
|
||||||
|
case '*':
|
||||||
|
s.next()
|
||||||
|
switch s.peek() {
|
||||||
|
case '*':
|
||||||
|
tok = POW
|
||||||
|
lit = "**"
|
||||||
|
case '=':
|
||||||
|
tok = MULEQ
|
||||||
|
lit = "*="
|
||||||
|
default:
|
||||||
|
s.back()
|
||||||
|
tok = int(ch)
|
||||||
|
lit = string(ch)
|
||||||
|
}
|
||||||
|
case '/':
|
||||||
|
s.next()
|
||||||
|
switch s.peek() {
|
||||||
|
case '=':
|
||||||
|
tok = DIVEQ
|
||||||
|
lit = "/="
|
||||||
|
default:
|
||||||
|
s.back()
|
||||||
|
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 = OPCHAN
|
||||||
|
lit = "<-"
|
||||||
|
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 = "||"
|
||||||
|
case '=':
|
||||||
|
tok = OREQ
|
||||||
|
lit = "|="
|
||||||
|
default:
|
||||||
|
s.back()
|
||||||
|
tok = int(ch)
|
||||||
|
lit = string(ch)
|
||||||
|
}
|
||||||
|
case '&':
|
||||||
|
s.next()
|
||||||
|
switch s.peek() {
|
||||||
|
case '&':
|
||||||
|
tok = ANDAND
|
||||||
|
lit = "&&"
|
||||||
|
case '=':
|
||||||
|
tok = ANDEQ
|
||||||
|
lit = "&="
|
||||||
|
default:
|
||||||
|
s.back()
|
||||||
|
tok = int(ch)
|
||||||
|
lit = string(ch)
|
||||||
|
}
|
||||||
|
case '.':
|
||||||
|
s.next()
|
||||||
|
if s.peek() == '.' {
|
||||||
|
s.next()
|
||||||
|
if s.peek() == '.' {
|
||||||
|
tok = VARARG
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(`syntax error "%s"`, "..")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.back()
|
||||||
|
tok = int(ch)
|
||||||
|
lit = string(ch)
|
||||||
|
}
|
||||||
|
case '(', ')', ':', ';', '%', '?', '{', '}', ',', '[', ']', '^', '\n':
|
||||||
|
s.next()
|
||||||
|
if ch == '[' && s.peek() == ']' {
|
||||||
|
s.next()
|
||||||
|
if isLetter(s.peek()) {
|
||||||
|
s.back()
|
||||||
|
tok = ARRAYLIT
|
||||||
|
lit = "[]"
|
||||||
|
} else {
|
||||||
|
s.back()
|
||||||
|
s.back()
|
||||||
|
tok = int(ch)
|
||||||
|
lit = string(ch)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.back()
|
||||||
|
tok = int(ch)
|
||||||
|
lit = string(ch)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf(`syntax error "%s"`, string(ch))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.next()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLetter returns true if the rune is a letter for identity.
|
||||||
|
func isLetter(ch rune) bool {
|
||||||
|
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || 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()), Pos: pos, Fatal: true}
|
||||||
|
}
|
||||||
|
lval.tok = ast.Token{Tok: tok, Lit: lit}
|
||||||
|
lval.tok.SetPosition(pos)
|
||||||
|
l.lit = lit
|
||||||
|
l.pos = pos
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets parse error.
|
||||||
|
func (l *Lexer) Error(msg string) {
|
||||||
|
l.e = &Error{Message: msg, Pos: l.pos, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParserSrc provides way to parse the code from source.
|
||||||
|
func ParseSrc(src string) ([]ast.Stmt, error) {
|
||||||
|
scanner := &Scanner{
|
||||||
|
src: []rune(src),
|
||||||
|
}
|
||||||
|
return Parse(scanner)
|
||||||
|
}
|
||||||
1997
vendor/github.com/mattn/anko/parser/parser.go
generated
vendored
Normal file
1997
vendor/github.com/mattn/anko/parser/parser.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
705
vendor/github.com/mattn/anko/parser/parser.go.y
generated
vendored
Normal file
705
vendor/github.com/mattn/anko/parser/parser.go.y
generated
vendored
Normal file
@@ -0,0 +1,705 @@
|
|||||||
|
%{
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mattn/anko/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%type<compstmt> compstmt
|
||||||
|
%type<stmts> stmts
|
||||||
|
%type<stmt> stmt
|
||||||
|
%type<stmt_if> stmt_if
|
||||||
|
%type<stmt_default> stmt_default
|
||||||
|
%type<stmt_case> stmt_case
|
||||||
|
%type<stmt_cases> stmt_cases
|
||||||
|
%type<typ> typ
|
||||||
|
%type<expr> expr
|
||||||
|
%type<exprs> exprs
|
||||||
|
%type<expr_many> expr_many
|
||||||
|
%type<expr_lets> expr_lets
|
||||||
|
%type<expr_pair> expr_pair
|
||||||
|
%type<expr_pairs> expr_pairs
|
||||||
|
%type<expr_idents> expr_idents
|
||||||
|
|
||||||
|
%union{
|
||||||
|
compstmt []ast.Stmt
|
||||||
|
stmt_if ast.Stmt
|
||||||
|
stmt_default ast.Stmt
|
||||||
|
stmt_case ast.Stmt
|
||||||
|
stmt_cases []ast.Stmt
|
||||||
|
stmts []ast.Stmt
|
||||||
|
stmt ast.Stmt
|
||||||
|
typ ast.Type
|
||||||
|
expr ast.Expr
|
||||||
|
exprs []ast.Expr
|
||||||
|
expr_many []ast.Expr
|
||||||
|
expr_lets ast.Expr
|
||||||
|
expr_pair ast.Expr
|
||||||
|
expr_pairs []ast.Expr
|
||||||
|
expr_idents []string
|
||||||
|
tok ast.Token
|
||||||
|
term ast.Token
|
||||||
|
terms ast.Token
|
||||||
|
opt_terms ast.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
%token<tok> IDENT NUMBER STRING ARRAY VARARG FUNC RETURN VAR THROW IF ELSE FOR IN EQEQ NEQ GE LE OROR ANDAND NEW TRUE FALSE NIL MODULE TRY CATCH FINALLY PLUSEQ MINUSEQ MULEQ DIVEQ ANDEQ OREQ BREAK CONTINUE PLUSPLUS MINUSMINUS POW SHIFTLEFT SHIFTRIGHT SWITCH CASE DEFAULT GO CHAN MAKE OPCHAN ARRAYLIT
|
||||||
|
|
||||||
|
%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 :
|
||||||
|
{
|
||||||
|
$$ = nil
|
||||||
|
if l, ok := yylex.(*Lexer); ok {
|
||||||
|
l.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 :
|
||||||
|
VAR expr_idents '=' expr_many
|
||||||
|
{
|
||||||
|
$$ = &ast.VarStmt{Names: $2, Exprs: $4}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '=' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.LetsStmt{Lhss: []ast.Expr{$1}, Operator: "=", Rhss: []ast.Expr{$3}}
|
||||||
|
}
|
||||||
|
| expr_many '=' expr_many
|
||||||
|
{
|
||||||
|
$$ = &ast.LetsStmt{Lhss: $1, Operator: "=", Rhss: $3}
|
||||||
|
}
|
||||||
|
| BREAK
|
||||||
|
{
|
||||||
|
$$ = &ast.BreakStmt{}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| CONTINUE
|
||||||
|
{
|
||||||
|
$$ = &ast.ContinueStmt{}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| RETURN exprs
|
||||||
|
{
|
||||||
|
$$ = &ast.ReturnStmt{Exprs: $2}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| THROW expr
|
||||||
|
{
|
||||||
|
$$ = &ast.ThrowStmt{Expr: $2}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| MODULE IDENT '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.ModuleStmt{Name: $2.Lit, Stmts: $4}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| stmt_if
|
||||||
|
{
|
||||||
|
$$ = $1
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FOR '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.LoopStmt{Stmts: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FOR IDENT IN expr '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.ForStmt{Var: $2.Lit, Value: $4, Stmts: $6}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FOR expr_lets ';' expr ';' expr '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.CForStmt{Expr1: $2, Expr2: $4, Expr3: $6, Stmts: $8}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FOR expr '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.LoopStmt{Expr: $2, Stmts: $4}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| TRY '{' compstmt '}' CATCH IDENT '{' compstmt '}' FINALLY '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.TryStmt{Try: $3, Var: $6.Lit, Catch: $8, Finally: $12}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| TRY '{' compstmt '}' CATCH '{' compstmt '}' FINALLY '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.TryStmt{Try: $3, Catch: $7, Finally: $11}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| TRY '{' compstmt '}' CATCH IDENT '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.TryStmt{Try: $3, Var: $6.Lit, Catch: $8}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| TRY '{' compstmt '}' CATCH '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.TryStmt{Try: $3, Catch: $7}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| SWITCH expr '{' stmt_cases '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.SwitchStmt{Expr: $2, Cases: $4}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr
|
||||||
|
{
|
||||||
|
$$ = &ast.ExprStmt{Expr: $1}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stmt_if :
|
||||||
|
stmt_if ELSE IF expr '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$1.(*ast.IfStmt).ElseIf = append($1.(*ast.IfStmt).ElseIf, &ast.IfStmt{If: $4, Then: $6})
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| stmt_if ELSE '{' compstmt '}'
|
||||||
|
{
|
||||||
|
if $$.(*ast.IfStmt).Else != nil {
|
||||||
|
yylex.Error("multiple else statement")
|
||||||
|
} else {
|
||||||
|
$$.(*ast.IfStmt).Else = append($$.(*ast.IfStmt).Else, $4...)
|
||||||
|
}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| IF expr '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.IfStmt{If: $2, Then: $4, Else: nil}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt_cases :
|
||||||
|
{
|
||||||
|
$$ = []ast.Stmt{}
|
||||||
|
}
|
||||||
|
| opt_terms stmt_case
|
||||||
|
{
|
||||||
|
$$ = []ast.Stmt{$2}
|
||||||
|
}
|
||||||
|
| opt_terms stmt_default
|
||||||
|
{
|
||||||
|
$$ = []ast.Stmt{$2}
|
||||||
|
}
|
||||||
|
| stmt_cases stmt_case
|
||||||
|
{
|
||||||
|
$$ = append($1, $2)
|
||||||
|
}
|
||||||
|
| stmt_cases stmt_default
|
||||||
|
{
|
||||||
|
for _, stmt := range $1 {
|
||||||
|
if _, ok := stmt.(*ast.DefaultStmt); ok {
|
||||||
|
yylex.Error("multiple default statement")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$$ = append($1, $2)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt_case :
|
||||||
|
CASE expr ':' opt_terms compstmt
|
||||||
|
{
|
||||||
|
$$ = &ast.CaseStmt{Expr: $2, Stmts: $5}
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt_default :
|
||||||
|
DEFAULT ':' opt_terms compstmt
|
||||||
|
{
|
||||||
|
$$ = &ast.DefaultStmt{Stmts: $4}
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_pair :
|
||||||
|
STRING ':' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.PairExpr{Key: $1.Lit, Value: $3}
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_pairs :
|
||||||
|
{
|
||||||
|
$$ = []ast.Expr{}
|
||||||
|
}
|
||||||
|
| expr_pair
|
||||||
|
{
|
||||||
|
$$ = []ast.Expr{$1}
|
||||||
|
}
|
||||||
|
| expr_pairs ',' opt_terms expr_pair
|
||||||
|
{
|
||||||
|
$$ = append($1, $4)
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_idents :
|
||||||
|
{
|
||||||
|
$$ = []string{}
|
||||||
|
}
|
||||||
|
| IDENT
|
||||||
|
{
|
||||||
|
$$ = []string{$1.Lit}
|
||||||
|
}
|
||||||
|
| expr_idents ',' opt_terms IDENT
|
||||||
|
{
|
||||||
|
$$ = append($1, $4.Lit)
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_lets : expr_many '=' expr_many
|
||||||
|
{
|
||||||
|
$$ = &ast.LetsExpr{Lhss: $1, Operator: "=", Rhss: $3}
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_many :
|
||||||
|
expr
|
||||||
|
{
|
||||||
|
$$ = []ast.Expr{$1}
|
||||||
|
}
|
||||||
|
| exprs ',' opt_terms expr
|
||||||
|
{
|
||||||
|
$$ = append($1, $4)
|
||||||
|
}
|
||||||
|
| exprs ',' opt_terms IDENT
|
||||||
|
{
|
||||||
|
$$ = append($1, &ast.IdentExpr{Lit: $4.Lit})
|
||||||
|
}
|
||||||
|
|
||||||
|
typ : IDENT
|
||||||
|
{
|
||||||
|
$$ = ast.Type{Name: $1.Lit}
|
||||||
|
}
|
||||||
|
| typ '.' IDENT
|
||||||
|
{
|
||||||
|
$$ = ast.Type{Name: $1.Name + "." + $3.Lit}
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs :
|
||||||
|
{
|
||||||
|
$$ = nil
|
||||||
|
}
|
||||||
|
| expr
|
||||||
|
{
|
||||||
|
$$ = []ast.Expr{$1}
|
||||||
|
}
|
||||||
|
| exprs ',' opt_terms expr
|
||||||
|
{
|
||||||
|
$$ = append($1, $4)
|
||||||
|
}
|
||||||
|
| exprs ',' opt_terms IDENT
|
||||||
|
{
|
||||||
|
$$ = append($1, &ast.IdentExpr{Lit: $4.Lit})
|
||||||
|
}
|
||||||
|
|
||||||
|
expr :
|
||||||
|
IDENT
|
||||||
|
{
|
||||||
|
$$ = &ast.IdentExpr{Lit: $1.Lit}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| NUMBER
|
||||||
|
{
|
||||||
|
$$ = &ast.NumberExpr{Lit: $1.Lit}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| '-' expr %prec UNARY
|
||||||
|
{
|
||||||
|
$$ = &ast.UnaryExpr{Operator: "-", Expr: $2}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| '!' expr %prec UNARY
|
||||||
|
{
|
||||||
|
$$ = &ast.UnaryExpr{Operator: "!", Expr: $2}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| '^' expr %prec UNARY
|
||||||
|
{
|
||||||
|
$$ = &ast.UnaryExpr{Operator: "^", Expr: $2}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| '&' IDENT %prec UNARY
|
||||||
|
{
|
||||||
|
$$ = &ast.AddrExpr{Expr: &ast.IdentExpr{Lit: $2.Lit}}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| '&' expr '.' IDENT %prec UNARY
|
||||||
|
{
|
||||||
|
$$ = &ast.AddrExpr{Expr: &ast.MemberExpr{Expr: $2, Name: $4.Lit}}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| '*' IDENT %prec UNARY
|
||||||
|
{
|
||||||
|
$$ = &ast.DerefExpr{Expr: &ast.IdentExpr{Lit: $2.Lit}}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| '*' expr '.' IDENT %prec UNARY
|
||||||
|
{
|
||||||
|
$$ = &ast.DerefExpr{Expr: &ast.MemberExpr{Expr: $2, Name: $4.Lit}}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| STRING
|
||||||
|
{
|
||||||
|
$$ = &ast.StringExpr{Lit: $1.Lit}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| TRUE
|
||||||
|
{
|
||||||
|
$$ = &ast.ConstExpr{Value: $1.Lit}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FALSE
|
||||||
|
{
|
||||||
|
$$ = &ast.ConstExpr{Value: $1.Lit}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| NIL
|
||||||
|
{
|
||||||
|
$$ = &ast.ConstExpr{Value: $1.Lit}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '?' expr ':' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.TernaryOpExpr{Expr: $1, Lhs: $3, Rhs: $5}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '.' IDENT
|
||||||
|
{
|
||||||
|
$$ = &ast.MemberExpr{Expr: $1, Name: $3.Lit}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FUNC '(' expr_idents ')' '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.FuncExpr{Args: $3, Stmts: $6}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FUNC '(' IDENT VARARG ')' '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.FuncExpr{Args: []string{$3.Lit}, Stmts: $7, VarArg: true}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FUNC IDENT '(' expr_idents ')' '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.FuncExpr{Name: $2.Lit, Args: $4, Stmts: $7}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| FUNC IDENT '(' IDENT VARARG ')' '{' compstmt '}'
|
||||||
|
{
|
||||||
|
$$ = &ast.FuncExpr{Name: $2.Lit, Args: []string{$4.Lit}, Stmts: $8, VarArg: true}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| '[' opt_terms exprs opt_terms ']'
|
||||||
|
{
|
||||||
|
$$ = &ast.ArrayExpr{Exprs: $3}
|
||||||
|
if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) }
|
||||||
|
}
|
||||||
|
| '[' opt_terms exprs ',' opt_terms ']'
|
||||||
|
{
|
||||||
|
$$ = &ast.ArrayExpr{Exprs: $3}
|
||||||
|
if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) }
|
||||||
|
}
|
||||||
|
| '{' opt_terms expr_pairs opt_terms '}'
|
||||||
|
{
|
||||||
|
mapExpr := make(map[string]ast.Expr)
|
||||||
|
for _, v := range $3 {
|
||||||
|
mapExpr[v.(*ast.PairExpr).Key] = v.(*ast.PairExpr).Value
|
||||||
|
}
|
||||||
|
$$ = &ast.MapExpr{MapExpr: mapExpr}
|
||||||
|
if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) }
|
||||||
|
}
|
||||||
|
| '{' opt_terms expr_pairs ',' opt_terms '}'
|
||||||
|
{
|
||||||
|
mapExpr := make(map[string]ast.Expr)
|
||||||
|
for _, v := range $3 {
|
||||||
|
mapExpr[v.(*ast.PairExpr).Key] = v.(*ast.PairExpr).Value
|
||||||
|
}
|
||||||
|
$$ = &ast.MapExpr{MapExpr: mapExpr}
|
||||||
|
if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) }
|
||||||
|
}
|
||||||
|
| '(' expr ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.ParenExpr{SubExpr: $2}
|
||||||
|
if l, ok := yylex.(*Lexer); ok { $$.SetPosition(l.pos) }
|
||||||
|
}
|
||||||
|
| NEW IDENT '(' exprs ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.NewExpr{Name: $2.Lit, SubExprs: $4}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '+' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "+", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '-' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "-", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '*' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "*", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '/' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "/", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '%' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "%", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr POW expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "**", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr SHIFTLEFT expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<<", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr SHIFTRIGHT expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">>", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr EQEQ expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "==", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr NEQ expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "!=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '>' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr GE expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '<' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr LE expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr PLUSEQ expr
|
||||||
|
{
|
||||||
|
$$ = &ast.AssocExpr{Lhs: $1, Operator: "+=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr MINUSEQ expr
|
||||||
|
{
|
||||||
|
$$ = &ast.AssocExpr{Lhs: $1, Operator: "-=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr MULEQ expr
|
||||||
|
{
|
||||||
|
$$ = &ast.AssocExpr{Lhs: $1, Operator: "*=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr DIVEQ expr
|
||||||
|
{
|
||||||
|
$$ = &ast.AssocExpr{Lhs: $1, Operator: "/=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr ANDEQ expr
|
||||||
|
{
|
||||||
|
$$ = &ast.AssocExpr{Lhs: $1, Operator: "&=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr OREQ expr
|
||||||
|
{
|
||||||
|
$$ = &ast.AssocExpr{Lhs: $1, Operator: "|=", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr PLUSPLUS
|
||||||
|
{
|
||||||
|
$$ = &ast.AssocExpr{Lhs: $1, Operator: "++"}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr MINUSMINUS
|
||||||
|
{
|
||||||
|
$$ = &ast.AssocExpr{Lhs: $1, Operator: "--"}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '|' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "|", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr OROR expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "||", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '&' expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr ANDAND expr
|
||||||
|
{
|
||||||
|
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&&", Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| IDENT '(' exprs VARARG ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.CallExpr{Name: $1.Lit, SubExprs: $3, VarArg: true}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| IDENT '(' exprs ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.CallExpr{Name: $1.Lit, SubExprs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| GO IDENT '(' exprs VARARG ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.CallExpr{Name: $2.Lit, SubExprs: $4, VarArg: true, Go: true}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| GO IDENT '(' exprs ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.CallExpr{Name: $2.Lit, SubExprs: $4, Go: true}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| expr '(' exprs VARARG ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.AnonCallExpr{Expr: $1, SubExprs: $3, VarArg: true}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '(' exprs ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.AnonCallExpr{Expr: $1, SubExprs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| GO expr '(' exprs VARARG ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.AnonCallExpr{Expr: $2, SubExprs: $4, VarArg: true, Go: true}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
| GO expr '(' exprs ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.AnonCallExpr{Expr: $2, SubExprs: $4, Go: true}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| IDENT '[' expr ']'
|
||||||
|
{
|
||||||
|
$$ = &ast.ItemExpr{Value: &ast.IdentExpr{Lit: $1.Lit}, Index: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '[' expr ']'
|
||||||
|
{
|
||||||
|
$$ = &ast.ItemExpr{Value: $1, Index: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| IDENT '[' expr ':' expr ']'
|
||||||
|
{
|
||||||
|
$$ = &ast.SliceExpr{Value: &ast.IdentExpr{Lit: $1.Lit}, Begin: $3, End: $5}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr '[' expr ':' expr ']'
|
||||||
|
{
|
||||||
|
$$ = &ast.SliceExpr{Value: $1, Begin: $3, End: $5}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| MAKE '(' CHAN typ ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.MakeChanExpr{Type: $4.Name, SizeExpr: nil}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| MAKE '(' CHAN typ ',' expr ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.MakeChanExpr{Type: $4.Name, SizeExpr: $6}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| MAKE '(' ARRAYLIT typ ',' expr ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.MakeArrayExpr{Type: $4.Name, LenExpr: $6}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| MAKE '(' ARRAYLIT typ ',' expr ',' expr ')'
|
||||||
|
{
|
||||||
|
$$ = &ast.MakeArrayExpr{Type: $4.Name, LenExpr: $6, CapExpr: $8}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| expr OPCHAN expr
|
||||||
|
{
|
||||||
|
$$ = &ast.ChanExpr{Lhs: $1, Rhs: $3}
|
||||||
|
$$.SetPosition($1.Position())
|
||||||
|
}
|
||||||
|
| OPCHAN expr
|
||||||
|
{
|
||||||
|
$$ = &ast.ChanExpr{Rhs: $2}
|
||||||
|
$$.SetPosition($2.Position())
|
||||||
|
}
|
||||||
|
|
||||||
|
opt_terms : /* none */
|
||||||
|
| terms
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
terms : term
|
||||||
|
{
|
||||||
|
}
|
||||||
|
| terms term
|
||||||
|
{
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
term : ';'
|
||||||
|
{
|
||||||
|
}
|
||||||
|
| '\n'
|
||||||
|
{
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
||||||
2
vendor/github.com/mattn/anko/vm/doc.go
generated
vendored
Normal file
2
vendor/github.com/mattn/anko/vm/doc.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Package vm implements virtual-machine for anko.
|
||||||
|
package vm
|
||||||
258
vendor/github.com/mattn/anko/vm/env.go
generated
vendored
Normal file
258
vendor/github.com/mattn/anko/vm/env.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mattn/anko/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)
|
||||||
|
}
|
||||||
1504
vendor/github.com/mattn/anko/vm/vm.go
generated
vendored
Normal file
1504
vendor/github.com/mattn/anko/vm/vm.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user