43 Commits

Author SHA1 Message Date
Leonel Quinteros
4b94e83723 Properly handle singular vs plural defaults for untranslated strings. Fixes #9 2017-09-01 13:28:51 -03:00
Leonel Quinteros
756045ab5e Handle empty translation strings as untranslated as defined in https://www.gnu.org/software/gettext/manual/html_node/Untranslated-Entries.html. Fixes #9 2017-08-30 10:53:39 -03:00
Leonel Quinteros
2bb9254f26 Edit badges 2017-07-05 19:06:45 -03:00
Leonel Quinteros
88952938dc Update readme 2017-07-05 19:03:09 -03:00
Leonel Quinteros
cabefc2786 Fix: Remove plural default in Po Gets #8 2017-07-05 18:40:45 -03:00
Leonel Quinteros
b8456329aa Merge pull request #7 from mattn/kinako
use kinako instead of anko
2017-06-16 11:18:48 -03:00
Yasuhiro Matsumoto
9bcfe68591 use kinako instead of anko 2017-06-16 16:06:17 +09:00
Leonel Quinteros
28bb992796 Remove support for Go 1.5 as isn't supported by anko package. 2017-05-29 15:47:39 -03:00
Leonel Quinteros
eccb6c7cf3 Merge branch 'fishbrain-master' 2017-05-29 15:44:28 -03:00
Mattias Lundell
1c611eff63 removed debug print 2017-05-29 11:44:49 +02:00
Mattias Lundell
4a52c7709d Add support for multi-line msgid 2017-05-29 11:36:18 +02:00
Leonel Quinteros
2c51ed2000 Add license badge 2016-08-09 12:57:24 -03:00
Leonel Quinteros
d52a867157 Add version badge 2016-08-09 12:46:32 -03:00
Leonel Quinteros
744b85e833 Add codecov.io badge 2016-08-09 12:41:18 -03:00
Leonel Quinteros
ffcea86f47 Add codecov.io configuration to travis file 2016-08-09 12:29:36 -03:00
Leonel Quinteros
a735812a72 Release latest changes 2016-08-08 11:37:53 -03:00
Leonel Quinteros
0284dca059 Refactoring to make gocyclo happy 2016-08-08 11:36:05 -03:00
Leonel Quinteros
9a30bf7f45 Make golint happy 2016-08-08 09:54:52 -03:00
Leonel Quinteros
520de6b223 Add goreportcard.com bagde 2016-08-07 00:10:41 -03:00
Leonel Quinteros
468e983e10 Add gocover.io link 2016-08-07 00:02:34 -03:00
Leonel Quinteros
04d24e4d7c Add gocover.io link 2016-08-07 00:02:05 -03:00
Leonel Quinteros
41d0924ac6 Godep vendoring for external packages 2016-07-19 14:18:24 -03:00
Leonel Quinteros
745244309c Go back to use mattn/anko repo 2016-07-19 14:12:06 -03:00
Leonel Quinteros
af699a9df0 Add Travis badge 2016-07-15 19:49:54 -03:00
Leonel Quinteros
eaec826ac5 Reduce go versions on travis tests to 1.5 and forward 2016-07-15 19:46:51 -03:00
Leonel Quinteros
653444ba98 Fix race conditions using Anko. 2016-07-15 19:46:17 -03:00
Leonel Quinteros
74daa24696 Plural-Forms formula support. Headers parsing. Multiline strings support. 2016-07-15 19:04:59 -03:00
Leonel Quinteros
af707140e3 Link GNU home from readme 2016-07-12 16:46:55 -03:00
Leonel Quinteros
e1c7cc4c7d Improved readme docs 2016-07-01 12:43:21 -03:00
Leonel Quinteros
4ec3949a9c Improved readme docs 2016-07-01 12:40:59 -03:00
Leonel Quinteros
2981e87657 Set default library location to default path on GNU gettext utilities. 2016-07-01 12:21:59 -03:00
Leonel Quinteros
ad380b8ede Fix Locale.findPO to support language code simplification on LC_MESSAGES dir. 2016-07-01 11:21:13 -03:00
Leonel Quinteros
e62229fc8e Merge pull request #3 from cinghiale/support_LC_MESSAGES
added support for .po files stored in a LC_MESSAGES subdir
2016-07-01 10:59:45 -03:00
David Mugnai
8a0f825cf4 added support for .po files stored in a LC_MESSAGES subdir 2016-07-01 15:41:56 +02:00
Leonel Quinteros
f9c18b1237 Improved docs 2016-06-26 15:52:59 -03:00
Leonel Quinteros
80c24ef4e1 Link v1.0.0 release 2016-06-26 15:46:47 -03:00
Leonel Quinteros
ea87d40cc2 Add Context (msgctxt) support 2016-06-26 15:43:54 -03:00
Leonel Quinteros
21c6bc86cb Improve test coverage to ~100% 2016-06-26 12:15:08 -03:00
Leonel Quinteros
2c5ca9c0e6 Improve test coverage to ~100% 2016-06-26 11:54:28 -03:00
Leonel Quinteros
5f34149d25 Fix typos 2016-06-24 18:32:58 -03:00
Leonel Quinteros
50cf0f07b7 Release v0.9.1 2016-06-24 17:48:06 -03:00
Leonel Quinteros
d6f4cbb2d5 Add race conditions tests. Fix races. 2016-06-24 17:45:12 -03:00
Leonel Quinteros
6e728a3df5 Link to release on Readme 2016-06-24 17:14:07 -03:00
18 changed files with 4955 additions and 198 deletions

16
.travis.yml Normal file
View File

@@ -0,0 +1,16 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
before_install:
- go get -t -v ./...
script: go test -v -race -coverprofile=coverage.txt -covermode=atomic .
after_success:
- bash <(curl -s https://codecov.io/bash)

18
Godeps/Godeps.json generated Normal file
View File

@@ -0,0 +1,18 @@
{
"ImportPath": "github.com/leonelquinteros/gotext",
"GoVersion": "devel +be9d7f6d87 Fri Jun 16 00:31:25 2017 +0000",
"Deps": [
{
"ImportPath": "github.com/mattn/kinako/ast",
"Rev": "fbc18625ec69b28ae03b4eea4c349f215840ee09"
},
{
"ImportPath": "github.com/mattn/kinako/parser",
"Rev": "fbc18625ec69b28ae03b4eea4c349f215840ee09"
},
{
"ImportPath": "github.com/mattn/kinako/vm",
"Rev": "fbc18625ec69b28ae03b4eea4c349f215840ee09"
}
]
}

5
Godeps/Readme generated Normal file
View 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.

141
README.md
View File

@@ -1,24 +1,39 @@
[![GitHub release](https://img.shields.io/github/release/leonelquinteros/gotext.svg)](https://github.com/leonelquinteros/gotext)
[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![GoDoc](https://godoc.org/github.com/leonelquinteros/gotext?status.svg)](https://godoc.org/github.com/leonelquinteros/gotext)
[![Build Status](https://travis-ci.org/leonelquinteros/gotext.svg?branch=master)](https://travis-ci.org/leonelquinteros/gotext)
[![codecov](https://codecov.io/gh/leonelquinteros/gotext/branch/master/graph/badge.svg)](https://codecov.io/gh/leonelquinteros/gotext)
[![Go Report Card](https://goreportcard.com/badge/github.com/leonelquinteros/gotext)](https://goreportcard.com/report/github.com/leonelquinteros/gotext)
# Gotext
GNU gettext utilities for Go.
**Version: 0.9.0**
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
#Features
# Features
- Implements GNU gettext support in native Go.
- Safe for concurrent use accross multiple goroutines.
- Complete support for [PO files](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) including:
- Support for multiline strings and headers.
- Support for variables inside translation strings using Go's [fmt syntax](https://golang.org/pkg/fmt/).
- Support for [pluralization rules](https://www.gnu.org/software/gettext/manual/html_node/Translating-plural-forms.html).
- Support for [message contexts](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html).
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
- It works with UTF-8 encoding as it's the default for Go language.
- Unit tests available
- Language codes are automatically simplified from the form "en_UK" to "en" if the formed isn't available.
- Unit tests available.
- Language codes are automatically simplified from the form `en_UK` to `en` if the first isn't available.
- Ready to use inside Go templates.
- Support for pluralization rules.
- Support for variables inside translation strings using Go's [fmt package syntax](https://golang.org/pkg/fmt/)
# License
[MIT license](LICENSE)
# Documentation
Refer to the Godoc package documentation at (https://godoc.org/github.com/leonelquinteros/gotext)
# Installation
@@ -31,42 +46,62 @@ go get github.com/leonelquinteros/gotext
- No need for environment variables. Some naming conventions are applied but not needed.
# License
#### Version vendoring
[MIT license](LICENSE)
Stable releases use [semantic versioning](http://semver.org/spec/v2.0.0.html) tagging on this repository.
You can rely on this to use your preferred vendoring tool or to manually retrieve the corresponding release tag from the GitHub repository.
# Documentation
##### Vendoring with [gopkg.in](http://labix.org/gopkg.in)
Refer to the Godoc package documentation at (https://godoc.org/github.com/leonelquinteros/gotext)
[http://gopkg.in/leonelquinteros/gotext.v1](http://gopkg.in/leonelquinteros/gotext.v1)
To get the latest v1 package stable release, execute:
```
go get gopkg.in/leonelquinteros/gotext.v1
```
To import this package, add the following line to your code:
```go
import "gopkg.in/leonelquinteros/gotext.v1"
```
Refer to it as gotext.
# Locales directories structure
The package will asume a directories structure starting with a base path that will be provided to the package configuration
The package will assume a directories structure starting with a base path that will be provided to the package configuration
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
So if the language set is "en_UK", but there is no directory named after that code and there is a directory named "en",
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
all package functions will be able to resolve this generalization and provide translations for the more general library.
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
That said, most functions will work with any coding standard as long the directory name matches the language code set on the configuration.
A normal library directory structure may look like:
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
A library directory structure can look like:
```
/path/to/locales
/path/to/locales/en_US
/path/to/locales/en_US/default.po
/path/to/locales/en_US/extras.po
/path/to/locales/en_US/LC_MESSAGES
/path/to/locales/en_US/LC_MESSAGES/default.po
/path/to/locales/en_US/LC_MESSAGES/extras.po
/path/to/locales/en_UK
/path/to/locales/en_UK/default.po
/path/to/locales/en_UK/extras.po
/path/to/locales/en_UK/LC_MESSAGES
/path/to/locales/en_UK/LC_MESSAGES/default.po
/path/to/locales/en_UK/LC_MESSAGES/extras.po
/path/to/locales/en_AU
/path/to/locales/en_AU/default.po
/path/to/locales/en_AU/extras.po
/path/to/locales/en_AU/LC_MESSAGES
/path/to/locales/en_AU/LC_MESSAGES/default.po
/path/to/locales/en_AU/LC_MESSAGES/extras.po
/path/to/locales/es
/path/to/locales/es/default.po
/path/to/locales/es/extras.po
@@ -84,9 +119,9 @@ And so on...
# About translation function names
The standard GNU gettext defines helper functions that maps to the gettext() function and it's widely adopted by most implementations.
The 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")
@@ -109,8 +144,8 @@ func _(str string, vars ...interface{}) string {
This is valid and can be used within a package.
In normal conditions the Go compiler will optimize the calls to _() by replacing its content in place of the function call to reduce the function calling overhead.
This is a normal Go compiler behaviour.
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 behavior.
@@ -121,17 +156,20 @@ This is a normal Go compiler behaviour.
For quick/simple translations you can use the package level functions directly.
```go
import "github.com/leonelquinteros/gotext"
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Configure package
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
// Translate text from default domain
println(gotext.Get("My text on 'domain-name' domain"))
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
// Translate text from a different domain without reconfigure
println(gotext.GetD("domain2", "Another text on a different domain"))
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
}
```
@@ -142,7 +180,10 @@ All translation strings support dynamic variables to be inserted without transla
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
```go
import "github.com/leonelquinteros/gotext"
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Configure package
@@ -152,7 +193,7 @@ func main() {
name := "John"
// Translate text with variables
println(gotext.Get("Hi, my name is %s", name))
fmt.Println(gotext.Get("Hi, my name is %s", name))
}
```
@@ -164,7 +205,10 @@ When having multiple languages/domains/libraries at the same time, you can creat
so you can handle each settings on their own.
```go
import "github.com/leonelquinteros/gotext"
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Create Locale with library path and language code
@@ -174,18 +218,18 @@ func main() {
l.AddDomain("default")
// Translate text from default domain
println(l.Get("Translate this"))
fmt.Println(l.Get("Translate this"))
// Load different domain
l.AddDomain("translations")
// Translate text from domain
println(l.GetD("translations", "Translate this"))
fmt.Println(l.GetD("translations", "Translate this"))
}
```
This is also helpful for using inside templates (from the "text/template" package), where you can pass the Locale object to the template.
If you set the Locale object as "Loc" in the template, then the templace code would look like:
If you set the Locale object as "Loc" in the template, then the template code would look like:
```
{{ .Loc.Get "Translate this" }}
@@ -198,7 +242,10 @@ For when you need to work with PO files and strings,
you can directly use the Po object to parse it and access the translations in there in the same way.
```go
import "github.com/leonelquinteros/gotext"
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Set PO content
@@ -217,7 +264,7 @@ msgstr "This one sets the var: %s"
po := new(Po)
po.Parse(str)
println(po.Get("Translate this"))
fmt.Println(po.Get("Translate this"))
}
```
@@ -225,13 +272,26 @@ msgstr "This one sets the var: %s"
## Use plural forms of translations
PO format supports defining one or more plural forms for the same translation.
Relying on the PO file headers, a Plural-Forms formula can be set on the translation file
as defined in (https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html)
Plural formulas are parsed and evaluated using [Kinako](https://github.com/mattn/kinako)
```go
import "github.com/leonelquinteros/gotext"
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Set PO content
str := `
msgid ""
msgstr ""
# Header below
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Translate this"
msgstr "Translated text"
@@ -242,15 +302,14 @@ msgid "One with var: %s"
msgid_plural "Several with vars: %s"
msgstr[0] "This one is the singular: %s"
msgstr[1] "This one is the plural: %s"
msgstr[2] "And this is the second plural form: %s"
`
// Create Po object
po := new(Po)
po.Parse(str)
println(po.GetN("One with var: %s", "Several with vars: %s", 2, v))
// "And this is the second plural form: Variable"
fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
// "This one is the plural: Variable"
}
```

View File

@@ -1,21 +1,22 @@
/*
Package gotext implements GNU gettext utilities.
Version 0.9.0 (stable)
For quick/simple translations you can use the package level functions directly.
import "github.com/leonelquinteros/gotext"
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Configure package
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
// Translate text from default domain
println(gotext.Get("My text on 'domain-name' domain"))
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
// Translate text from a different domain without reconfigure
println(gotext.GetD("domain2", "Another text on a different domain"))
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
}
*/
@@ -30,7 +31,7 @@ var (
language = "en_US"
// 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 *Locale
@@ -102,8 +103,7 @@ func Get(str string, vars ...interface{}) string {
return GetD(domain, str, vars...)
}
// GetN retrieves the (N)th plural form translation for the given string in the "default" domain.
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
// GetN retrieves the (N)th plural form of translation for the given string in the "default" domain.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func GetN(str, plural string, n int, vars ...interface{}) string {
return GetND("default", str, plural, n, vars...)
@@ -112,10 +112,10 @@ func GetN(str, plural string, n int, vars ...interface{}) 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.
func GetD(dom, str string, vars ...interface{}) string {
return GetND(dom, str, str, 0, vars...)
return GetND(dom, str, str, 1, vars...)
}
// GetND retrieves the (N)th plural form translation in the given domain for a given string.
// GetND retrieves the (N)th plural form of translation in the given domain for a given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func GetND(dom, str, plural string, n int, vars ...interface{}) string {
// Try to load default package Locale storage
@@ -124,3 +124,31 @@ func GetND(dom, str, plural string, n int, vars ...interface{}) string {
// Return translation
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...)
}

View File

@@ -6,7 +6,220 @@ import (
"testing"
)
func TestGettersSetters(t *testing.T) {
SetDomain("test")
dom := GetDomain()
if dom != "test" {
t.Errorf("Expected GetDomain to return 'test', but got '%s'", dom)
}
SetLibrary("/tmp/test")
lib := GetLibrary()
if lib != "/tmp/test" {
t.Errorf("Expected GetLibrary to return '/tmp/test', but got '%s'", lib)
}
SetLanguage("es")
lang := GetLanguage()
if lang != "es" {
t.Errorf("Expected GetLanguage to return 'es', but got '%s'", lang)
}
}
func TestPackageFunctions(t *testing.T) {
// Set PO content
str := `
# 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"
msgid "Untranslated"
msgid_plural "Several untranslated"
msgstr[0] ""
msgstr[1] ""
`
// Create Locales directory on default location
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to default domain file
filename := path.Clean(dirname + string(os.PathSeparator) + "default.po")
f, err := os.Create(filename)
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
defer f.Close()
_, err = f.WriteString(str)
if err != nil {
t.Fatalf("Can't write to test file: %s", err.Error())
}
// Set package configuration
Configure("/tmp", "en_US", "default")
// Test translations
tr := Get("My text")
if tr != "Translated text" {
t.Errorf("Expected 'Translated text' but got '%s'", tr)
}
v := "Variable"
tr = Get("One with var: %s", v)
if tr != "This one is the singular: Variable" {
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
}
// Test plural
tr = GetN("One with var: %s", "Several with vars: %s", 2, v)
if tr != "This one is the plural: Variable" {
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
}
// Test context translations
tr = GetC("Some random in a context", "Ctx")
if tr != "Some random translation in a context" {
t.Errorf("Expected 'Some random translation in a context' but got '%s'", tr)
}
v = "Variable"
tr = GetC("One with var: %s", "Ctx", v)
if tr != "This one is the singular in a Ctx context: Variable" {
t.Errorf("Expected 'This one is the singular in a Ctx context: Variable' but got '%s'", tr)
}
tr = GetNC("One with var: %s", "Several with vars: %s", 19, "Ctx", v)
if tr != "This one is the plural in a Ctx context: Variable" {
t.Errorf("Expected 'This one is the plural in a Ctx context: Variable' but got '%s'", tr)
}
}
func TestUntranslated(t *testing.T) {
// Set PO content
str := `
# msgid ""
# msgstr ""
# Initial comment
# Headers below
"Language: en\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Untranslated"
msgid_plural "Several untranslated"
msgstr[0] ""
msgstr[1] ""
`
// Create Locales directory on default location
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to default domain file
filename := path.Clean(dirname + string(os.PathSeparator) + "default.po")
f, err := os.Create(filename)
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
defer f.Close()
_, err = f.WriteString(str)
if err != nil {
t.Fatalf("Can't write to test file: %s", err.Error())
}
// Set package configuration
Configure("/tmp", "en_US", "default")
// Test untranslated
tr := Get("Untranslated")
if tr != "Untranslated" {
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
}
tr = GetN("Untranslated", "Several untranslated", 1)
if tr != "Untranslated" {
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
}
tr = GetN("Untranslated", "Several untranslated", 2)
if tr != "Several untranslated" {
t.Errorf("Expected 'Several untranslated' but got '%s'", tr)
}
tr = GetD("default", "Untranslated")
if tr != "Untranslated" {
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
}
tr = GetND("default", "Untranslated", "Several untranslated", 1)
if tr != "Untranslated" {
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
}
tr = GetND("default", "Untranslated", "Several untranslated", 2)
if tr != "Several untranslated" {
t.Errorf("Expected 'Several untranslated' but got '%s'", tr)
}
}
func TestPackageRace(t *testing.T) {
// Set PO content
str := `# Some comment
msgid "My text"
@@ -45,21 +258,22 @@ msgstr[2] "And this is the second plural form: %s"
t.Fatalf("Can't write to test file: %s", err.Error())
}
// Init sync channels
c1 := make(chan bool)
c2 := make(chan bool)
for i := 0; i < 100; i++ {
// Test translations
tr := Get("My text")
if tr != "Translated text" {
t.Errorf("Expected 'Translated text' but got '%s'", tr)
}
go func(done chan bool) {
Get("My text")
done <- true
}(c1)
v := "Variable"
tr = Get("One with var: %s", v)
if tr != "This one is the singular: Variable" {
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
}
go func(done chan bool) {
Get("My text")
done <- true
}(c2)
// Test plural
tr = GetN("One with var: %s", "Several with vars: %s", 2, v)
if tr != "And this is the second plural form: Variable" {
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
Get("My text")
}
}

108
locale.go
View File

@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
"sync"
)
/*
@@ -13,23 +14,26 @@ multiple languages at the same time by working with this object.
Example:
import "github.com/leonelquinteros/gotext"
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Create Locale with library path and language code
l := gotext.NewLocale("/path/to/i18n/dir", "en_US")
// Load domain '/path/to/i18n/dir/en_US/default.po'
// Load domain '/path/to/i18n/dir/en_US/LC_MESSAGES/default.po'
l.AddDomain("default")
// Translate text from default domain
println(l.Get("Translate this"))
fmt.Println(l.Get("Translate this"))
// Load different domain ('/path/to/i18n/dir/en_US/extras.po')
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.po')
l.AddDomain("extras")
// Translate text from domain
println(l.GetD("extras", "Translate this"))
fmt.Println(l.GetD("extras", "Translate this"))
}
*/
@@ -42,6 +46,9 @@ type Locale struct {
// List of available domains for this locale.
domains map[string]*Po
// Sync Mutex
sync.RWMutex
}
// NewLocale creates and initializes a new Locale object for a given language.
@@ -54,25 +61,43 @@ func NewLocale(p, l string) *Locale {
}
}
func (l *Locale) findPO(dom string) string {
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+".po")
if _, err := os.Stat(filename); err == nil {
return filename
}
if len(l.lang) > 2 {
filename = path.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+".po")
if _, err := os.Stat(filename); err == nil {
return filename
}
}
filename = path.Join(l.path, l.lang, dom+".po")
if _, err := os.Stat(filename); err == nil {
return filename
}
if len(l.lang) > 2 {
filename = path.Join(l.path, l.lang[:2], dom+".po")
}
return filename
}
// AddDomain creates a new domain for a given locale object and initializes the Po object.
// If the domain exists, it gets reloaded.
func (l *Locale) AddDomain(dom string) {
po := new(Po)
// Check for file.
filename := path.Clean(l.path + string(os.PathSeparator) + l.lang + string(os.PathSeparator) + dom + ".po")
// Try to use the generic language dir if the provided isn't available
if _, err := os.Stat(filename); err != nil {
if len(l.lang) > 2 {
filename = path.Clean(l.path + string(os.PathSeparator) + l.lang[:2] + string(os.PathSeparator) + dom + ".po")
}
}
// Parse file.
po.ParseFile(filename)
po.ParseFile(l.findPO(dom))
// Save new domain
l.Lock()
defer l.Unlock()
if l.domains == nil {
l.domains = make(map[string]*Po)
}
@@ -85,23 +110,25 @@ func (l *Locale) Get(str string, vars ...interface{}) string {
return l.GetD("default", str, vars...)
}
// GetN retrieves the (N)th plural form translation for the given string in the "default" domain.
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
// GetN retrieves the (N)th plural form of translation for the given string in the "default" domain.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
return l.GetND("default", str, plural, n, vars...)
}
// GetD returns the corresponding translation in the given domain for a given string.
// GetD returns the corresponding translation in the given domain for the given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
return l.GetND(dom, str, str, 0, vars...)
return l.GetND(dom, str, str, 1, vars...)
}
// GetND retrieves the (N)th plural form translation in the given domain for a given string.
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
// GetND retrieves the (N)th plural form of translation in the given domain for the given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string {
// Sync read
l.RLock()
defer l.RUnlock()
if l.domains != nil {
if _, ok := l.domains[dom]; ok {
if l.domains[dom] != nil {
@@ -113,3 +140,40 @@ func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) stri
// Return the same we received by default
return fmt.Sprintf(plural, vars...)
}
// 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...)
}

View File

@@ -7,6 +7,243 @@ import (
)
func TestLocale(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 translations
tr := l.GetD("my_domain", "My text")
if tr != "Translated text" {
t.Errorf("Expected 'Translated text' but got '%s'", tr)
}
v := "Variable"
tr = l.GetD("my_domain", "One with var: %s", v)
if tr != "This one is the singular: Variable" {
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
}
// Test plural
tr = l.GetND("my_domain", "One with var: %s", "Several with vars: %s", 7, v)
if tr != "This one is the plural: Variable" {
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
}
// Test context translations
v = "Test"
tr = l.GetDC("my_domain", "One with var: %s", "Ctx", v)
if tr != "This one is the singular in a Ctx context: Test" {
t.Errorf("Expected 'This one is the singular in a Ctx context: Test' but got '%s'", tr)
}
// Test plural
tr = l.GetNDC("my_domain", "One with var: %s", "Several with vars: %s", 3, "Ctx", v)
if tr != "This one is the plural in a Ctx context: Test" {
t.Errorf("Expected 'This one is the plural in a Ctx context: Test' but got '%s'", tr)
}
// Test last translation
tr = l.GetD("my_domain", "More")
if tr != "More translation" {
t.Errorf("Expected 'More translation' but got '%s'", tr)
}
}
func TestLocaleFails(t *testing.T) {
// Set PO content
str := `
msgid ""
msgstr ""
# Initial comment
# Headers below
"Language: en\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
# Some comment
msgid "My text"
msgstr "Translated text"
# More comments
msgid "Another string"
msgstr ""
msgid "One with var: %s"
msgid_plural "Several with vars: %s"
msgstr[0] "This one is the singular: %s"
msgstr[1] "This one is the plural: %s"
msgstr[2] "And this is the second plural form: %s"
msgid "This one has invalid syntax translations"
msgid_plural "Plural index"
msgstr[abc] "Wrong index"
msgstr[1 "Forgot to close brackets"
msgstr[0] "Badly formatted string'
msgid "Invalid formatted id[] with no translations
msgctxt "Ctx"
msgid "One with var: %s"
msgid_plural "Several with vars: %s"
msgstr[0] "This one is the singular in a Ctx context: %s"
msgstr[1] "This one is the plural in a Ctx context: %s"
msgid "Some random"
msgstr "Some random translation"
msgctxt "Ctx"
msgid "Some random in a context"
msgstr "Some random translation in a context"
msgid "More"
msgstr "More translation"
`
// Create Locales directory with simplified language code
dirname := path.Join("/tmp", "en", "LC_MESSAGES")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to file
filename := path.Join(dirname, "my_domain.po")
f, err := os.Create(filename)
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
defer f.Close()
_, err = f.WriteString(str)
if err != nil {
t.Fatalf("Can't write to test file: %s", err.Error())
}
// Create Locale with full language code
l := NewLocale("/tmp", "en_US")
// Force nil domain storage
l.domains = nil
// Add domain
l.AddDomain("my_domain")
// Test non-existent "deafult" domain responses
tr := l.Get("My text")
if tr != "My text" {
t.Errorf("Expected 'My text' but got '%s'", tr)
}
v := "Variable"
tr = l.GetN("One with var: %s", "Several with vars: %s", 2, v)
if tr != "Several with vars: Variable" {
t.Errorf("Expected 'Several with vars: Variable' but got '%s'", tr)
}
// Test inexistent translations
tr = l.Get("This is a test")
if tr != "This is a test" {
t.Errorf("Expected 'This is a test' but got '%s'", tr)
}
tr = l.GetN("This is a test", "This are tests", 1)
if tr != "This are tests" {
t.Errorf("Expected 'This are tests' but got '%s'", tr)
}
// Test syntax error parsed translations
tr = l.Get("This one has invalid syntax translations")
if tr != "This one has invalid syntax translations" {
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
}
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
if tr != "This are tests" {
t.Errorf("Expected 'Plural index' but got '%s'", tr)
}
}
func TestLocaleRace(t *testing.T) {
// Set PO content
str := `# Some comment
msgid "My text"
@@ -25,14 +262,14 @@ msgstr[2] "And this is the second plural form: %s"
`
// Create Locales directory with simplified language code
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en")
dirname := path.Join("/tmp", "es")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to file
filename := path.Clean(dirname + string(os.PathSeparator) + "my_domain.po")
filename := path.Join(dirname, "race.po")
f, err := os.Create(filename)
if err != nil {
@@ -46,26 +283,28 @@ msgstr[2] "And this is the second plural form: %s"
}
// Create Locale with full language code
l := NewLocale("/tmp", "en_US")
l := NewLocale("/tmp", "es")
// Add domain
l.AddDomain("my_domain")
// Init sync channels
ac := make(chan bool)
rc := make(chan bool)
// Test translations
tr := l.GetD("my_domain", "My text")
if tr != "Translated text" {
t.Errorf("Expected 'Translated text' but got '%s'", tr)
}
// Add domain in goroutine
go func(l *Locale, done chan bool) {
l.AddDomain("race")
done <- true
}(l, ac)
v := "Variable"
tr = l.GetD("my_domain", "One with var: %s", v)
if tr != "This one is the singular: Variable" {
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
}
// Get translations in goroutine
go func(l *Locale, done chan bool) {
l.GetD("race", "My text")
done <- true
}(l, rc)
// Test plural
tr = l.GetND("my_domain", "One with var: %s", "Several with vars: %s", 2, v)
if tr != "And this is the second plural form: Variable" {
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
}
// Get translations at top level
l.GetD("race", "My text")
// Wait for goroutines to finish
<-ac
<-rc
}

402
po.go
View File

@@ -1,8 +1,11 @@
package gotext
import (
"bufio"
"fmt"
"github.com/mattn/kinako/vm"
"io/ioutil"
"net/textproto"
"os"
"strconv"
"strings"
@@ -11,7 +14,7 @@ import (
type translation struct {
id string
pluralId string
pluralID string
trs map[int]string
}
@@ -25,8 +28,10 @@ func newTranslation() *translation {
func (t *translation) get() string {
// Look for translation index 0
if _, ok := t.trs[0]; ok {
if t.trs[0] != "" {
return t.trs[0]
}
}
// Return unstranlated id by default
return t.id
@@ -35,21 +40,31 @@ func (t *translation) get() string {
func (t *translation) getN(n int) string {
// Look for translation index
if _, ok := t.trs[n]; ok {
if t.trs[n] != "" {
return t.trs[n]
}
}
// Return unstranlated plural by default
return t.pluralId
// Return unstranlated singular if corresponding
if n == 0 {
return t.id
}
// Return untranslated plural by default
return t.pluralID
}
/*
Po parses the content of any PO file and provides all the translation functions needed.
It's the base object used by all packafe methods.
And it's safe for concurrent use by multiple goroutines by using the sync package for write locking.
It's the base object used by all package methods.
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
Example:
import "github.com/leonelquinteros/gotext"
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Create po object
@@ -59,18 +74,46 @@ Example:
po.ParseFile("/path/to/po/file/translations.po")
// Get translation
println(po.Get("Translate this"))
fmt.Println(po.Get("Translate this"))
}
*/
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
translations map[string]*translation
contexts map[string]map[string]*translation
// Sync Mutex
sync.RWMutex
// Parsing buffers
trBuffer *translation
ctxBuffer string
}
type parseState int
const (
head parseState = iota
msgCtxt
msgID
msgIDPlural
msgStr
)
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
func (po *Po) ParseFile(f string) {
// Check if file exists
@@ -95,97 +138,288 @@ func (po *Po) ParseFile(f string) {
// Parse loads the translations specified in the provided string (str)
func (po *Po) Parse(str string) {
if po.translations == nil {
// Lock while parsing
po.Lock()
defer po.Unlock()
// Init storage
if po.translations == nil {
po.translations = make(map[string]*translation)
po.Unlock()
po.contexts = make(map[string]map[string]*translation)
}
// Get lines
lines := strings.Split(str, "\n")
tr := newTranslation()
// Init buffer
po.trBuffer = newTranslation()
po.ctxBuffer = ""
state := head
for _, l := range lines {
// Trim spaces
l = strings.TrimSpace(l)
// Skip empty lines
if l == "" {
// Skip invalid lines
if !po.isValidLine(l) {
continue
}
// Skip invalid lines
if !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") {
// Buffer context and continue
if strings.HasPrefix(l, "msgctxt") {
po.parseContext(l)
state = msgCtxt
continue
}
// Buffer msgid and continue
if strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") {
// Save current translation buffer.
po.Lock()
po.translations[tr.id] = tr
po.Unlock()
// Flush buffer
tr = newTranslation()
// Set id
tr.id, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
// Loop
po.parseID(l)
state = msgID
continue
}
// Check for plural form
if strings.HasPrefix(l, "msgid_plural") {
tr.pluralId, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
// Loop
po.parsePluralID(l)
state = msgIDPlural
continue
}
// Save translation
if strings.HasPrefix(l, "msgstr") {
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
// Check for indexed translation forms
if strings.HasPrefix(l, "[") {
in := strings.Index(l, "]")
if in == -1 {
// Skip wrong index formatting
po.parseMessage(l)
state = msgStr
continue
}
// Parse index
i, err := strconv.Atoi(l[1:in])
if err != nil {
// Skip wrong index formatting
// Multi line strings and headers
if strings.HasPrefix(l, "\"") && strings.HasSuffix(l, "\"") {
state = po.parseString(l, state)
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.
if tr.id != "" {
po.Lock()
po.translations[tr.id] = tr
po.Unlock()
po.saveBuffer()
// Parse headers
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, state parseState) parseState {
switch state {
case msgStr:
// 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
}
case msgID:
// Multiline msgid - Append to current id
uq, _ := strconv.Unquote(l)
po.trBuffer.id += uq
case msgIDPlural:
// Multiline msgid - Append to current id
uq, _ := strconv.Unquote(l)
po.trBuffer.pluralID += uq
case msgCtxt:
// Multiline context - Append to current context
ctxt, _ := strconv.Unquote(l)
po.ctxBuffer += ctxt
default:
// Otherwise is a header
h, _ := strconv.Unquote(strings.TrimSpace(l))
po.RawHeaders += h
return head
}
return state
}
// 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.
// 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 {
// Sync read
po.RLock()
defer po.RUnlock()
if po.translations != nil {
if _, ok := po.translations[str]; ok {
return fmt.Sprintf(po.translations[str].get(), vars...)
@@ -196,16 +430,66 @@ func (po *Po) Get(str string, vars ...interface{}) string {
return fmt.Sprintf(str, vars...)
}
// GetN retrieves the (N)th plural form translation for the given string.
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
// GetN retrieves the (N)th plural form of translation for the given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
// Sync read
po.RLock()
defer po.RUnlock()
if po.translations != nil {
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
if n == 1 {
return fmt.Sprintf(str, vars...)
}
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...)
}
}
}
}
if n == 1 {
return fmt.Sprintf(str, vars...)
}
return fmt.Sprintf(plural, vars...)
}

View File

@@ -7,6 +7,438 @@ import (
)
func TestPo(t *testing.T) {
// Set PO content
str := `
# 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 ""
# Multi-line msgid
msgid "multi"
"line"
"id"
msgstr "id with multiline content"
# Multi-line msgid_plural
msgid "multi"
"line"
"plural"
"id"
msgstr "plural id with multiline content"
#Multi-line string
msgid "Multi-line"
msgstr "Multi "
"line"
msgid "One with var: %s"
msgid_plural "Several with vars: %s"
msgstr[0] "This one is the singular: %s"
msgstr[1] "This one is the plural: %s"
msgstr[2] "And this is the second plural form: %s"
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 "Empty translation"
msgstr ""
msgid "Empty plural form singular"
msgid_plural "Empty plural form"
msgstr[0] "Singular translated"
msgstr[1] "
msgid "More"
msgstr "More translation"
"
`
// Write PO content to file
filename := path.Clean(os.TempDir() + string(os.PathSeparator) + "default.po")
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 po object
po := new(Po)
// Try to parse a directory
po.ParseFile(path.Clean(os.TempDir()))
// Parse file
po.ParseFile(filename)
// Test translations
tr := po.Get("My text")
if tr != "Translated text" {
t.Errorf("Expected 'Translated text' but got '%s'", tr)
}
v := "Variable"
tr = po.Get("One with var: %s", v)
if tr != "This one is the singular: Variable" {
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
}
// Test multi-line id
tr = po.Get("multilineid")
if tr != "id with multiline content" {
t.Errorf("Expected 'id with multiline content' but got '%s'", tr)
}
// Test multi-line plural id
tr = po.Get("multilinepluralid")
if tr != "plural id with multiline content" {
t.Errorf("Expected 'plural id with multiline content' but got '%s'", tr)
}
// Test multi-line
tr = po.Get("Multi-line")
if tr != "Multi line" {
t.Errorf("Expected 'Multi line' but got '%s'", tr)
}
// Test plural
tr = po.GetN("One with var: %s", "Several with vars: %s", 2, v)
if tr != "This one is the plural: Variable" {
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
}
// Test inexistent translations
tr = po.Get("This is a test")
if tr != "This is a test" {
t.Errorf("Expected 'This is a test' but got '%s'", tr)
}
tr = po.GetN("This is a test", "This are tests", 100)
if tr != "This are tests" {
t.Errorf("Expected 'This are tests' but got '%s'", tr)
}
// Test syntax error parsed translations
tr = po.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 = 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 default plural vs singular return responses
tr = po.GetN("Original", "Original plural", 4)
if tr != "Original plural" {
t.Errorf("Expected 'Original plural' but got '%s'", tr)
}
tr = po.GetN("Original", "Original plural", 1)
if tr != "Original" {
t.Errorf("Expected 'Original' but got '%s'", tr)
}
// Test empty translation strings
tr = po.Get("Empty translation")
if tr != "Empty translation" {
t.Errorf("Expected 'Empty translation' but got '%s'", tr)
}
tr = po.Get("Empty plural form singular")
if tr != "Singular translated" {
t.Errorf("Expected 'Singular translated' but got '%s'", tr)
}
tr = po.GetN("Empty plural form singular", "Empty plural form", 1)
if tr != "Singular translated" {
t.Errorf("Expected 'Singular translated' but got '%s'", tr)
}
tr = po.GetN("Empty plural form singular", "Empty plural form", 2)
if tr != "Empty plural form" {
t.Errorf("Expected 'Empty plural form' but got '%s'", tr)
}
// Test last translation
tr = po.Get("More")
if tr != "More translation" {
t.Errorf("Expected 'More translation' but got '%s'", tr)
}
}
func TestPoHeaders(t *testing.T) {
// Set PO content
str := `
# 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)
}
}
func TestPoRace(t *testing.T) {
// Set PO content
str := `# Some comment
msgid "My text"
@@ -23,39 +455,30 @@ msgstr[1] "This one is the plural: %s"
msgstr[2] "And this is the second plural form: %s"
`
// Write PO content to file
filename := path.Clean(os.TempDir() + string(os.PathSeparator) + "default.po")
f, err := os.Create(filename)
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
defer f.Close()
_, err = f.WriteString(str)
if err != nil {
t.Fatalf("Can't write to test file: %s", err.Error())
}
// Parse po file
// Create Po object
po := new(Po)
po.ParseFile(filename)
// Test translations
tr := po.Get("My text")
if tr != "Translated text" {
t.Errorf("Expected 'Translated text' but got '%s'", tr)
}
// Create sync channels
pc := make(chan bool)
rc := make(chan bool)
v := "Variable"
tr = po.Get("One with var: %s", v)
if tr != "This one is the singular: Variable" {
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
}
// Parse po content in a goroutine
go func(po *Po, done chan bool) {
po.Parse(str)
done <- true
}(po, pc)
// Test plural
tr = po.GetN("One with var: %s", "Several with vars: %s", 2, v)
if tr != "And this is the second plural form: Variable" {
t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr)
}
// Read some translation on a goroutine
go func(po *Po, done chan bool) {
po.Get("My text")
done <- true
}(po, rc)
// Read something at top level
po.Get("My text")
// Wait for goroutines to finish
<-pc
<-rc
}

104
vendor/github.com/mattn/kinako/ast/expr.go generated vendored Normal file
View File

@@ -0,0 +1,104 @@
package ast
type Token struct {
Tok int
Lit string
}
// Position provides interface to store code locations.
type Position struct {
Line int
Column int
}
// Expr provides all of interfaces for expression.
type Expr interface {
expr()
}
// ExprImpl provide commonly implementations for Expr.
type ExprImpl struct {
}
// expr provide restraint interface.
func (x *ExprImpl) expr() {}
// NumberExpr provide Number expression.
type NumberExpr struct {
ExprImpl
Lit string
}
// UnaryExpr provide unary minus expression. ex: -1, ^1, ~1.
type UnaryExpr struct {
ExprImpl
Operator string
Expr Expr
}
// IdentExpr provide identity expression.
type IdentExpr struct {
ExprImpl
Lit string
}
// Stmt provides all of interfaces for statement.
type Stmt interface {
stmt()
}
// StmtImpl provide commonly implementations for Stmt..
type StmtImpl struct {
}
// stmt provide restraint interface.
func (x *StmtImpl) stmt() {}
// LetsStmt provide multiple statement of let.
type LetsStmt struct {
StmtImpl
Lhss []Expr
Operator string
Rhss []Expr
}
// StringExpr provide String expression.
type StringExpr struct {
ExprImpl
Lit string
}
type TernaryOpExpr struct {
ExprImpl
Expr Expr
Lhs Expr
Rhs Expr
}
// ParenExpr provide parent block expression.
type ParenExpr struct {
ExprImpl
SubExpr Expr
}
// BinOpExpr provide binary operator expression.
type BinOpExpr struct {
ExprImpl
Lhs Expr
Operator string
Rhs Expr
}
// ExprStmt provide expression statement.
type ExprStmt struct {
StmtImpl
Expr Expr
}
// LetStmt provide statement of let.
type LetStmt struct {
StmtImpl
Lhs Expr
Operator string
Rhs Expr
}

4
vendor/github.com/mattn/kinako/parser/Makefile generated vendored Normal file
View File

@@ -0,0 +1,4 @@
all : parser.go
parser.go : parser.go.y
goyacc -o $@ parser.go.y

427
vendor/github.com/mattn/kinako/parser/lexer.go generated vendored Normal file
View File

@@ -0,0 +1,427 @@
package parser
import (
"errors"
"fmt"
"unicode"
"github.com/mattn/kinako/ast"
)
const (
EOF = -1 // End of file.
EOL = '\n' // End of line.
)
// Error provides a convenient interface for handling runtime error.
// It can be Error inteface with type cast which can call Pos().
type Error struct {
Message string
Filename string
Fatal bool
}
// Error returns the error message.
func (e *Error) Error() string {
return e.Message
}
// Scanner stores informations for lexer.
type Scanner struct {
src []rune
offset int
lineHead int
line int
}
// Init resets code to scan.
func (s *Scanner) Init(src string) {
s.src = []rune(src)
}
// Scan analyses token, and decide identify or literals.
func (s *Scanner) Scan() (tok int, lit string, pos ast.Position, err error) {
retry:
s.skipBlank()
pos = s.pos()
switch ch := s.peek(); {
case isLetter(ch):
tok = IDENT
lit, err = s.scanIdentifier()
if err != nil {
return
}
case isDigit(ch):
tok = NUMBER
lit, err = s.scanNumber()
if err != nil {
return
}
case ch == '"':
tok = STRING
lit, err = s.scanString('"')
if err != nil {
return
}
case ch == '\'':
tok = STRING
lit, err = s.scanString('\'')
if err != nil {
return
}
case ch == '`':
tok = STRING
lit, err = s.scanRawString()
if err != nil {
return
}
default:
switch ch {
case EOF:
tok = EOF
case '#':
for !isEOL(s.peek()) {
s.next()
}
goto retry
case '!':
s.next()
switch s.peek() {
case '=':
tok = NEQ
lit = "!="
default:
s.back()
tok = int(ch)
lit = string(ch)
}
case '=':
s.next()
switch s.peek() {
case '=':
tok = EQEQ
lit = "=="
default:
s.back()
tok = int(ch)
lit = string(ch)
}
case '+':
tok = int(ch)
lit = string(ch)
case '-':
tok = int(ch)
lit = string(ch)
case '*':
tok = int(ch)
lit = string(ch)
case '/':
tok = int(ch)
lit = string(ch)
case '>':
s.next()
switch s.peek() {
case '=':
tok = GE
lit = ">="
case '>':
tok = SHIFTRIGHT
lit = ">>"
default:
s.back()
tok = int(ch)
lit = string(ch)
}
case '<':
s.next()
switch s.peek() {
case '=':
tok = LE
lit = "<="
case '<':
tok = SHIFTLEFT
lit = "<<"
default:
s.back()
tok = int(ch)
lit = string(ch)
}
case '|':
s.next()
switch s.peek() {
case '|':
tok = OROR
lit = "||"
default:
s.back()
tok = int(ch)
lit = string(ch)
}
case '&':
s.next()
switch s.peek() {
case '&':
tok = ANDAND
lit = "&&"
default:
s.back()
tok = int(ch)
lit = string(ch)
}
case '.':
tok = int(ch)
lit = string(ch)
case '\n':
tok = int(ch)
lit = string(ch)
case '(', ')', ':', ';', '%', '?', '{', '}', ',', '[', ']', '^':
tok = int(ch)
lit = string(ch)
default:
err = fmt.Errorf(`syntax error "%s"`, string(ch))
tok = int(ch)
lit = string(ch)
return
}
s.next()
}
return
}
// isLetter returns true if the rune is a letter for identity.
func isLetter(ch rune) bool {
return unicode.IsLetter(ch) || ch == '_'
}
// isDigit returns true if the rune is a number.
func isDigit(ch rune) bool {
return '0' <= ch && ch <= '9'
}
// isHex returns true if the rune is a hex digits.
func isHex(ch rune) bool {
return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
}
// isEOL returns true if the rune is at end-of-line or end-of-file.
func isEOL(ch rune) bool {
return ch == '\n' || ch == -1
}
// isBlank returns true if the rune is empty character..
func isBlank(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\r'
}
// peek returns current rune in the code.
func (s *Scanner) peek() rune {
if s.reachEOF() {
return EOF
}
return s.src[s.offset]
}
// next moves offset to next.
func (s *Scanner) next() {
if !s.reachEOF() {
if s.peek() == '\n' {
s.lineHead = s.offset + 1
s.line++
}
s.offset++
}
}
// current returns the current offset.
func (s *Scanner) current() int {
return s.offset
}
// offset sets the offset value.
func (s *Scanner) set(o int) {
s.offset = o
}
// back moves back offset once to top.
func (s *Scanner) back() {
s.offset--
}
// reachEOF returns true if offset is at end-of-file.
func (s *Scanner) reachEOF() bool {
return len(s.src) <= s.offset
}
// pos returns the position of current.
func (s *Scanner) pos() ast.Position {
return ast.Position{Line: s.line + 1, Column: s.offset - s.lineHead + 1}
}
// skipBlank moves position into non-black character.
func (s *Scanner) skipBlank() {
for isBlank(s.peek()) {
s.next()
}
}
// scanIdentifier returns identifier begining at current position.
func (s *Scanner) scanIdentifier() (string, error) {
var ret []rune
for {
if !isLetter(s.peek()) && !isDigit(s.peek()) {
break
}
ret = append(ret, s.peek())
s.next()
}
return string(ret), nil
}
// scanNumber returns number begining at current position.
func (s *Scanner) scanNumber() (string, error) {
var ret []rune
ch := s.peek()
ret = append(ret, ch)
s.next()
if ch == '0' && s.peek() == 'x' {
ret = append(ret, s.peek())
s.next()
for isHex(s.peek()) {
ret = append(ret, s.peek())
s.next()
}
} else {
for isDigit(s.peek()) || s.peek() == '.' {
ret = append(ret, s.peek())
s.next()
}
if s.peek() == 'e' {
ret = append(ret, s.peek())
s.next()
if isDigit(s.peek()) || s.peek() == '+' || s.peek() == '-' {
ret = append(ret, s.peek())
s.next()
for isDigit(s.peek()) || s.peek() == '.' {
ret = append(ret, s.peek())
s.next()
}
}
for isDigit(s.peek()) || s.peek() == '.' {
ret = append(ret, s.peek())
s.next()
}
}
if isLetter(s.peek()) {
return "", errors.New("identifier starts immediately after numeric literal")
}
}
return string(ret), nil
}
// scanRawString returns raw-string starting at current position.
func (s *Scanner) scanRawString() (string, error) {
var ret []rune
for {
s.next()
if s.peek() == EOF {
return "", errors.New("unexpected EOF")
break
}
if s.peek() == '`' {
s.next()
break
}
ret = append(ret, s.peek())
}
return string(ret), nil
}
// scanString returns string starting at current position.
// This handles backslash escaping.
func (s *Scanner) scanString(l rune) (string, error) {
var ret []rune
eos:
for {
s.next()
switch s.peek() {
case EOL:
return "", errors.New("unexpected EOL")
case EOF:
return "", errors.New("unexpected EOF")
case l:
s.next()
break eos
case '\\':
s.next()
switch s.peek() {
case 'b':
ret = append(ret, '\b')
continue
case 'f':
ret = append(ret, '\f')
continue
case 'r':
ret = append(ret, '\r')
continue
case 'n':
ret = append(ret, '\n')
continue
case 't':
ret = append(ret, '\t')
continue
}
ret = append(ret, s.peek())
continue
default:
ret = append(ret, s.peek())
}
}
return string(ret), nil
}
// Lexer provides inteface to parse codes.
type Lexer struct {
s *Scanner
lit string
pos ast.Position
e error
stmts []ast.Stmt
}
// Lex scans the token and literals.
func (l *Lexer) Lex(lval *yySymType) int {
tok, lit, pos, err := l.s.Scan()
if err != nil {
l.e = &Error{Message: fmt.Sprintf("%s", err.Error()), Fatal: true}
}
lval.tok = ast.Token{Tok: tok, Lit: lit}
l.lit = lit
l.pos = pos
return tok
}
// Error sets parse error.
func (l *Lexer) Error(msg string) {
l.e = &Error{Message: msg, Fatal: false}
}
// Parser provides way to parse the code using Scanner.
func Parse(s *Scanner) ([]ast.Stmt, error) {
l := Lexer{s: s}
if yyParse(&l) != 0 {
return nil, l.e
}
return l.stmts, l.e
}
func EnableErrorVerbose() {
yyErrorVerbose = true
}
// ParserSrc provides way to parse the code from source.
func ParseSrc(src string) ([]ast.Stmt, error) {
scanner := &Scanner{
src: []rune(src),
}
return Parse(scanner)
}

752
vendor/github.com/mattn/kinako/parser/parser.go generated vendored Normal file
View File

@@ -0,0 +1,752 @@
//line parser.go.y:2
package parser
import __yyfmt__ "fmt"
//line parser.go.y:2
import (
"github.com/mattn/kinako/ast"
)
//line parser.go.y:15
type yySymType struct {
yys int
compstmt []ast.Stmt
stmts []ast.Stmt
stmt ast.Stmt
expr ast.Expr
tok ast.Token
term ast.Token
terms ast.Token
opt_terms ast.Token
}
const IDENT = 57346
const NUMBER = 57347
const STRING = 57348
const EQEQ = 57349
const NEQ = 57350
const GE = 57351
const LE = 57352
const OROR = 57353
const ANDAND = 57354
const POW = 57355
const SHIFTLEFT = 57356
const SHIFTRIGHT = 57357
const PLUSPLUS = 57358
const MINUSMINUS = 57359
const UNARY = 57360
var yyToknames = [...]string{
"$end",
"error",
"$unk",
"IDENT",
"NUMBER",
"STRING",
"EQEQ",
"NEQ",
"GE",
"LE",
"OROR",
"ANDAND",
"POW",
"'='",
"'?'",
"':'",
"','",
"'>'",
"'<'",
"SHIFTLEFT",
"SHIFTRIGHT",
"'+'",
"'-'",
"PLUSPLUS",
"MINUSMINUS",
"'*'",
"'/'",
"'%'",
"UNARY",
"'!'",
"'^'",
"'('",
"')'",
"'|'",
"'&'",
"';'",
"'\\n'",
}
var yyStatenames = [...]string{}
const yyEofCode = 1
const yyErrCode = 2
const yyInitialStackSize = 16
//line parser.go.y:194
//line yacctab:1
var yyExca = [...]int{
-1, 1,
1, -1,
-2, 0,
-1, 49,
7, 0,
8, 0,
-2, 20,
-1, 50,
7, 0,
8, 0,
-2, 21,
}
const yyNprod = 36
const yyPrivate = 57344
var yyTokenNames []string
var yyStates []string
const yyLast = 249
var yyAct = [...]int{
9, 6, 7, 33, 35, 4, 22, 23, 3, 18,
24, 25, 26, 37, 38, 39, 2, 40, 33, 35,
17, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, 54, 55, 56, 57, 58, 27, 28,
30, 32, 34, 36, 8, 1, 21, 60, 0, 29,
31, 0, 0, 22, 23, 5, 0, 24, 25, 26,
19, 61, 0, 41, 0, 33, 35, 27, 28, 30,
32, 34, 36, 0, 19, 21, 0, 0, 29, 31,
0, 0, 22, 23, 0, 0, 24, 25, 26, 0,
0, 0, 0, 59, 33, 35, 27, 28, 30, 32,
34, 36, 0, 20, 21, 0, 0, 29, 31, 0,
0, 22, 23, 0, 0, 24, 25, 26, 0, 0,
0, 0, 0, 33, 35, 27, 28, 30, 32, 34,
36, 0, 0, 21, 0, 0, 29, 31, 0, 0,
22, 23, 0, 0, 24, 25, 26, 0, 0, 0,
0, 0, 33, 35, 27, 28, 30, 32, 0, 36,
0, 0, 0, 0, 0, 29, 31, 0, 0, 22,
23, 0, 0, 24, 25, 26, 27, 28, 30, 32,
0, 33, 35, 0, 0, 0, 0, 29, 31, 0,
0, 22, 23, 0, 0, 24, 25, 26, 30, 32,
10, 11, 15, 33, 35, 0, 0, 29, 31, 0,
0, 22, 23, 0, 0, 24, 25, 26, 0, 12,
10, 11, 15, 33, 35, 0, 13, 14, 16, 24,
25, 26, 6, 7, 0, 0, 0, 33, 35, 12,
0, 0, 0, 0, 0, 0, 13, 14, 16,
}
var yyPact = [...]int{
-35, -1000, 216, -35, -35, -1000, -1000, -1000, -1000, 89,
-1000, -1000, 216, 216, 216, -1000, 216, -1000, 196, -1000,
216, 216, 216, 216, 216, 216, 216, 216, 216, 216,
216, 216, 216, 216, 216, 216, 216, -31, -31, -31,
60, -1000, 118, 31, 203, 203, -31, -31, -31, 189,
189, -16, -16, -16, -16, 118, 147, 118, 169, -1000,
216, 118,
}
var yyPgo = [...]int{
0, 45, 8, 44, 0, 16, 5, 55,
}
var yyR1 = [...]int{
0, 1, 1, 2, 2, 3, 3, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 6, 6, 7, 7,
}
var yyR2 = [...]int{
0, 1, 2, 2, 3, 3, 1, 1, 1, 2,
2, 2, 1, 5, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
0, 1, 1, 2, 1, 1,
}
var yyChk = [...]int{
-1000, -1, -5, -2, -6, -7, 36, 37, -3, -4,
4, 5, 23, 30, 31, 6, 32, -5, -6, -7,
14, 15, 22, 23, 26, 27, 28, 7, 8, 18,
9, 19, 10, 34, 11, 35, 12, -4, -4, -4,
-4, -3, -4, -4, -4, -4, -4, -4, -4, -4,
-4, -4, -4, -4, -4, -4, -4, -4, -4, 33,
16, -4,
}
var yyDef = [...]int{
30, -2, 1, 30, 31, 32, 34, 35, 3, 6,
7, 8, 0, 0, 0, 12, 0, 2, 31, 33,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 9, 10, 11,
0, 4, 5, 0, 15, 16, 17, 18, 19, -2,
-2, 22, 23, 24, 25, 26, 27, 28, 29, 14,
0, 13,
}
var yyTok1 = [...]int{
1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
37, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 30, 3, 3, 3, 28, 35, 3,
32, 33, 26, 22, 17, 23, 3, 27, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 16, 36,
19, 14, 18, 15, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 31, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 34,
}
var yyTok2 = [...]int{
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 20, 21, 24, 25, 29,
}
var yyTok3 = [...]int{
0,
}
var yyErrorMessages = [...]struct {
state int
token int
msg string
}{}
//line yaccpar:1
/* parser for yacc output */
var (
yyDebug = 0
yyErrorVerbose = false
)
type yyLexer interface {
Lex(lval *yySymType) int
Error(s string)
}
type yyParser interface {
Parse(yyLexer) int
Lookahead() int
}
type yyParserImpl struct {
lval yySymType
stack [yyInitialStackSize]yySymType
char int
}
func (p *yyParserImpl) Lookahead() int {
return p.char
}
func yyNewParser() yyParser {
return &yyParserImpl{}
}
const yyFlag = -1000
func yyTokname(c int) string {
if c >= 1 && c-1 < len(yyToknames) {
if yyToknames[c-1] != "" {
return yyToknames[c-1]
}
}
return __yyfmt__.Sprintf("tok-%v", c)
}
func yyStatname(s int) string {
if s >= 0 && s < len(yyStatenames) {
if yyStatenames[s] != "" {
return yyStatenames[s]
}
}
return __yyfmt__.Sprintf("state-%v", s)
}
func yyErrorMessage(state, lookAhead int) string {
const TOKSTART = 4
if !yyErrorVerbose {
return "syntax error"
}
for _, e := range yyErrorMessages {
if e.state == state && e.token == lookAhead {
return "syntax error: " + e.msg
}
}
res := "syntax error: unexpected " + yyTokname(lookAhead)
// To match Bison, suggest at most four expected tokens.
expected := make([]int, 0, 4)
// Look for shiftable tokens.
base := yyPact[state]
for tok := TOKSTART; tok-1 < len(yyToknames); tok++ {
if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok {
if len(expected) == cap(expected) {
return res
}
expected = append(expected, tok)
}
}
if yyDef[state] == -2 {
i := 0
for yyExca[i] != -1 || yyExca[i+1] != state {
i += 2
}
// Look for tokens that we accept or reduce.
for i += 2; yyExca[i] >= 0; i += 2 {
tok := yyExca[i]
if tok < TOKSTART || yyExca[i+1] == 0 {
continue
}
if len(expected) == cap(expected) {
return res
}
expected = append(expected, tok)
}
// If the default action is to accept or reduce, give up.
if yyExca[i+1] != 0 {
return res
}
}
for i, tok := range expected {
if i == 0 {
res += ", expecting "
} else {
res += " or "
}
res += yyTokname(tok)
}
return res
}
func yylex1(lex yyLexer, lval *yySymType) (char, token int) {
token = 0
char = lex.Lex(lval)
if char <= 0 {
token = yyTok1[0]
goto out
}
if char < len(yyTok1) {
token = yyTok1[char]
goto out
}
if char >= yyPrivate {
if char < yyPrivate+len(yyTok2) {
token = yyTok2[char-yyPrivate]
goto out
}
}
for i := 0; i < len(yyTok3); i += 2 {
token = yyTok3[i+0]
if token == char {
token = yyTok3[i+1]
goto out
}
}
out:
if token == 0 {
token = yyTok2[1] /* unknown char */
}
if yyDebug >= 3 {
__yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char))
}
return char, token
}
func yyParse(yylex yyLexer) int {
return yyNewParser().Parse(yylex)
}
func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int {
var yyn int
var yyVAL yySymType
var yyDollar []yySymType
_ = yyDollar // silence set and not used
yyS := yyrcvr.stack[:]
Nerrs := 0 /* number of errors */
Errflag := 0 /* error recovery flag */
yystate := 0
yyrcvr.char = -1
yytoken := -1 // yyrcvr.char translated into internal numbering
defer func() {
// Make sure we report no lookahead when not parsing.
yystate = -1
yyrcvr.char = -1
yytoken = -1
}()
yyp := -1
goto yystack
ret0:
return 0
ret1:
return 1
yystack:
/* put a state and value onto the stack */
if yyDebug >= 4 {
__yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate))
}
yyp++
if yyp >= len(yyS) {
nyys := make([]yySymType, len(yyS)*2)
copy(nyys, yyS)
yyS = nyys
}
yyS[yyp] = yyVAL
yyS[yyp].yys = yystate
yynewstate:
yyn = yyPact[yystate]
if yyn <= yyFlag {
goto yydefault /* simple state */
}
if yyrcvr.char < 0 {
yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)
}
yyn += yytoken
if yyn < 0 || yyn >= yyLast {
goto yydefault
}
yyn = yyAct[yyn]
if yyChk[yyn] == yytoken { /* valid shift */
yyrcvr.char = -1
yytoken = -1
yyVAL = yyrcvr.lval
yystate = yyn
if Errflag > 0 {
Errflag--
}
goto yystack
}
yydefault:
/* default state action */
yyn = yyDef[yystate]
if yyn == -2 {
if yyrcvr.char < 0 {
yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval)
}
/* look through exception table */
xi := 0
for {
if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate {
break
}
xi += 2
}
for xi += 2; ; xi += 2 {
yyn = yyExca[xi+0]
if yyn < 0 || yyn == yytoken {
break
}
}
yyn = yyExca[xi+1]
if yyn < 0 {
goto ret0
}
}
if yyn == 0 {
/* error ... attempt to resume parsing */
switch Errflag {
case 0: /* brand new error */
yylex.Error(yyErrorMessage(yystate, yytoken))
Nerrs++
if yyDebug >= 1 {
__yyfmt__.Printf("%s", yyStatname(yystate))
__yyfmt__.Printf(" saw %s\n", yyTokname(yytoken))
}
fallthrough
case 1, 2: /* incompletely recovered error ... try again */
Errflag = 3
/* find a state where "error" is a legal shift action */
for yyp >= 0 {
yyn = yyPact[yyS[yyp].yys] + yyErrCode
if yyn >= 0 && yyn < yyLast {
yystate = yyAct[yyn] /* simulate a shift of "error" */
if yyChk[yystate] == yyErrCode {
goto yystack
}
}
/* the current p has no shift on "error", pop stack */
if yyDebug >= 2 {
__yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys)
}
yyp--
}
/* there is no state on the stack with an error shift ... abort */
goto ret1
case 3: /* no shift yet; clobber input char */
if yyDebug >= 2 {
__yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken))
}
if yytoken == yyEofCode {
goto ret1
}
yyrcvr.char = -1
yytoken = -1
goto yynewstate /* try again in the same state */
}
}
/* reduction by production yyn */
if yyDebug >= 2 {
__yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate))
}
yynt := yyn
yypt := yyp
_ = yypt // guard against "declared and not used"
yyp -= yyR2[yyn]
// yyp is now the index of $0. Perform the default action. Iff the
// reduced production is ε, $1 is possibly out of range.
if yyp+1 >= len(yyS) {
nyys := make([]yySymType, len(yyS)*2)
copy(nyys, yyS)
yyS = nyys
}
yyVAL = yyS[yyp+1]
/* consult goto table to find next state */
yyn = yyR1[yyn]
yyg := yyPgo[yyn]
yyj := yyg + yyS[yyp].yys + 1
if yyj >= yyLast {
yystate = yyAct[yyg]
} else {
yystate = yyAct[yyj]
if yyChk[yystate] != -yyn {
yystate = yyAct[yyg]
}
}
// dummy call; replaced with literal code
switch yynt {
case 1:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:43
{
yyVAL.compstmt = nil
}
case 2:
yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:47
{
yyVAL.compstmt = yyDollar[1].stmts
}
case 3:
yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:53
{
yyVAL.stmts = []ast.Stmt{yyDollar[2].stmt}
if l, ok := yylex.(*Lexer); ok {
l.stmts = yyVAL.stmts
}
}
case 4:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:60
{
if yyDollar[3].stmt != nil {
yyVAL.stmts = append(yyDollar[1].stmts, yyDollar[3].stmt)
if l, ok := yylex.(*Lexer); ok {
l.stmts = yyVAL.stmts
}
}
}
case 5:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:71
{
yyVAL.stmt = &ast.LetStmt{Lhs: yyDollar[1].expr, Operator: "=", Rhs: yyDollar[3].expr}
}
case 6:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:75
{
yyVAL.stmt = &ast.ExprStmt{Expr: yyDollar[1].expr}
}
case 7:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:81
{
yyVAL.expr = &ast.IdentExpr{Lit: yyDollar[1].tok.Lit}
}
case 8:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:85
{
yyVAL.expr = &ast.NumberExpr{Lit: yyDollar[1].tok.Lit}
}
case 9:
yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:89
{
yyVAL.expr = &ast.UnaryExpr{Operator: "-", Expr: yyDollar[2].expr}
}
case 10:
yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:93
{
yyVAL.expr = &ast.UnaryExpr{Operator: "!", Expr: yyDollar[2].expr}
}
case 11:
yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:97
{
yyVAL.expr = &ast.UnaryExpr{Operator: "^", Expr: yyDollar[2].expr}
}
case 12:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:101
{
yyVAL.expr = &ast.StringExpr{Lit: yyDollar[1].tok.Lit}
}
case 13:
yyDollar = yyS[yypt-5 : yypt+1]
//line parser.go.y:105
{
yyVAL.expr = &ast.TernaryOpExpr{Expr: yyDollar[1].expr, Lhs: yyDollar[3].expr, Rhs: yyDollar[5].expr}
}
case 14:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:109
{
yyVAL.expr = &ast.ParenExpr{SubExpr: yyDollar[2].expr}
}
case 15:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:113
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "+", Rhs: yyDollar[3].expr}
}
case 16:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:117
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "-", Rhs: yyDollar[3].expr}
}
case 17:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:121
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "*", Rhs: yyDollar[3].expr}
}
case 18:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:125
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "/", Rhs: yyDollar[3].expr}
}
case 19:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:129
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "%", Rhs: yyDollar[3].expr}
}
case 20:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:133
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "==", Rhs: yyDollar[3].expr}
}
case 21:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:137
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "!=", Rhs: yyDollar[3].expr}
}
case 22:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:141
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">", Rhs: yyDollar[3].expr}
}
case 23:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:145
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">=", Rhs: yyDollar[3].expr}
}
case 24:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:149
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<", Rhs: yyDollar[3].expr}
}
case 25:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:153
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<=", Rhs: yyDollar[3].expr}
}
case 26:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:157
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "|", Rhs: yyDollar[3].expr}
}
case 27:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:161
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "||", Rhs: yyDollar[3].expr}
}
case 28:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:165
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&", Rhs: yyDollar[3].expr}
}
case 29:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:169
{
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&&", Rhs: yyDollar[3].expr}
}
case 32:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:179
{
}
case 33:
yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:182
{
}
case 34:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:187
{
}
case 35:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:190
{
}
}
goto yystack /* stack new state and value */
}

195
vendor/github.com/mattn/kinako/parser/parser.go.y generated vendored Normal file
View File

@@ -0,0 +1,195 @@
%{
package parser
import (
"github.com/mattn/kinako/ast"
)
%}
%type<compstmt> compstmt
%type<stmts> stmts
%type<stmt> stmt
%type<expr> expr
%union{
compstmt []ast.Stmt
stmts []ast.Stmt
stmt ast.Stmt
expr ast.Expr
tok ast.Token
term ast.Token
terms ast.Token
opt_terms ast.Token
}
%token<tok> IDENT NUMBER STRING EQEQ NEQ GE LE OROR ANDAND POW
%right '='
%right '?' ':'
%left OROR
%left ANDAND
%left IDENT
%nonassoc EQEQ NEQ ','
%left '>' GE '<' LE SHIFTLEFT SHIFTRIGHT
%left '+' '-' PLUSPLUS MINUSMINUS
%left '*' '/' '%'
%right UNARY
%%
compstmt : opt_terms
{
$$ = nil
}
| stmts opt_terms
{
$$ = $1
}
stmts :
opt_terms stmt
{
$$ = []ast.Stmt{$2}
if l, ok := yylex.(*Lexer); ok {
l.stmts = $$
}
}
| stmts terms stmt
{
if $3 != nil {
$$ = append($1, $3)
if l, ok := yylex.(*Lexer); ok {
l.stmts = $$
}
}
}
stmt :
expr '=' expr
{
$$ = &ast.LetStmt{Lhs: $1, Operator: "=", Rhs: $3}
}
| expr
{
$$ = &ast.ExprStmt{Expr: $1}
}
expr :
IDENT
{
$$ = &ast.IdentExpr{Lit: $1.Lit}
}
| NUMBER
{
$$ = &ast.NumberExpr{Lit: $1.Lit}
}
| '-' expr %prec UNARY
{
$$ = &ast.UnaryExpr{Operator: "-", Expr: $2}
}
| '!' expr %prec UNARY
{
$$ = &ast.UnaryExpr{Operator: "!", Expr: $2}
}
| '^' expr %prec UNARY
{
$$ = &ast.UnaryExpr{Operator: "^", Expr: $2}
}
| STRING
{
$$ = &ast.StringExpr{Lit: $1.Lit}
}
| expr '?' expr ':' expr
{
$$ = &ast.TernaryOpExpr{Expr: $1, Lhs: $3, Rhs: $5}
}
| '(' expr ')'
{
$$ = &ast.ParenExpr{SubExpr: $2}
}
| expr '+' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "+", Rhs: $3}
}
| expr '-' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "-", Rhs: $3}
}
| expr '*' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "*", Rhs: $3}
}
| expr '/' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "/", Rhs: $3}
}
| expr '%' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "%", Rhs: $3}
}
| expr EQEQ expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "==", Rhs: $3}
}
| expr NEQ expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "!=", Rhs: $3}
}
| expr '>' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">", Rhs: $3}
}
| expr GE expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: ">=", Rhs: $3}
}
| expr '<' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<", Rhs: $3}
}
| expr LE expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "<=", Rhs: $3}
}
| expr '|' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "|", Rhs: $3}
}
| expr OROR expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "||", Rhs: $3}
}
| expr '&' expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&", Rhs: $3}
}
| expr ANDAND expr
{
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&&", Rhs: $3}
}
opt_terms : /* none */
| terms
;
terms : term
{
}
| terms term
{
}
;
term : ';'
{
}
| '\n'
{
}
;
%%

1258
vendor/github.com/mattn/kinako/parser/y.output generated vendored Normal file

File diff suppressed because it is too large Load Diff

258
vendor/github.com/mattn/kinako/vm/env.go generated vendored Normal file
View File

@@ -0,0 +1,258 @@
package vm
import (
"fmt"
"reflect"
"strings"
"sync"
"github.com/mattn/kinako/parser"
)
// Env provides interface to run VM. This mean function scope and blocked-scope.
// If stack goes to blocked-scope, it will make new Env.
type Env struct {
name string
env map[string]reflect.Value
typ map[string]reflect.Type
parent *Env
interrupt *bool
sync.RWMutex
}
// NewEnv creates new global scope.
func NewEnv() *Env {
b := false
return &Env{
env: make(map[string]reflect.Value),
typ: make(map[string]reflect.Type),
parent: nil,
interrupt: &b,
}
}
// NewEnv creates new child scope.
func (e *Env) NewEnv() *Env {
return &Env{
env: make(map[string]reflect.Value),
typ: make(map[string]reflect.Type),
parent: e,
name: e.name,
interrupt: e.interrupt,
}
}
func NewPackage(n string) *Env {
b := false
return &Env{
env: make(map[string]reflect.Value),
typ: make(map[string]reflect.Type),
parent: nil,
name: n,
interrupt: &b,
}
}
func (e *Env) NewPackage(n string) *Env {
return &Env{
env: make(map[string]reflect.Value),
typ: make(map[string]reflect.Type),
parent: e,
name: n,
interrupt: e.interrupt,
}
}
// Destroy deletes current scope.
func (e *Env) Destroy() {
e.Lock()
defer e.Unlock()
if e.parent == nil {
return
}
for k, v := range e.parent.env {
if v.IsValid() && v.Interface() == e {
delete(e.parent.env, k)
}
}
e.parent = nil
e.env = nil
}
// NewModule creates new module scope as global.
func (e *Env) NewModule(n string) *Env {
m := &Env{
env: make(map[string]reflect.Value),
parent: e,
name: n,
}
e.Define(n, m)
return m
}
// SetName sets a name of the scope. This means that the scope is module.
func (e *Env) SetName(n string) {
e.Lock()
e.name = n
e.Unlock()
}
// GetName returns module name.
func (e *Env) GetName() string {
e.RLock()
defer e.RUnlock()
return e.name
}
// Addr returns pointer value which specified symbol. It goes to upper scope until
// found or returns error.
func (e *Env) Addr(k string) (reflect.Value, error) {
e.RLock()
defer e.RUnlock()
if v, ok := e.env[k]; ok {
return v.Addr(), nil
}
if e.parent == nil {
return NilValue, fmt.Errorf("Undefined symbol '%s'", k)
}
return e.parent.Addr(k)
}
// Type returns type which specified symbol. It goes to upper scope until
// found or returns error.
func (e *Env) Type(k string) (reflect.Type, error) {
e.RLock()
defer e.RUnlock()
if v, ok := e.typ[k]; ok {
return v, nil
}
if e.parent == nil {
return NilType, fmt.Errorf("Undefined type '%s'", k)
}
return e.parent.Type(k)
}
// Get returns value which specified symbol. It goes to upper scope until
// found or returns error.
func (e *Env) Get(k string) (reflect.Value, error) {
e.RLock()
defer e.RUnlock()
if v, ok := e.env[k]; ok {
return v, nil
}
if e.parent == nil {
return NilValue, fmt.Errorf("Undefined symbol '%s'", k)
}
return e.parent.Get(k)
}
// Set modifies value which specified as symbol. It goes to upper scope until
// found or returns error.
func (e *Env) Set(k string, v interface{}) error {
e.Lock()
defer e.Unlock()
if _, ok := e.env[k]; ok {
val, ok := v.(reflect.Value)
if !ok {
val = reflect.ValueOf(v)
}
e.env[k] = val
return nil
}
if e.parent == nil {
return fmt.Errorf("Unknown symbol '%s'", k)
}
return e.parent.Set(k, v)
}
// DefineGlobal defines symbol in global scope.
func (e *Env) DefineGlobal(k string, v interface{}) error {
if e.parent == nil {
return e.Define(k, v)
}
return e.parent.DefineGlobal(k, v)
}
// DefineType defines type which specifis symbol in global scope.
func (e *Env) DefineType(k string, t interface{}) error {
if strings.Contains(k, ".") {
return fmt.Errorf("Unknown symbol '%s'", k)
}
global := e
keys := []string{k}
e.RLock()
for global.parent != nil {
if global.name != "" {
keys = append(keys, global.name)
}
global = global.parent
}
e.RUnlock()
for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
keys[i], keys[j] = keys[j], keys[i]
}
typ, ok := t.(reflect.Type)
if !ok {
typ = reflect.TypeOf(t)
}
global.Lock()
global.typ[strings.Join(keys, ".")] = typ
global.Unlock()
return nil
}
// Define defines symbol in current scope.
func (e *Env) Define(k string, v interface{}) error {
if strings.Contains(k, ".") {
return fmt.Errorf("Unknown symbol '%s'", k)
}
val, ok := v.(reflect.Value)
if !ok {
val = reflect.ValueOf(v)
}
e.Lock()
e.env[k] = val
e.Unlock()
return nil
}
// String return the name of current scope.
func (e *Env) String() string {
e.RLock()
defer e.RUnlock()
return e.name
}
// Dump show symbol values in the scope.
func (e *Env) Dump() {
e.RLock()
for k, v := range e.env {
fmt.Printf("%v = %#v\n", k, v)
}
e.RUnlock()
}
// Execute parses and runs source in current scope.
func (e *Env) Execute(src string) (reflect.Value, error) {
stmts, err := parser.ParseSrc(src)
if err != nil {
return NilValue, err
}
return Run(stmts, e)
}

409
vendor/github.com/mattn/kinako/vm/vm.go generated vendored Normal file
View File

@@ -0,0 +1,409 @@
package vm
import (
"errors"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"github.com/mattn/kinako/ast"
)
var (
NilValue = reflect.ValueOf((*interface{})(nil))
NilType = reflect.TypeOf((*interface{})(nil))
TrueValue = reflect.ValueOf(true)
FalseValue = reflect.ValueOf(false)
)
// Error provides a convenient interface for handling runtime error.
// It can be Error interface with type cast which can call Pos().
type Error struct {
Message string
}
var (
BreakError = errors.New("Unexpected break statement")
ContinueError = errors.New("Unexpected continue statement")
ReturnError = errors.New("Unexpected return statement")
InterruptError = errors.New("Execution interrupted")
)
// Error returns the error message.
func (e *Error) Error() string {
return e.Message
}
// Func is function interface to reflect functions internaly.
type Func func(args ...reflect.Value) (reflect.Value, error)
func (f Func) String() string {
return fmt.Sprintf("[Func: %p]", f)
}
func ToFunc(f Func) reflect.Value {
return reflect.ValueOf(f)
}
// Run executes statements in the specified environment.
func Run(stmts []ast.Stmt, env *Env) (reflect.Value, error) {
rv := NilValue
var err error
for _, stmt := range stmts {
rv, err = RunSingleStmt(stmt, env)
if err != nil {
return rv, err
}
}
return rv, nil
}
// Interrupts the execution of any running statements in the specified environment.
//
// Note that the execution is not instantly aborted: after a call to Interrupt,
// the current running statement will finish, but the next statement will not run,
// and instead will return a NilValue and an InterruptError.
func Interrupt(env *Env) {
env.Lock()
*(env.interrupt) = true
env.Unlock()
}
// RunSingleStmt executes one statement in the specified environment.
func RunSingleStmt(stmt ast.Stmt, env *Env) (reflect.Value, error) {
env.Lock()
if *(env.interrupt) {
*(env.interrupt) = false
env.Unlock()
return NilValue, InterruptError
}
env.Unlock()
switch stmt := stmt.(type) {
case *ast.ExprStmt:
rv, err := invokeExpr(stmt.Expr, env)
if err != nil {
return rv, err
}
return rv, nil
case *ast.LetStmt:
rv := NilValue
var err error
rv, err = invokeExpr(stmt.Rhs, env)
if err != nil {
return rv, err
}
_, err = invokeLetExpr(stmt.Lhs, rv, env)
if err != nil {
return rv, err
}
return rv, nil
default:
return NilValue, errors.New("unknown statement")
}
}
// toString converts all reflect.Value-s into string.
func toString(v reflect.Value) string {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
if v.Kind() == reflect.String {
return v.String()
}
if !v.IsValid() {
return "nil"
}
return fmt.Sprint(v.Interface())
}
// toBool converts all reflect.Value-s into bool.
func toBool(v reflect.Value) bool {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
switch v.Kind() {
case reflect.Float32, reflect.Float64:
return v.Float() != 0.0
case reflect.Int, reflect.Int32, reflect.Int64:
return v.Int() != 0
case reflect.Bool:
return v.Bool()
case reflect.String:
if v.String() == "true" {
return true
}
if toInt64(v) != 0 {
return true
}
}
return false
}
// toFloat64 converts all reflect.Value-s into float64.
func toFloat64(v reflect.Value) float64 {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
switch v.Kind() {
case reflect.Float32, reflect.Float64:
return v.Float()
case reflect.Int, reflect.Int32, reflect.Int64:
return float64(v.Int())
}
return 0.0
}
func isNil(v reflect.Value) bool {
if !v.IsValid() || v.Kind().String() == "unsafe.Pointer" {
return true
}
if (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.IsNil() {
return true
}
return false
}
func isNum(v reflect.Value) bool {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64:
return true
}
return false
}
// equal returns true when lhsV and rhsV is same value.
func equal(lhsV, rhsV reflect.Value) bool {
lhsIsNil, rhsIsNil := isNil(lhsV), isNil(rhsV)
if lhsIsNil && rhsIsNil {
return true
}
if (!lhsIsNil && rhsIsNil) || (lhsIsNil && !rhsIsNil) {
return false
}
if lhsV.Kind() == reflect.Interface || lhsV.Kind() == reflect.Ptr {
lhsV = lhsV.Elem()
}
if rhsV.Kind() == reflect.Interface || rhsV.Kind() == reflect.Ptr {
rhsV = rhsV.Elem()
}
if !lhsV.IsValid() || !rhsV.IsValid() {
return true
}
if isNum(lhsV) && isNum(rhsV) {
if rhsV.Type().ConvertibleTo(lhsV.Type()) {
rhsV = rhsV.Convert(lhsV.Type())
}
}
if lhsV.CanInterface() && rhsV.CanInterface() {
return reflect.DeepEqual(lhsV.Interface(), rhsV.Interface())
}
return reflect.DeepEqual(lhsV, rhsV)
}
// toInt64 converts all reflect.Value-s into int64.
func toInt64(v reflect.Value) int64 {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
switch v.Kind() {
case reflect.Float32, reflect.Float64:
return int64(v.Float())
case reflect.Int, reflect.Int32, reflect.Int64:
return v.Int()
case reflect.String:
s := v.String()
var i int64
var err error
if strings.HasPrefix(s, "0x") {
i, err = strconv.ParseInt(s, 16, 64)
} else {
i, err = strconv.ParseInt(s, 10, 64)
}
if err == nil {
return int64(i)
}
}
return 0
}
func invokeLetExpr(expr ast.Expr, rv reflect.Value, env *Env) (reflect.Value, error) {
switch lhs := expr.(type) {
case *ast.IdentExpr:
if env.Set(lhs.Lit, rv) != nil {
if strings.Contains(lhs.Lit, ".") {
return NilValue, fmt.Errorf("Undefined symbol '%s'", lhs.Lit)
}
env.Define(lhs.Lit, rv)
}
return rv, nil
}
return NilValue, errors.New("Invalid operation")
}
// invokeExpr evaluates one expression.
func invokeExpr(expr ast.Expr, env *Env) (reflect.Value, error) {
switch e := expr.(type) {
case *ast.NumberExpr:
if strings.Contains(e.Lit, ".") || strings.Contains(e.Lit, "e") {
v, err := strconv.ParseFloat(e.Lit, 64)
if err != nil {
return NilValue, err
}
return reflect.ValueOf(float64(v)), nil
}
var i int64
var err error
if strings.HasPrefix(e.Lit, "0x") {
i, err = strconv.ParseInt(e.Lit[2:], 16, 64)
} else {
i, err = strconv.ParseInt(e.Lit, 10, 64)
}
if err != nil {
return NilValue, err
}
return reflect.ValueOf(i), nil
case *ast.IdentExpr:
return env.Get(e.Lit)
case *ast.StringExpr:
return reflect.ValueOf(e.Lit), nil
case *ast.UnaryExpr:
v, err := invokeExpr(e.Expr, env)
if err != nil {
return v, err
}
switch e.Operator {
case "-":
if v.Kind() == reflect.Float64 {
return reflect.ValueOf(-v.Float()), nil
}
return reflect.ValueOf(-v.Int()), nil
case "^":
return reflect.ValueOf(^toInt64(v)), nil
case "!":
return reflect.ValueOf(!toBool(v)), nil
default:
return NilValue, errors.New("Unknown operator ''")
}
case *ast.ParenExpr:
v, err := invokeExpr(e.SubExpr, env)
if err != nil {
return v, err
}
return v, nil
case *ast.BinOpExpr:
lhsV := NilValue
rhsV := NilValue
var err error
lhsV, err = invokeExpr(e.Lhs, env)
if err != nil {
return lhsV, err
}
if lhsV.Kind() == reflect.Interface {
lhsV = lhsV.Elem()
}
if e.Rhs != nil {
rhsV, err = invokeExpr(e.Rhs, env)
if err != nil {
return rhsV, err
}
if rhsV.Kind() == reflect.Interface {
rhsV = rhsV.Elem()
}
}
switch e.Operator {
case "+":
if lhsV.Kind() == reflect.String || rhsV.Kind() == reflect.String {
return reflect.ValueOf(toString(lhsV) + toString(rhsV)), nil
}
if (lhsV.Kind() == reflect.Array || lhsV.Kind() == reflect.Slice) && (rhsV.Kind() != reflect.Array && rhsV.Kind() != reflect.Slice) {
return reflect.Append(lhsV, rhsV), nil
}
if (lhsV.Kind() == reflect.Array || lhsV.Kind() == reflect.Slice) && (rhsV.Kind() == reflect.Array || rhsV.Kind() == reflect.Slice) {
return reflect.AppendSlice(lhsV, rhsV), nil
}
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
return reflect.ValueOf(toFloat64(lhsV) + toFloat64(rhsV)), nil
}
return reflect.ValueOf(toInt64(lhsV) + toInt64(rhsV)), nil
case "-":
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
return reflect.ValueOf(toFloat64(lhsV) - toFloat64(rhsV)), nil
}
return reflect.ValueOf(toInt64(lhsV) - toInt64(rhsV)), nil
case "*":
if lhsV.Kind() == reflect.String && (rhsV.Kind() == reflect.Int || rhsV.Kind() == reflect.Int32 || rhsV.Kind() == reflect.Int64) {
return reflect.ValueOf(strings.Repeat(toString(lhsV), int(toInt64(rhsV)))), nil
}
if lhsV.Kind() == reflect.Float64 || rhsV.Kind() == reflect.Float64 {
return reflect.ValueOf(toFloat64(lhsV) * toFloat64(rhsV)), nil
}
return reflect.ValueOf(toInt64(lhsV) * toInt64(rhsV)), nil
case "/":
return reflect.ValueOf(toFloat64(lhsV) / toFloat64(rhsV)), nil
case "%":
return reflect.ValueOf(toInt64(lhsV) % toInt64(rhsV)), nil
case "==":
return reflect.ValueOf(equal(lhsV, rhsV)), nil
case "!=":
return reflect.ValueOf(equal(lhsV, rhsV) == false), nil
case ">":
return reflect.ValueOf(toFloat64(lhsV) > toFloat64(rhsV)), nil
case ">=":
return reflect.ValueOf(toFloat64(lhsV) >= toFloat64(rhsV)), nil
case "<":
return reflect.ValueOf(toFloat64(lhsV) < toFloat64(rhsV)), nil
case "<=":
return reflect.ValueOf(toFloat64(lhsV) <= toFloat64(rhsV)), nil
case "|":
return reflect.ValueOf(toInt64(lhsV) | toInt64(rhsV)), nil
case "||":
if toBool(lhsV) {
return lhsV, nil
}
return rhsV, nil
case "&":
return reflect.ValueOf(toInt64(lhsV) & toInt64(rhsV)), nil
case "&&":
if toBool(lhsV) {
return rhsV, nil
}
return lhsV, nil
case "**":
if lhsV.Kind() == reflect.Float64 {
return reflect.ValueOf(math.Pow(toFloat64(lhsV), toFloat64(rhsV))), nil
}
return reflect.ValueOf(int64(math.Pow(toFloat64(lhsV), toFloat64(rhsV)))), nil
case ">>":
return reflect.ValueOf(toInt64(lhsV) >> uint64(toInt64(rhsV))), nil
case "<<":
return reflect.ValueOf(toInt64(lhsV) << uint64(toInt64(rhsV))), nil
default:
return NilValue, errors.New("Unknown operator")
}
case *ast.TernaryOpExpr:
rv, err := invokeExpr(e.Expr, env)
if err != nil {
return rv, err
}
if toBool(rv) {
lhsV, err := invokeExpr(e.Lhs, env)
if err != nil {
return lhsV, err
}
return lhsV, nil
}
rhsV, err := invokeExpr(e.Rhs, env)
if err != nil {
return rhsV, err
}
return rhsV, nil
default:
return NilValue, errors.New("Unknown expression")
}
}