13 Commits

Author SHA1 Message Date
Leonel Quinteros
c583d0991b Fix GetN and GetNC to honor package domain. Refactor global package functions to make them all concurrent safe. Fixes #14 2018-02-13 17:35:07 -03:00
Leonel Quinteros
4d0fbfd720 Update PULL_REQUEST_TEMPLATE.md 2018-02-13 15:34:25 -03:00
Leonel Quinteros
d0a0759bca Create ISSUE_TEMPLATE.md 2018-02-13 15:27:02 -03:00
Leonel Quinteros
972f5685e7 Create PULL_REQUEST_TEMPLATE.md 2018-02-13 15:21:25 -03:00
Leonel Quinteros
36b0166de9 Create CONTRIBUTING.md 2017-11-16 18:50:32 -03:00
Leonel Quinteros
b44b8cf3f4 Merge pull request #12 from leonelquinteros/add-code-of-conduct-1
Create CODE_OF_CONDUCT.md
2017-11-16 18:45:56 -03:00
Leonel Quinteros
3f452f14f2 Create CODE_OF_CONDUCT.md 2017-11-16 18:45:37 -03:00
Leonel Quinteros
7d86bb66fe Unify fmt.Sprintf behaviour on Po and Locale 2017-11-02 10:21:20 -03:00
Leonel Quinteros
50cdb4e058 Add Go 1.9 to travis builds 2017-10-26 16:27:26 -03:00
Leonel Quinteros
1fc8dec04d Rewrite PO headers parsing and handling. Implement correct GNU gettext headers format. Fix tests. Fixes #10 2017-09-08 18:08:56 -03:00
Leonel Quinteros
1bb93891f4 Fix README examples 2017-09-01 17:15:49 -03:00
Leonel Quinteros
1e28907f7a Replace Godeps with dep 2017-09-01 17:05:40 -03:00
Leonel Quinteros
4b94e83723 Properly handle singular vs plural defaults for untranslated strings. Fixes #9 2017-09-01 13:28:51 -03:00
25 changed files with 1280 additions and 489 deletions

32
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,32 @@
# Please describe your issue
## Is this a bug, an improvement, a proposal or something else?
- [ ] Bug
- [ ] Improvement
- [ ] Proposal
- [ ] Something else
## Briefly explain your issue
...
## What's the expected behaviour?
...
## What's the actual behaviour?
...
## What are the steps to reproduce the actual behaviour?
...
## Comments

31
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,31 @@
# Before creating your Pull Request...
- New Pull Requests should include a good description of what's being merged.
- Ideally, all Pull Requests are preceded by a discussion initiated in an Issue on this repository.
- For bug fixes is mandatory to have tests that cover and fail when the bug is present and will pass after this Pull Request.
- For changes and improvements, new tests have to be provided to cover the new features.
## What does this change implement/fix?
... *answer here*
## Is this a fix or an improvement?
- [ ] Fix
- [ ] Improvement
## Have discussed this change in an issue?
- [ ] Yes
- [ ] No
## Was some test failing because of this issue or change needed?
- [ ] Yes
- [ ] No
## Are you including tests to cover this change?
- [ ] Yes
- [ ] No

View File

@@ -4,6 +4,7 @@ go:
- 1.6 - 1.6
- 1.7 - 1.7
- 1.8 - 1.8
- 1.9
- tip - tip
before_install: before_install:

46
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at leonel.quinteros@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

19
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,19 @@
# CONTRIBUTING
This open source project welcomes everybody that wants to contribute to it by implementing new features, fixing bugs, testing, creating documentation or simply talk about it.
Most contributions will start by creating a new Issue to discuss what is the contribution about and to agree on the steps to move forward.
## Issues
All issues reports are welcome. Open a new Issue whenever you want to report a bug, request a change or make a proposal.
This should be your start point of contribution.
## Pull Requests
If you have any changes that can be merged, feel free to send a Pull Request.
Usually, you'd want to create a new Issue to discuss about the change you want to merge and why it's needed or what it solves.

18
Godeps/Godeps.json generated
View File

@@ -1,18 +0,0 @@
{
"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
View File

@@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

15
Gopkg.lock generated Normal file
View File

@@ -0,0 +1,15 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/mattn/kinako"
packages = ["ast","parser","vm"]
revision = "332c0a7e205a29536e672337a4bea6c7a96b04c1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "d3069fabe2d6f79fe33ad88133e861db84aef0400f6b949c4e64395913b3ae97"
solver-name = "gps-cdcl"
solver-version = 1

26
Gopkg.toml Normal file
View File

@@ -0,0 +1,26 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/mattn/kinako"

View File

@@ -116,39 +116,6 @@ A library directory structure can look like:
And so on... And so on...
# About translation function names
The standard GNU gettext defines helper functions that maps to the `gettext()` function and it's widely adopted by most implementations.
The basic translation function is usually `_()` in the form:
```
_("Translate this")
```
In Go, this can't be implemented by a reusable package as the function name has to start with a capital letter in order to be exported.
Each implementation of this package can declare this helper functions inside their own packages if this function naming are desired/needed:
```go
package main
import "github.com/leonelquinteros/gotext"
func _(str string, vars ...interface{}) string {
return gotext.Get(str, vars...)
}
```
This is valid and can be used within a package.
In normal conditions the Go compiler will optimize the calls to `_()` by replacing its content in place of the function call to reduce the function calling overhead.
This is a normal Go compiler behavior.
# Usage examples # Usage examples
## Using package for single language/domain settings ## Using package for single language/domain settings
@@ -156,17 +123,20 @@ This is a normal Go compiler behavior.
For quick/simple translations you can use the package level functions directly. For quick/simple translations you can use the package level functions directly.
```go ```go
import "github.com/leonelquinteros/gotext" import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() { func main() {
// Configure package // Configure package
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name") gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
// Translate text from default domain // 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 // 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"))
} }
``` ```
@@ -177,7 +147,10 @@ All translation strings support dynamic variables to be inserted without transla
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string. Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
```go ```go
import "github.com/leonelquinteros/gotext" import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() { func main() {
// Configure package // Configure package
@@ -187,7 +160,7 @@ func main() {
name := "John" name := "John"
// Translate text with variables // Translate text with variables
println(gotext.Get("Hi, my name is %s", name)) fmt.Println(gotext.Get("Hi, my name is %s", name))
} }
``` ```
@@ -199,7 +172,10 @@ When having multiple languages/domains/libraries at the same time, you can creat
so you can handle each settings on their own. so you can handle each settings on their own.
```go ```go
import "github.com/leonelquinteros/gotext" import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() { func main() {
// Create Locale with library path and language code // Create Locale with library path and language code
@@ -209,13 +185,13 @@ func main() {
l.AddDomain("default") l.AddDomain("default")
// Translate text from default domain // Translate text from default domain
println(l.Get("Translate this")) fmt.Println(l.Get("Translate this"))
// Load different domain // Load different domain
l.AddDomain("translations") l.AddDomain("translations")
// Translate text from domain // Translate text from domain
println(l.GetD("translations", "Translate this")) fmt.Println(l.GetD("translations", "Translate this"))
} }
``` ```
@@ -233,7 +209,10 @@ For when you need to work with PO files and strings,
you can directly use the Po object to parse it and access the translations in there in the same way. you can directly use the Po object to parse it and access the translations in there in the same way.
```go ```go
import "github.com/leonelquinteros/gotext" import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() { func main() {
// Set PO content // Set PO content
@@ -249,10 +228,10 @@ msgstr "This one sets the var: %s"
` `
// Create Po object // Create Po object
po := new(Po) po := new(gotext.Po)
po.Parse(str) po.Parse(str)
println(po.Get("Translate this")) fmt.Println(po.Get("Translate this"))
} }
``` ```
@@ -266,7 +245,10 @@ as defined in (https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_no
Plural formulas are parsed and evaluated using [Kinako](https://github.com/mattn/kinako) Plural formulas are parsed and evaluated using [Kinako](https://github.com/mattn/kinako)
```go ```go
import "github.com/leonelquinteros/gotext" import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() { func main() {
// Set PO content // Set PO content
@@ -290,10 +272,10 @@ msgstr[1] "This one is the plural: %s"
` `
// Create Po object // Create Po object
po := new(Po) po := new(gotext.Po)
po.Parse(str) po.Parse(str)
println(po.GetN("One with var: %s", "Several with vars: %s", 54, v)) fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
// "This one is the plural: Variable" // "This one is the plural: Variable"
} }
``` ```

126
gotext.go
View File

@@ -3,82 +3,129 @@ Package gotext implements GNU gettext utilities.
For quick/simple translations you can use the package level functions directly. For quick/simple translations you can use the package level functions directly.
import "github.com/leonelquinteros/gotext" import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() { func main() {
// Configure package // Configure package
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name") gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
// Translate text from default domain // 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 // Translate text from a different domain without reconfigure
println(gotext.GetD("domain2", "Another text on a different domain")) fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
} }
*/ */
package gotext package gotext
import (
"fmt"
"sync"
)
// Global environment variables // Global environment variables
var ( type config struct {
sync.RWMutex
// Default domain to look at when no domain is specified. Used by package level functions. // Default domain to look at when no domain is specified. Used by package level functions.
domain = "default" domain string
// Language set. // Language set.
language = "en_US" language string
// Path to library directory where all locale directories and translation files are. // Path to library directory where all locale directories and translation files are.
library = "/usr/local/share/locale" library string
// Storage for package level methods // Storage for package level methods
storage *Locale storage *Locale
) }
var globalConfig *config
// Init default configuration
func init() {
globalConfig = &config{
domain: "default",
language: "en_US",
library: "/usr/local/share/locale",
storage: nil,
}
}
// loadStorage creates a new Locale object at package level based on the Global variables settings. // loadStorage creates a new Locale object at package level based on the Global variables settings.
// It's called automatically when trying to use Get or GetD methods. // It's called automatically when trying to use Get or GetD methods.
func loadStorage(force bool) { func loadStorage(force bool) {
if storage == nil || force { globalConfig.Lock()
storage = NewLocale(library, language)
if globalConfig.storage == nil || force {
globalConfig.storage = NewLocale(globalConfig.library, globalConfig.language)
} }
if _, ok := storage.domains[domain]; !ok || force { if _, ok := globalConfig.storage.domains[globalConfig.domain]; !ok || force {
storage.AddDomain(domain) globalConfig.storage.AddDomain(globalConfig.domain)
} }
globalConfig.Unlock()
} }
// GetDomain is the domain getter for the package configuration // GetDomain is the domain getter for the package configuration
func GetDomain() string { func GetDomain() string {
return domain globalConfig.RLock()
dom := globalConfig.domain
globalConfig.RUnlock()
return dom
} }
// SetDomain sets the name for the domain to be used at package level. // SetDomain sets the name for the domain to be used at package level.
// It reloads the corresponding translation file. // It reloads the corresponding translation file.
func SetDomain(dom string) { func SetDomain(dom string) {
domain = dom globalConfig.Lock()
globalConfig.domain = dom
globalConfig.Unlock()
loadStorage(true) loadStorage(true)
} }
// GetLanguage is the language getter for the package configuration // GetLanguage is the language getter for the package configuration
func GetLanguage() string { func GetLanguage() string {
return language globalConfig.RLock()
lang := globalConfig.language
globalConfig.RUnlock()
return lang
} }
// SetLanguage sets the language code to be used at package level. // SetLanguage sets the language code to be used at package level.
// It reloads the corresponding translation file. // It reloads the corresponding translation file.
func SetLanguage(lang string) { func SetLanguage(lang string) {
language = lang globalConfig.Lock()
globalConfig.language = lang
globalConfig.Unlock()
loadStorage(true) loadStorage(true)
} }
// GetLibrary is the library getter for the package configuration // GetLibrary is the library getter for the package configuration
func GetLibrary() string { func GetLibrary() string {
return library globalConfig.RLock()
lib := globalConfig.library
globalConfig.RUnlock()
return lib
} }
// SetLibrary sets the root path for the loale directories and files to be used at package level. // SetLibrary sets the root path for the loale directories and files to be used at package level.
// It reloads the corresponding translation file. // It reloads the corresponding translation file.
func SetLibrary(lib string) { func SetLibrary(lib string) {
library = lib globalConfig.Lock()
globalConfig.library = lib
globalConfig.Unlock()
loadStorage(true) loadStorage(true)
} }
@@ -87,9 +134,13 @@ func SetLibrary(lib string) {
// This function is recommended to be used when changing more than one setting, // This function is recommended to be used when changing more than one setting,
// as using each setter will introduce a I/O overhead because the translation file will be loaded after each set. // as using each setter will introduce a I/O overhead because the translation file will be loaded after each set.
func Configure(lib, lang, dom string) { func Configure(lib, lang, dom string) {
library = lib globalConfig.Lock()
language = lang
domain = dom globalConfig.library = lib
globalConfig.language = lang
globalConfig.domain = dom
globalConfig.Unlock()
loadStorage(true) loadStorage(true)
} }
@@ -97,13 +148,13 @@ func Configure(lib, lang, dom string) {
// Get uses the default domain globally set to return the corresponding translation of a given string. // Get uses the default domain globally set to return the corresponding translation of a given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func Get(str string, vars ...interface{}) string { func Get(str string, vars ...interface{}) string {
return GetD(domain, str, vars...) return GetD(GetDomain(), str, vars...)
} }
// GetN retrieves the (N)th plural form of translation for the given string in the "default" domain. // GetN retrieves the (N)th plural form of translation for the given string in the default domain.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func GetN(str, plural string, n int, vars ...interface{}) string { func GetN(str, plural string, n int, vars ...interface{}) string {
return GetND("default", str, plural, n, vars...) return GetND(GetDomain(), 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 a given string.
@@ -119,19 +170,23 @@ func GetND(dom, str, plural string, n int, vars ...interface{}) string {
loadStorage(false) loadStorage(false)
// Return translation // Return translation
return storage.GetND(dom, str, plural, n, vars...) globalConfig.RLock()
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
globalConfig.RUnlock()
return tr
} }
// GetC uses the default domain globally set to return the corresponding translation of the given string in the given context. // 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. // 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 { func GetC(str, ctx string, vars ...interface{}) string {
return GetDC(domain, str, ctx, vars...) return GetDC(GetDomain(), str, ctx, vars...)
} }
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the "default" domain. // 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. // 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 { func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
return GetNDC("default", str, plural, n, ctx, vars...) return GetNDC(GetDomain(), str, plural, n, ctx, vars...)
} }
// GetDC returns the corresponding translation in the given domain for the given string in the given context. // GetDC returns the corresponding translation in the given domain for the given string in the given context.
@@ -147,5 +202,18 @@ func GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) str
loadStorage(false) loadStorage(false)
// Return translation // Return translation
return storage.GetNDC(dom, str, plural, n, ctx, vars...) globalConfig.RLock()
tr := globalConfig.storage.GetNDC(dom, str, plural, n, ctx, vars...)
globalConfig.RUnlock()
return tr
}
// printf applies text formatting only when needed to parse variables.
func printf(str string, vars ...interface{}) string {
if len(vars) > 0 {
return fmt.Sprintf(str, vars...)
}
return str
} }

View File

@@ -3,6 +3,7 @@ package gotext
import ( import (
"os" "os"
"path" "path"
"sync"
"testing" "testing"
) )
@@ -32,10 +33,12 @@ func TestGettersSetters(t *testing.T) {
func TestPackageFunctions(t *testing.T) { func TestPackageFunctions(t *testing.T) {
// Set PO content // Set PO content
str := ` str := `
# msgid "" msgid ""
# msgstr "" msgstr "Project-Id-Version: %s\n"
"Report-Msgid-Bugs-To: %s\n"
# Initial comment # Initial comment
# Headers below # More Headers below
"Language: en\n" "Language: en\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@@ -55,14 +58,6 @@ msgstr[0] "This one is the singular: %s"
msgstr[1] "This one is the plural: %s" msgstr[1] "This one is the plural: %s"
msgstr[2] "And this is the second plural form: %s" msgstr[2] "And this is the second plural form: %s"
msgid "This one has invalid syntax translations"
msgid_plural "Plural index"
msgstr[abc] "Wrong index"
msgstr[1 "Forgot to close brackets"
msgstr[0] "Badly formatted string'
msgid "Invalid formatted id[] with no translations
msgctxt "Ctx" msgctxt "Ctx"
msgid "One with var: %s" msgid "One with var: %s"
msgid_plural "Several with vars: %s" msgid_plural "Several with vars: %s"
@@ -79,17 +74,22 @@ msgstr "Some random translation in a context"
msgid "More" msgid "More"
msgstr "More translation" msgstr "More translation"
msgid "Untranslated"
msgid_plural "Several untranslated"
msgstr[0] ""
msgstr[1] ""
` `
// Create Locales directory on default location // Create Locales directory on default location
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en_US") dirname := path.Join("/tmp", "en_US")
err := os.MkdirAll(dirname, os.ModePerm) err := os.MkdirAll(dirname, os.ModePerm)
if err != nil { if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error()) t.Fatalf("Can't create test directory: %s", err.Error())
} }
// Write PO content to default domain file // Write PO content to default domain file
filename := path.Clean(dirname + string(os.PathSeparator) + "default.po") filename := path.Join(dirname, "default.po")
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
@@ -141,6 +141,181 @@ msgstr "More translation"
} }
} }
func TestUntranslated(t *testing.T) {
// Set PO content
str := `
msgid ""
msgstr ""
# Initial comment
# Headers below
"Language: en\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Untranslated"
msgid_plural "Several untranslated"
msgstr[0] ""
msgstr[1] ""
`
// Create Locales directory on default location
dirname := path.Join("/tmp", "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to default domain file
filename := path.Join(dirname, "default.po")
f, err := os.Create(filename)
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
defer f.Close()
_, err = f.WriteString(str)
if err != nil {
t.Fatalf("Can't write to test file: %s", err.Error())
}
// Set package configuration
Configure("/tmp", "en_US", "default")
// Test untranslated
tr := Get("Untranslated")
if tr != "Untranslated" {
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
}
tr = GetN("Untranslated", "Several untranslated", 1)
if tr != "Untranslated" {
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
}
tr = GetN("Untranslated", "Several untranslated", 2)
if tr != "Several untranslated" {
t.Errorf("Expected 'Several untranslated' but got '%s'", tr)
}
tr = GetD("default", "Untranslated")
if tr != "Untranslated" {
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
}
tr = GetND("default", "Untranslated", "Several untranslated", 1)
if tr != "Untranslated" {
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
}
tr = GetND("default", "Untranslated", "Several untranslated", 2)
if tr != "Several untranslated" {
t.Errorf("Expected 'Several untranslated' but got '%s'", tr)
}
}
func TestDomains(t *testing.T) {
// Set PO content
strDefault := `
msgid ""
msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Default text"
msgid_plural "Default texts"
msgstr[0] "Default translation"
msgstr[1] "Default translations"
msgctxt "Ctx"
msgid "Default context"
msgid_plural "Default contexts"
msgstr[0] "Default ctx translation"
msgstr[1] "Default ctx translations"
`
strCustom := `
msgid ""
msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Custom text"
msgid_plural "Custom texts"
msgstr[0] "Custom translation"
msgstr[1] "Custom translations"
msgctxt "Ctx"
msgid "Custom context"
msgid_plural "Custom contexts"
msgstr[0] "Custom ctx translation"
msgstr[1] "Custom ctx translations"
`
// Create Locales directory and files on temp location
dirname := path.Join("/tmp", "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
fDefault, err := os.Create(path.Join(dirname, "default.po"))
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
defer fDefault.Close()
fCustom, err := os.Create(path.Join(dirname, "custom.po"))
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
defer fCustom.Close()
_, err = fDefault.WriteString(strDefault)
if err != nil {
t.Fatalf("Can't write to test file: %s", err.Error())
}
_, err = fCustom.WriteString(strCustom)
if err != nil {
t.Fatalf("Can't write to test file: %s", err.Error())
}
Configure("/tmp", "en_US", "default")
// Check default domain translation
SetDomain("default")
tr := Get("Default text")
if tr != "Default translation" {
t.Errorf("Expected 'Default translation'. Got '%s'", tr)
}
tr = GetN("Default text", "Default texts", 23)
if tr != "Default translations" {
t.Errorf("Expected 'Default translations'. Got '%s'", tr)
}
tr = GetC("Default context", "Ctx")
if tr != "Default ctx translation" {
t.Errorf("Expected 'Default ctx translation'. Got '%s'", tr)
}
tr = GetNC("Default context", "Default contexts", 23, "Ctx")
if tr != "Default ctx translations" {
t.Errorf("Expected 'Default ctx translations'. Got '%s'", tr)
}
SetDomain("custom")
tr = Get("Custom text")
if tr != "Custom translation" {
t.Errorf("Expected 'Custom translation'. Got '%s'", tr)
}
tr = GetN("Custom text", "Custom texts", 23)
if tr != "Custom translations" {
t.Errorf("Expected 'Custom translations'. Got '%s'", tr)
}
tr = GetC("Custom context", "Ctx")
if tr != "Custom ctx translation" {
t.Errorf("Expected 'Custom ctx translation'. Got '%s'", tr)
}
tr = GetNC("Custom context", "Custom contexts", 23, "Ctx")
if tr != "Custom ctx translations" {
t.Errorf("Expected 'Custom ctx translations'. Got '%s'", tr)
}
}
func TestPackageRace(t *testing.T) { func TestPackageRace(t *testing.T) {
// Set PO content // Set PO content
str := `# Some comment str := `# Some comment
@@ -157,17 +332,21 @@ msgstr[0] "This one is the singular: %s"
msgstr[1] "This one is the plural: %s" msgstr[1] "This one is the plural: %s"
msgstr[2] "And this is the second plural form: %s" msgstr[2] "And this is the second plural form: %s"
msgctxt "Ctx"
msgid "Some random in a context"
msgstr "Some random translation in a context"
` `
// Create Locales directory on default location // Create Locales directory on default location
dirname := path.Clean(library + string(os.PathSeparator) + "en_US") dirname := path.Join("/tmp", "en_US")
err := os.MkdirAll(dirname, os.ModePerm) err := os.MkdirAll(dirname, os.ModePerm)
if err != nil { if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error()) t.Fatalf("Can't create test directory: %s", err.Error())
} }
// Write PO content to default domain file // Write PO content to default domain file
filename := path.Clean(dirname + string(os.PathSeparator) + domain + ".po") filename := path.Join("/tmp", GetDomain()+".po")
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
@@ -180,20 +359,27 @@ msgstr[2] "And this is the second plural form: %s"
t.Fatalf("Can't write to test file: %s", err.Error()) t.Fatalf("Can't write to test file: %s", err.Error())
} }
// Init sync channels var wg sync.WaitGroup
c1 := make(chan bool)
c2 := make(chan bool)
// Test translations for i := 0; i < 1000; i++ {
go func(done chan bool) { wg.Add(1)
Get("My text") // Test translations
done <- true go func() {
}(c1) defer wg.Done()
go func(done chan bool) { GetLibrary()
Get("My text") SetLibrary(path.Join("/tmp", "gotextlib"))
done <- true GetDomain()
}(c2) SetDomain("default")
GetLanguage()
SetLanguage("en_US")
Configure("/tmp", "en_US", "default")
Get("My text") Get("My text")
GetN("One with var: %s", "Several with vars: %s", 0, "test")
GetC("Some random in a context", "Ctx")
}()
}
wg.Wait()
} }

View File

@@ -1,7 +1,6 @@
package gotext package gotext
import ( import (
"fmt"
"os" "os"
"path" "path"
"sync" "sync"
@@ -14,7 +13,10 @@ multiple languages at the same time by working with this object.
Example: Example:
import "github.com/leonelquinteros/gotext" import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() { func main() {
// Create Locale with library path and language code // Create Locale with library path and language code
@@ -24,13 +26,13 @@ Example:
l.AddDomain("default") l.AddDomain("default")
// Translate text from default domain // 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/LC_MESSAGES/extras.po') // Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.po')
l.AddDomain("extras") l.AddDomain("extras")
// Translate text from domain // Translate text from domain
println(l.GetD("extras", "Translate this")) fmt.Println(l.GetD("extras", "Translate this"))
} }
*/ */
@@ -135,7 +137,7 @@ func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) stri
} }
// Return the same we received by default // Return the same we received by default
return fmt.Sprintf(plural, vars...) return printf(plural, vars...)
} }
// GetC uses a domain "default" to return the corresponding translation of the given string in the given context. // GetC uses a domain "default" to return the corresponding translation of the given string in the given context.
@@ -172,5 +174,5 @@ func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...inte
} }
// Return the same we received by default // Return the same we received by default
return fmt.Sprintf(plural, vars...) return printf(plural, vars...)
} }

View File

@@ -9,8 +9,8 @@ import (
func TestLocale(t *testing.T) { func TestLocale(t *testing.T) {
// Set PO content // Set PO content
str := ` str := `
# msgid "" msgid ""
# msgstr "" msgstr ""
# Initial comment # Initial comment
# Headers below # Headers below
"Language: en\n" "Language: en\n"
@@ -38,8 +38,6 @@ msgstr[abc] "Wrong index"
msgstr[1 "Forgot to close brackets" msgstr[1 "Forgot to close brackets"
msgstr[0] "Badly formatted string' msgstr[0] "Badly formatted string'
msgid "Invalid formatted id[] with no translations
msgctxt "Ctx" msgctxt "Ctx"
msgid "One with var: %s" msgid "One with var: %s"
msgid_plural "Several with vars: %s" msgid_plural "Several with vars: %s"

142
po.go
View File

@@ -2,7 +2,6 @@ package gotext
import ( import (
"bufio" "bufio"
"fmt"
"io/ioutil" "io/ioutil"
"net/textproto" "net/textproto"
"os" "os"
@@ -46,7 +45,12 @@ func (t *translation) getN(n int) string {
} }
} }
// Return unstranlated plural by default // Return unstranlated singular if corresponding
if n == 0 {
return t.id
}
// Return untranslated plural by default
return t.pluralID return t.pluralID
} }
@@ -57,7 +61,10 @@ And it's safe for concurrent use by multiple goroutines by using the sync packag
Example: Example:
import "github.com/leonelquinteros/gotext" import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() { func main() {
// Create po object // Create po object
@@ -67,13 +74,13 @@ Example:
po.ParseFile("/path/to/po/file/translations.po") po.ParseFile("/path/to/po/file/translations.po")
// Get translation // Get translation
println(po.Get("Translate this")) fmt.Println(po.Get("Translate this"))
} }
*/ */
type Po struct { type Po struct {
// Headers // Headers storage
RawHeaders string Headers textproto.MIMEHeader
// Language header // Language header
Language string Language string
@@ -133,7 +140,6 @@ func (po *Po) ParseFile(f string) {
func (po *Po) Parse(str string) { func (po *Po) Parse(str string) {
// Lock while parsing // Lock while parsing
po.Lock() po.Lock()
defer po.Unlock()
// Init storage // Init storage
if po.translations == nil { if po.translations == nil {
@@ -188,7 +194,7 @@ func (po *Po) Parse(str string) {
// Multi line strings and headers // Multi line strings and headers
if strings.HasPrefix(l, "\"") && strings.HasSuffix(l, "\"") { if strings.HasPrefix(l, "\"") && strings.HasSuffix(l, "\"") {
state = po.parseString(l, state) po.parseString(l, state)
continue continue
} }
} }
@@ -196,6 +202,9 @@ func (po *Po) Parse(str string) {
// Save last translation buffer. // Save last translation buffer.
po.saveBuffer() po.saveBuffer()
// Unlock to parse headers
po.Unlock()
// Parse headers // Parse headers
po.parseHeaders() po.parseHeaders()
} }
@@ -203,23 +212,24 @@ func (po *Po) Parse(str string) {
// saveBuffer takes the context and translation buffers // saveBuffer takes the context and translation buffers
// and saves it on the translations collection // and saves it on the translations collection
func (po *Po) saveBuffer() { func (po *Po) saveBuffer() {
// If we have something to save... // With no context...
if po.trBuffer.id != "" { if po.ctxBuffer == "" {
// With no context... po.translations[po.trBuffer.id] = po.trBuffer
if po.ctxBuffer == "" { } else {
po.translations[po.trBuffer.id] = po.trBuffer // With context...
} else { if _, ok := po.contexts[po.ctxBuffer]; !ok {
// With context... po.contexts[po.ctxBuffer] = make(map[string]*translation)
if _, ok := po.contexts[po.ctxBuffer]; !ok {
po.contexts[po.ctxBuffer] = make(map[string]*translation)
}
po.contexts[po.ctxBuffer][po.trBuffer.id] = po.trBuffer
} }
po.contexts[po.ctxBuffer][po.trBuffer.id] = po.trBuffer
// Flush buffer // Cleanup current context buffer if needed
po.trBuffer = newTranslation() if po.trBuffer.id != "" {
po.ctxBuffer = "" po.ctxBuffer = ""
}
} }
// Flush translation buffer
po.trBuffer = newTranslation()
} }
// parseContext takes a line starting with "msgctxt", // parseContext takes a line starting with "msgctxt",
@@ -279,70 +289,72 @@ func (po *Po) parseMessage(l string) {
// parseString takes a well formatted string without prefix // parseString takes a well formatted string without prefix
// and creates headers or attach multi-line strings when corresponding // and creates headers or attach multi-line strings when corresponding
func (po *Po) parseString(l string, state parseState) parseState { func (po *Po) parseString(l string, state parseState) {
clean, _ := strconv.Unquote(l)
switch state { switch state {
case msgStr: case msgStr:
// Check for multiline from previously set msgid // Append to last translation found
if po.trBuffer.id != "" { po.trBuffer.trs[len(po.trBuffer.trs)-1] += clean
// Append to last translation found
uq, _ := strconv.Unquote(l)
po.trBuffer.trs[len(po.trBuffer.trs)-1] += uq
}
case msgID: case msgID:
// Multiline msgid - Append to current id // Multiline msgid - Append to current id
uq, _ := strconv.Unquote(l) po.trBuffer.id += clean
po.trBuffer.id += uq
case msgIDPlural: case msgIDPlural:
// Multiline msgid - Append to current id // Multiline msgid - Append to current id
uq, _ := strconv.Unquote(l) po.trBuffer.pluralID += clean
po.trBuffer.pluralID += uq
case msgCtxt: case msgCtxt:
// Multiline context - Append to current context // Multiline context - Append to current context
ctxt, _ := strconv.Unquote(l) po.ctxBuffer += clean
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. // isValidLine checks for line prefixes to detect valid syntax.
func (po *Po) isValidLine(l string) bool { func (po *Po) isValidLine(l string) bool {
// Skip empty lines
if l == "" {
return false
}
// Check prefix // Check prefix
if !strings.HasPrefix(l, "\"") && !strings.HasPrefix(l, "msgctxt") && !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") { valid := []string{
return false "\"",
"msgctxt",
"msgid",
"msgid_plural",
"msgstr",
} }
return true for _, v := range valid {
if strings.HasPrefix(l, v) {
return true
}
}
return false
} }
// parseHeaders retrieves data from previously parsed headers // parseHeaders retrieves data from previously parsed headers
func (po *Po) parseHeaders() { func (po *Po) parseHeaders() {
// Make sure we end with 2 carriage returns. // Make sure we end with 2 carriage returns.
po.RawHeaders += "\n\n" raw := po.Get("") + "\n\n"
// Read // Read
reader := bufio.NewReader(strings.NewReader(po.RawHeaders)) reader := bufio.NewReader(strings.NewReader(raw))
tp := textproto.NewReader(reader) tp := textproto.NewReader(reader)
mimeHeader, err := tp.ReadMIMEHeader() var err error
// Sync Headers write.
po.Lock()
defer po.Unlock()
po.Headers, err = tp.ReadMIMEHeader()
if err != nil { if err != nil {
return return
} }
// Get/save needed headers // Get/save needed headers
po.Language = mimeHeader.Get("Language") po.Language = po.Headers.Get("Language")
po.PluralForms = mimeHeader.Get("Plural-Forms") po.PluralForms = po.Headers.Get("Plural-Forms")
// Parse Plural-Forms formula // Parse Plural-Forms formula
if po.PluralForms == "" { if po.PluralForms == "" {
@@ -415,12 +427,12 @@ func (po *Po) Get(str string, vars ...interface{}) string {
if po.translations != nil { if po.translations != nil {
if _, ok := po.translations[str]; ok { if _, ok := po.translations[str]; ok {
return fmt.Sprintf(po.translations[str].get(), vars...) return printf(po.translations[str].get(), vars...)
} }
} }
// Return the same we received by default // Return the same we received by default
return fmt.Sprintf(str, vars...) return printf(str, vars...)
} }
// GetN retrieves the (N)th plural form of translation for the given string. // GetN retrieves the (N)th plural form of translation for the given string.
@@ -432,14 +444,14 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
if po.translations != nil { if po.translations != nil {
if _, ok := po.translations[str]; ok { if _, ok := po.translations[str]; ok {
return fmt.Sprintf(po.translations[str].getN(po.pluralForm(n)), vars...) return printf(po.translations[str].getN(po.pluralForm(n)), vars...)
} }
} }
if n == 1 { if n == 1 {
return fmt.Sprintf(str, vars...) return printf(str, vars...)
} }
return fmt.Sprintf(plural, vars...) return printf(plural, vars...)
} }
// GetC retrieves the corresponding translation for a given string in the given context. // GetC retrieves the corresponding translation for a given string in the given context.
@@ -453,14 +465,14 @@ func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
if _, ok := po.contexts[ctx]; ok { if _, ok := po.contexts[ctx]; ok {
if po.contexts[ctx] != nil { if po.contexts[ctx] != nil {
if _, ok := po.contexts[ctx][str]; ok { if _, ok := po.contexts[ctx][str]; ok {
return fmt.Sprintf(po.contexts[ctx][str].get(), vars...) return printf(po.contexts[ctx][str].get(), vars...)
} }
} }
} }
} }
// Return the string we received by default // Return the string we received by default
return fmt.Sprintf(str, vars...) return printf(str, vars...)
} }
// GetNC retrieves the (N)th plural form of translation for the given string in the given context. // GetNC retrieves the (N)th plural form of translation for the given string in the given context.
@@ -474,14 +486,14 @@ func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{})
if _, ok := po.contexts[ctx]; ok { if _, ok := po.contexts[ctx]; ok {
if po.contexts[ctx] != nil { if po.contexts[ctx] != nil {
if _, ok := po.contexts[ctx][str]; ok { if _, ok := po.contexts[ctx][str]; ok {
return fmt.Sprintf(po.contexts[ctx][str].getN(po.pluralForm(n)), vars...) return printf(po.contexts[ctx][str].getN(po.pluralForm(n)), vars...)
} }
} }
} }
} }
if n == 1 { if n == 1 {
return fmt.Sprintf(str, vars...) return printf(str, vars...)
} }
return fmt.Sprintf(plural, vars...) return printf(plural, vars...)
} }

View File

@@ -9,6 +9,9 @@ import (
func TestPo(t *testing.T) { func TestPo(t *testing.T) {
// Set PO content // Set PO content
str := ` str := `
msgid ""
msgstr ""
# Initial comment # Initial comment
# Headers below # Headers below
"Language: en\n" "Language: en\n"
@@ -48,14 +51,6 @@ msgstr[0] "This one is the singular: %s"
msgstr[1] "This one is the plural: %s" msgstr[1] "This one is the plural: %s"
msgstr[2] "And this is the second plural form: %s" msgstr[2] "And this is the second plural form: %s"
msgid "This one has invalid syntax translations"
msgid_plural "Plural index"
msgstr[abc] "Wrong index"
msgstr[1 "Forgot to close brackets"
msgstr[0] "Badly formatted string'
msgid "Invalid formatted id[] with no translations
msgctxt "Ctx" msgctxt "Ctx"
msgid "One with var: %s" msgid "One with var: %s"
msgid_plural "Several with vars: %s" msgid_plural "Several with vars: %s"
@@ -75,11 +70,11 @@ msgstr ""
msgid "Empty plural form singular" msgid "Empty plural form singular"
msgid_plural "Empty plural form" msgid_plural "Empty plural form"
msgstr[0] "Singular translated" msgstr[0] "Singular translated"
msgstr[1] " msgstr[1] ""
msgid "More" msgid "More"
msgstr "More translation" msgstr "More translation"
"
` `
// Write PO content to file // Write PO content to file
@@ -152,17 +147,6 @@ msgstr "More translation"
t.Errorf("Expected 'This are tests' but got '%s'", tr) 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 // Test context translations
v = "Test" v = "Test"
tr = po.GetC("One with var: %s", "Ctx", v) tr = po.GetC("One with var: %s", "Ctx", v)
@@ -214,9 +198,42 @@ msgstr "More translation"
} }
} }
func TestPlural(t *testing.T) {
// Set PO content
str := `
msgid ""
msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Singular: %s"
msgid_plural "Plural: %s"
msgstr[0] "TR Singular: %s"
msgstr[1] "TR Plural: %s"
msgstr[2] "TR Plural 2: %s"
`
// Create po object
po := new(Po)
po.Parse(str)
v := "Var"
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
if tr != "TR Plural: Var" {
t.Errorf("Expected 'TR Plural: Var' but got '%s'", tr)
}
tr = po.GetN("Singular: %s", "Plural: %s", 1, v)
if tr != "TR Singular: Var" {
t.Errorf("Expected 'TR Singular: Var' but got '%s'", tr)
}
}
func TestPoHeaders(t *testing.T) { func TestPoHeaders(t *testing.T) {
// Set PO content // Set PO content
str := ` str := `
msgid ""
msgstr ""
# Initial comment # Initial comment
# Headers below # Headers below
"Language: en\n" "Language: en\n"
@@ -246,9 +263,30 @@ msgstr "Translated example"
} }
} }
func TestMissingPoHeadersSupport(t *testing.T) {
// Set PO content
str := `
msgid "Example"
msgstr "Translated example"
`
// Create po object
po := new(Po)
// Parse
po.Parse(str)
// Check translation expected
if po.Get("Example") != "Translated example" {
t.Errorf("Expected 'Translated example' but got '%s'", po.Get("Example"))
}
}
func TestPluralFormsSingle(t *testing.T) { func TestPluralFormsSingle(t *testing.T) {
// Single form // Single form
str := ` str := `
msgid ""
msgstr ""
"Plural-Forms: nplurals=1; plural=0;" "Plural-Forms: nplurals=1; plural=0;"
# Some comment # Some comment
@@ -292,6 +330,8 @@ msgstr[3] "Plural form 3"
func TestPluralForms2(t *testing.T) { func TestPluralForms2(t *testing.T) {
// 2 forms // 2 forms
str := ` str := `
msgid ""
msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;" "Plural-Forms: nplurals=2; plural=n != 1;"
# Some comment # Some comment
@@ -331,6 +371,8 @@ msgstr[3] "Plural form 3"
func TestPluralForms3(t *testing.T) { func TestPluralForms3(t *testing.T) {
// 3 forms // 3 forms
str := ` str := `
msgid ""
msgstr ""
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;"
# Some comment # Some comment
@@ -378,6 +420,8 @@ msgstr[3] "Plural form 3"
func TestPluralFormsSpecial(t *testing.T) { func TestPluralFormsSpecial(t *testing.T) {
// 3 forms special // 3 forms special
str := ` str := `
msgid ""
msgstr ""
"Plural-Forms: nplurals=3;" "Plural-Forms: nplurals=3;"
"plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" "plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"

37
vendor/github.com/mattn/kinako/README.md generated vendored Normal file
View File

@@ -0,0 +1,37 @@
# kinako
Kinako is small VM written in Go.
![](https://raw.githubusercontent.com/mattn/kinako/master/kinako.png)
(Picture licensed under CC BY-SA 3.0 by wikipedia)
## Installation
Requires Go.
```
$ go get -u github.com/mattn/kinako
```
## Usage
Embedding the interpreter into your own program:
```Go
var env = vm.NewEnv()
env.Define("foo", 1)
val, err := env.Execute(`foo + 3`)
if err != nil {
panic(err)
}
fmt.Println(val)
```
# License
MIT
# Author
Yasuhiro Matsumoto (a.k.a mattn)

18
vendor/github.com/mattn/kinako/_example/main.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"log"
"github.com/mattn/kinako/vm"
)
func main() {
env := vm.NewEnv()
v, err := env.Execute(`foo=1; foo+3`)
if err != nil {
log.Fatal(err)
}
fmt.Println(v)
}

View File

@@ -75,6 +75,14 @@ type TernaryOpExpr struct {
Rhs Expr Rhs Expr
} }
// CallExpr provide calling expression.
type CallExpr struct {
ExprImpl
Func interface{}
Name string
SubExprs []Expr
}
// ParenExpr provide parent block expression. // ParenExpr provide parent block expression.
type ParenExpr struct { type ParenExpr struct {
ExprImpl ExprImpl

BIN
vendor/github.com/mattn/kinako/kinako.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

View File

@@ -8,13 +8,14 @@ import (
"github.com/mattn/kinako/ast" "github.com/mattn/kinako/ast"
) )
//line parser.go.y:15 //line parser.go.y:16
type yySymType struct { type yySymType struct {
yys int yys int
compstmt []ast.Stmt compstmt []ast.Stmt
stmts []ast.Stmt stmts []ast.Stmt
stmt ast.Stmt stmt ast.Stmt
expr ast.Expr expr ast.Expr
exprs []ast.Expr
tok ast.Token tok ast.Token
term ast.Token term ast.Token
terms ast.Token terms ast.Token
@@ -82,106 +83,107 @@ const yyEofCode = 1
const yyErrCode = 2 const yyErrCode = 2
const yyInitialStackSize = 16 const yyInitialStackSize = 16
//line parser.go.y:194 //line parser.go.y:213
//line yacctab:1 //line yacctab:1
var yyExca = [...]int{ var yyExca = [...]int{
-1, 1, -1, 1,
1, -1, 1, -1,
-2, 0, -2, 0,
-1, 49, -1, 50,
7, 0, 7, 0,
8, 0, 8, 0,
-2, 20, -2, 20,
-1, 50, -1, 51,
7, 0, 7, 0,
8, 0, 8, 0,
-2, 21, -2, 21,
} }
const yyNprod = 36 const yyNprod = 40
const yyPrivate = 57344 const yyPrivate = 57344
var yyTokenNames []string var yyTokenNames []string
var yyStates []string var yyStates []string
const yyLast = 249 const yyLast = 251
var yyAct = [...]int{ var yyAct = [...]int{
9, 6, 7, 33, 35, 4, 22, 23, 3, 18, 9, 6, 7, 33, 35, 37, 22, 23, 60, 3,
24, 25, 26, 37, 38, 39, 2, 40, 33, 35, 24, 25, 26, 38, 39, 40, 1, 41, 33, 35,
17, 42, 43, 44, 45, 46, 47, 48, 49, 50, 8, 43, 44, 45, 46, 47, 48, 49, 50, 51,
51, 52, 53, 54, 55, 56, 57, 58, 27, 28, 52, 53, 54, 55, 56, 57, 58, 59, 61, 42,
30, 32, 34, 36, 8, 1, 21, 60, 0, 29, 27, 28, 30, 32, 34, 36, 65, 0, 21, 63,
4, 29, 31, 2, 18, 22, 23, 17, 0, 24,
25, 26, 64, 0, 66, 0, 67, 33, 35, 27,
28, 30, 32, 34, 36, 0, 0, 21, 0, 0,
29, 31, 0, 0, 22, 23, 0, 0, 24, 25,
26, 0, 0, 0, 0, 62, 33, 35, 27, 28,
30, 32, 34, 36, 0, 20, 21, 0, 0, 29,
31, 0, 0, 22, 23, 5, 0, 24, 25, 26, 31, 0, 0, 22, 23, 5, 0, 24, 25, 26,
19, 61, 0, 41, 0, 33, 35, 27, 28, 30, 19, 0, 0, 0, 0, 33, 35, 27, 28, 30,
32, 34, 36, 0, 19, 21, 0, 0, 29, 31, 32, 34, 36, 0, 19, 21, 0, 0, 29, 31,
0, 0, 22, 23, 0, 0, 24, 25, 26, 0, 0, 0, 22, 23, 0, 0, 24, 25, 26, 0,
0, 0, 0, 59, 33, 35, 27, 28, 30, 32, 0, 0, 0, 0, 33, 35, 27, 28, 30, 32,
34, 36, 0, 20, 21, 0, 0, 29, 31, 0, 0, 36, 0, 0, 0, 0, 0, 29, 31, 0,
0, 22, 23, 0, 0, 24, 25, 26, 0, 0, 0, 22, 23, 0, 0, 24, 25, 26, 27, 28,
0, 0, 0, 33, 35, 27, 28, 30, 32, 34, 30, 32, 0, 33, 35, 0, 0, 0, 0, 29,
36, 0, 0, 21, 0, 0, 29, 31, 0, 0, 31, 0, 0, 22, 23, 0, 0, 24, 25, 26,
22, 23, 0, 0, 24, 25, 26, 0, 0, 0, 30, 32, 10, 11, 15, 33, 35, 0, 0, 29,
0, 0, 33, 35, 27, 28, 30, 32, 0, 36, 31, 0, 0, 22, 23, 0, 0, 24, 25, 26,
0, 0, 0, 0, 0, 29, 31, 0, 0, 22, 0, 12, 10, 11, 15, 33, 35, 0, 13, 14,
23, 0, 0, 24, 25, 26, 27, 28, 30, 32, 16, 24, 25, 26, 6, 7, 0, 0, 0, 33,
0, 33, 35, 0, 0, 0, 0, 29, 31, 0, 35, 12, 0, 0, 0, 0, 0, 0, 13, 14,
0, 22, 23, 0, 0, 24, 25, 26, 30, 32, 16,
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{ var yyPact = [...]int{
-35, -1000, 216, -35, -35, -1000, -1000, -1000, -1000, 89, -35, -1000, 218, -35, -35, -1000, -1000, -1000, -1000, 91,
-1000, -1000, 216, 216, 216, -1000, 216, -1000, 196, -1000, -27, -1000, 218, 218, 218, -1000, 218, -1000, 198, -1000,
216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218,
216, 216, 216, 216, 216, 216, 216, -31, -31, -31, 218, 218, 218, 218, 218, 218, 218, 218, -31, -31,
60, -1000, 118, 31, 203, 203, -31, -31, -31, 189, -31, 62, -1000, 120, 33, 205, 205, -31, -31, -31,
189, -16, -16, -16, -16, 118, 147, 118, 169, -1000, 191, 191, -16, -16, -16, -16, 120, 149, 120, 171,
216, 118, 29, 120, -1000, 218, -1000, 218, 120, 120,
} }
var yyPgo = [...]int{ var yyPgo = [...]int{
0, 45, 8, 44, 0, 16, 5, 55, 0, 16, 9, 20, 0, 8, 53, 50, 115,
} }
var yyR1 = [...]int{ var yyR1 = [...]int{
0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 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, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8,
} }
var yyR2 = [...]int{ var yyR2 = [...]int{
0, 1, 2, 2, 3, 3, 1, 1, 1, 2, 0, 1, 2, 2, 3, 3, 1, 1, 1, 2,
2, 2, 1, 5, 3, 3, 3, 3, 3, 3, 2, 2, 1, 5, 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,
0, 1, 1, 2, 1, 1, 4, 0, 1, 3, 0, 1, 1, 2, 1, 1,
} }
var yyChk = [...]int{ var yyChk = [...]int{
-1000, -1, -5, -2, -6, -7, 36, 37, -3, -4, -1000, -1, -6, -2, -7, -8, 36, 37, -3, -4,
4, 5, 23, 30, 31, 6, 32, -5, -6, -7, 4, 5, 23, 30, 31, 6, 32, -6, -7, -8,
14, 15, 22, 23, 26, 27, 28, 7, 8, 18, 14, 15, 22, 23, 26, 27, 28, 7, 8, 18,
9, 19, 10, 34, 11, 35, 12, -4, -4, -4, 9, 19, 10, 34, 11, 35, 12, 32, -4, -4,
-4, -3, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -3, -4, -4, -4, -4, -4, -4, -4,
-4, -4, -4, -4, -4, -4, -4, -4, -4, 33, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4,
16, -4, -5, -4, 33, 16, 33, 17, -4, -4,
} }
var yyDef = [...]int{ var yyDef = [...]int{
30, -2, 1, 30, 31, 32, 34, 35, 3, 6, 34, -2, 1, 34, 35, 36, 38, 39, 3, 6,
7, 8, 0, 0, 0, 12, 0, 2, 31, 33, 7, 8, 0, 0, 0, 12, 0, 2, 35, 37,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 9, 10, 11, 0, 0, 0, 0, 0, 0, 0, 31, 9, 10,
0, 4, 5, 0, 15, 16, 17, 18, 19, -2, 11, 0, 4, 5, 0, 15, 16, 17, 18, 19,
-2, 22, 23, 24, 25, 26, 27, 28, 29, 14, -2, -2, 22, 23, 24, 25, 26, 27, 28, 29,
0, 13, 0, 32, 14, 0, 30, 0, 13, 33,
} }
var yyTok1 = [...]int{ var yyTok1 = [...]int{
@@ -547,19 +549,19 @@ yydefault:
case 1: case 1:
yyDollar = yyS[yypt-1 : yypt+1] yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:43 //line parser.go.y:45
{ {
yyVAL.compstmt = nil yyVAL.compstmt = nil
} }
case 2: case 2:
yyDollar = yyS[yypt-2 : yypt+1] yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:47 //line parser.go.y:49
{ {
yyVAL.compstmt = yyDollar[1].stmts yyVAL.compstmt = yyDollar[1].stmts
} }
case 3: case 3:
yyDollar = yyS[yypt-2 : yypt+1] yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:53 //line parser.go.y:55
{ {
yyVAL.stmts = []ast.Stmt{yyDollar[2].stmt} yyVAL.stmts = []ast.Stmt{yyDollar[2].stmt}
if l, ok := yylex.(*Lexer); ok { if l, ok := yylex.(*Lexer); ok {
@@ -568,7 +570,7 @@ yydefault:
} }
case 4: case 4:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:60 //line parser.go.y:62
{ {
if yyDollar[3].stmt != nil { if yyDollar[3].stmt != nil {
yyVAL.stmts = append(yyDollar[1].stmts, yyDollar[3].stmt) yyVAL.stmts = append(yyDollar[1].stmts, yyDollar[3].stmt)
@@ -579,172 +581,196 @@ yydefault:
} }
case 5: case 5:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:71 //line parser.go.y:73
{ {
yyVAL.stmt = &ast.LetStmt{Lhs: yyDollar[1].expr, Operator: "=", Rhs: yyDollar[3].expr} yyVAL.stmt = &ast.LetStmt{Lhs: yyDollar[1].expr, Operator: "=", Rhs: yyDollar[3].expr}
} }
case 6: case 6:
yyDollar = yyS[yypt-1 : yypt+1] yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:75 //line parser.go.y:77
{ {
yyVAL.stmt = &ast.ExprStmt{Expr: yyDollar[1].expr} yyVAL.stmt = &ast.ExprStmt{Expr: yyDollar[1].expr}
} }
case 7: case 7:
yyDollar = yyS[yypt-1 : yypt+1] yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:81 //line parser.go.y:83
{ {
yyVAL.expr = &ast.IdentExpr{Lit: yyDollar[1].tok.Lit} yyVAL.expr = &ast.IdentExpr{Lit: yyDollar[1].tok.Lit}
} }
case 8: case 8:
yyDollar = yyS[yypt-1 : yypt+1] yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:85 //line parser.go.y:87
{ {
yyVAL.expr = &ast.NumberExpr{Lit: yyDollar[1].tok.Lit} yyVAL.expr = &ast.NumberExpr{Lit: yyDollar[1].tok.Lit}
} }
case 9: case 9:
yyDollar = yyS[yypt-2 : yypt+1] yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:89 //line parser.go.y:91
{ {
yyVAL.expr = &ast.UnaryExpr{Operator: "-", Expr: yyDollar[2].expr} yyVAL.expr = &ast.UnaryExpr{Operator: "-", Expr: yyDollar[2].expr}
} }
case 10: case 10:
yyDollar = yyS[yypt-2 : yypt+1] yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:93 //line parser.go.y:95
{ {
yyVAL.expr = &ast.UnaryExpr{Operator: "!", Expr: yyDollar[2].expr} yyVAL.expr = &ast.UnaryExpr{Operator: "!", Expr: yyDollar[2].expr}
} }
case 11: case 11:
yyDollar = yyS[yypt-2 : yypt+1] yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:97 //line parser.go.y:99
{ {
yyVAL.expr = &ast.UnaryExpr{Operator: "^", Expr: yyDollar[2].expr} yyVAL.expr = &ast.UnaryExpr{Operator: "^", Expr: yyDollar[2].expr}
} }
case 12: case 12:
yyDollar = yyS[yypt-1 : yypt+1] yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:101 //line parser.go.y:103
{ {
yyVAL.expr = &ast.StringExpr{Lit: yyDollar[1].tok.Lit} yyVAL.expr = &ast.StringExpr{Lit: yyDollar[1].tok.Lit}
} }
case 13: case 13:
yyDollar = yyS[yypt-5 : yypt+1] yyDollar = yyS[yypt-5 : yypt+1]
//line parser.go.y:105 //line parser.go.y:107
{ {
yyVAL.expr = &ast.TernaryOpExpr{Expr: yyDollar[1].expr, Lhs: yyDollar[3].expr, Rhs: yyDollar[5].expr} yyVAL.expr = &ast.TernaryOpExpr{Expr: yyDollar[1].expr, Lhs: yyDollar[3].expr, Rhs: yyDollar[5].expr}
} }
case 14: case 14:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:109 //line parser.go.y:111
{ {
yyVAL.expr = &ast.ParenExpr{SubExpr: yyDollar[2].expr} yyVAL.expr = &ast.ParenExpr{SubExpr: yyDollar[2].expr}
} }
case 15: case 15:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:113 //line parser.go.y:115
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "+", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "+", Rhs: yyDollar[3].expr}
} }
case 16: case 16:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:117 //line parser.go.y:119
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "-", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "-", Rhs: yyDollar[3].expr}
} }
case 17: case 17:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:121 //line parser.go.y:123
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "*", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "*", Rhs: yyDollar[3].expr}
} }
case 18: case 18:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:125 //line parser.go.y:127
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "/", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "/", Rhs: yyDollar[3].expr}
} }
case 19: case 19:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:129 //line parser.go.y:131
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "%", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "%", Rhs: yyDollar[3].expr}
} }
case 20: case 20:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:133 //line parser.go.y:135
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "==", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "==", Rhs: yyDollar[3].expr}
} }
case 21: case 21:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:137 //line parser.go.y:139
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "!=", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "!=", Rhs: yyDollar[3].expr}
} }
case 22: case 22:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:141 //line parser.go.y:143
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">", Rhs: yyDollar[3].expr}
} }
case 23: case 23:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:145 //line parser.go.y:147
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">=", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: ">=", Rhs: yyDollar[3].expr}
} }
case 24: case 24:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:149 //line parser.go.y:151
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<", Rhs: yyDollar[3].expr}
} }
case 25: case 25:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:153 //line parser.go.y:155
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<=", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "<=", Rhs: yyDollar[3].expr}
} }
case 26: case 26:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:157 //line parser.go.y:159
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "|", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "|", Rhs: yyDollar[3].expr}
} }
case 27: case 27:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:161 //line parser.go.y:163
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "||", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "||", Rhs: yyDollar[3].expr}
} }
case 28: case 28:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:165 //line parser.go.y:167
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&", Rhs: yyDollar[3].expr}
} }
case 29: case 29:
yyDollar = yyS[yypt-3 : yypt+1] yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:169 //line parser.go.y:171
{ {
yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&&", Rhs: yyDollar[3].expr} yyVAL.expr = &ast.BinOpExpr{Lhs: yyDollar[1].expr, Operator: "&&", Rhs: yyDollar[3].expr}
} }
case 30:
yyDollar = yyS[yypt-4 : yypt+1]
//line parser.go.y:175
{
yyVAL.expr = &ast.CallExpr{Name: yyDollar[1].tok.Lit, SubExprs: yyDollar[3].exprs}
}
case 31:
yyDollar = yyS[yypt-0 : yypt+1]
//line parser.go.y:180
{
yyVAL.exprs = nil
}
case 32: case 32:
yyDollar = yyS[yypt-1 : yypt+1] yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:179 //line parser.go.y:184
{ {
yyVAL.exprs = []ast.Expr{yyDollar[1].expr}
} }
case 33: case 33:
yyDollar = yyS[yypt-3 : yypt+1]
//line parser.go.y:188
{
yyVAL.exprs = append(yyDollar[1].exprs, yyDollar[3].expr)
}
case 36:
yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:198
{
}
case 37:
yyDollar = yyS[yypt-2 : yypt+1] yyDollar = yyS[yypt-2 : yypt+1]
//line parser.go.y:182 //line parser.go.y:201
{ {
} }
case 34: case 38:
yyDollar = yyS[yypt-1 : yypt+1] yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:187 //line parser.go.y:206
{ {
} }
case 35: case 39:
yyDollar = yyS[yypt-1 : yypt+1] yyDollar = yyS[yypt-1 : yypt+1]
//line parser.go.y:190 //line parser.go.y:209
{ {
} }
} }

View File

@@ -11,12 +11,14 @@ import (
%type<stmts> stmts %type<stmts> stmts
%type<stmt> stmt %type<stmt> stmt
%type<expr> expr %type<expr> expr
%type<exprs> exprs
%union{ %union{
compstmt []ast.Stmt compstmt []ast.Stmt
stmts []ast.Stmt stmts []ast.Stmt
stmt ast.Stmt stmt ast.Stmt
expr ast.Expr expr ast.Expr
exprs []ast.Expr
tok ast.Token tok ast.Token
term ast.Token term ast.Token
terms ast.Token terms ast.Token
@@ -169,6 +171,23 @@ expr :
{ {
$$ = &ast.BinOpExpr{Lhs: $1, Operator: "&&", Rhs: $3} $$ = &ast.BinOpExpr{Lhs: $1, Operator: "&&", Rhs: $3}
} }
| IDENT '(' exprs ')'
{
$$ = &ast.CallExpr{Name: $1.Lit, SubExprs: $3}
}
exprs :
{
$$ = nil
}
| expr
{
$$ = []ast.Expr{$1}
}
| exprs ',' expr
{
$$ = append($1, $3)
}
opt_terms : /* none */ opt_terms : /* none */
| terms | terms

View File

@@ -1,11 +1,11 @@
state 0 state 0
$accept: .compstmt $end $accept: .compstmt $end
opt_terms: . (30) opt_terms: . (34)
';' shift 6 ';' shift 6
'\n' shift 7 '\n' shift 7
. reduce 30 (src line 173) . reduce 34 (src line 192)
compstmt goto 1 compstmt goto 1
stmts goto 3 stmts goto 3
@@ -31,7 +31,7 @@ state 2
'!' shift 13 '!' shift 13
'^' shift 14 '^' shift 14
'(' shift 16 '(' shift 16
. reduce 1 (src line 42) . reduce 1 (src line 44)
stmt goto 8 stmt goto 8
expr goto 9 expr goto 9
@@ -39,48 +39,48 @@ state 2
state 3 state 3
compstmt: stmts.opt_terms compstmt: stmts.opt_terms
stmts: stmts.terms stmt stmts: stmts.terms stmt
opt_terms: . (30) opt_terms: . (34)
';' shift 6 ';' shift 6
'\n' shift 7 '\n' shift 7
. reduce 30 (src line 173) . reduce 34 (src line 192)
opt_terms goto 17 opt_terms goto 17
terms goto 18 terms goto 18
term goto 5 term goto 5
state 4 state 4
opt_terms: terms. (31) opt_terms: terms. (35)
terms: terms.term terms: terms.term
';' shift 6 ';' shift 6
'\n' shift 7 '\n' shift 7
. reduce 31 (src line 174) . reduce 35 (src line 193)
term goto 19 term goto 19
state 5 state 5
terms: term. (32) terms: term. (36)
. reduce 32 (src line 178) . reduce 36 (src line 197)
state 6 state 6
term: ';'. (34) term: ';'. (38)
. reduce 34 (src line 186) . reduce 38 (src line 205)
state 7 state 7
term: '\n'. (35) term: '\n'. (39)
. reduce 35 (src line 189) . reduce 39 (src line 208)
state 8 state 8
stmts: opt_terms stmt. (3) stmts: opt_terms stmt. (3)
. reduce 3 (src line 51) . reduce 3 (src line 53)
state 9 state 9
@@ -120,19 +120,21 @@ state 9
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 6 (src line 74) . reduce 6 (src line 76)
state 10 state 10
expr: IDENT. (7) expr: IDENT. (7)
expr: IDENT.'(' exprs ')'
. reduce 7 (src line 79) '(' shift 37
. reduce 7 (src line 81)
state 11 state 11
expr: NUMBER. (8) expr: NUMBER. (8)
. reduce 8 (src line 84) . reduce 8 (src line 86)
state 12 state 12
@@ -147,7 +149,7 @@ state 12
'(' shift 16 '(' shift 16
. error . error
expr goto 37 expr goto 38
state 13 state 13
expr: '!'.expr expr: '!'.expr
@@ -161,7 +163,7 @@ state 13
'(' shift 16 '(' shift 16
. error . error
expr goto 38 expr goto 39
state 14 state 14
expr: '^'.expr expr: '^'.expr
@@ -175,12 +177,12 @@ state 14
'(' shift 16 '(' shift 16
. error . error
expr goto 39 expr goto 40
state 15 state 15
expr: STRING. (12) expr: STRING. (12)
. reduce 12 (src line 100) . reduce 12 (src line 102)
state 16 state 16
@@ -195,17 +197,17 @@ state 16
'(' shift 16 '(' shift 16
. error . error
expr goto 40 expr goto 41
state 17 state 17
compstmt: stmts opt_terms. (2) compstmt: stmts opt_terms. (2)
. reduce 2 (src line 46) . reduce 2 (src line 48)
state 18 state 18
stmts: stmts terms.stmt stmts: stmts terms.stmt
opt_terms: terms. (31) opt_terms: terms. (35)
terms: terms.term terms: terms.term
IDENT shift 10 IDENT shift 10
@@ -217,16 +219,16 @@ state 18
'(' shift 16 '(' shift 16
';' shift 6 ';' shift 6
'\n' shift 7 '\n' shift 7
. reduce 31 (src line 174) . reduce 35 (src line 193)
stmt goto 41 stmt goto 42
expr goto 9 expr goto 9
term goto 19 term goto 19
state 19 state 19
terms: terms term. (33) terms: terms term. (37)
. reduce 33 (src line 181) . reduce 37 (src line 200)
state 20 state 20
@@ -241,7 +243,7 @@ state 20
'(' shift 16 '(' shift 16
. error . error
expr goto 42 expr goto 43
state 21 state 21
expr: expr '?'.expr ':' expr expr: expr '?'.expr ':' expr
@@ -255,7 +257,7 @@ state 21
'(' shift 16 '(' shift 16
. error . error
expr goto 43 expr goto 44
state 22 state 22
expr: expr '+'.expr expr: expr '+'.expr
@@ -269,7 +271,7 @@ state 22
'(' shift 16 '(' shift 16
. error . error
expr goto 44 expr goto 45
state 23 state 23
expr: expr '-'.expr expr: expr '-'.expr
@@ -283,7 +285,7 @@ state 23
'(' shift 16 '(' shift 16
. error . error
expr goto 45 expr goto 46
state 24 state 24
expr: expr '*'.expr expr: expr '*'.expr
@@ -297,7 +299,7 @@ state 24
'(' shift 16 '(' shift 16
. error . error
expr goto 46 expr goto 47
state 25 state 25
expr: expr '/'.expr expr: expr '/'.expr
@@ -311,7 +313,7 @@ state 25
'(' shift 16 '(' shift 16
. error . error
expr goto 47 expr goto 48
state 26 state 26
expr: expr '%'.expr expr: expr '%'.expr
@@ -325,7 +327,7 @@ state 26
'(' shift 16 '(' shift 16
. error . error
expr goto 48 expr goto 49
state 27 state 27
expr: expr EQEQ.expr expr: expr EQEQ.expr
@@ -339,7 +341,7 @@ state 27
'(' shift 16 '(' shift 16
. error . error
expr goto 49 expr goto 50
state 28 state 28
expr: expr NEQ.expr expr: expr NEQ.expr
@@ -353,7 +355,7 @@ state 28
'(' shift 16 '(' shift 16
. error . error
expr goto 50 expr goto 51
state 29 state 29
expr: expr '>'.expr expr: expr '>'.expr
@@ -367,7 +369,7 @@ state 29
'(' shift 16 '(' shift 16
. error . error
expr goto 51 expr goto 52
state 30 state 30
expr: expr GE.expr expr: expr GE.expr
@@ -381,7 +383,7 @@ state 30
'(' shift 16 '(' shift 16
. error . error
expr goto 52 expr goto 53
state 31 state 31
expr: expr '<'.expr expr: expr '<'.expr
@@ -395,7 +397,7 @@ state 31
'(' shift 16 '(' shift 16
. error . error
expr goto 53 expr goto 54
state 32 state 32
expr: expr LE.expr expr: expr LE.expr
@@ -409,7 +411,7 @@ state 32
'(' shift 16 '(' shift 16
. error . error
expr goto 54 expr goto 55
state 33 state 33
expr: expr '|'.expr expr: expr '|'.expr
@@ -423,7 +425,7 @@ state 33
'(' shift 16 '(' shift 16
. error . error
expr goto 55 expr goto 56
state 34 state 34
expr: expr OROR.expr expr: expr OROR.expr
@@ -437,7 +439,7 @@ state 34
'(' shift 16 '(' shift 16
. error . error
expr goto 56 expr goto 57
state 35 state 35
expr: expr '&'.expr expr: expr '&'.expr
@@ -451,7 +453,7 @@ state 35
'(' shift 16 '(' shift 16
. error . error
expr goto 57 expr goto 58
state 36 state 36
expr: expr ANDAND.expr expr: expr ANDAND.expr
@@ -465,11 +467,27 @@ state 36
'(' shift 16 '(' shift 16
. error . error
expr goto 58 expr goto 59
37: shift/reduce conflict (shift 33(0), red'n 9(10)) on '|'
37: shift/reduce conflict (shift 35(0), red'n 9(10)) on '&'
state 37 state 37
expr: IDENT '('.exprs ')'
exprs: . (31)
IDENT shift 10
NUMBER shift 11
STRING shift 15
'-' shift 12
'!' shift 13
'^' shift 14
'(' shift 16
. reduce 31 (src line 179)
expr goto 61
exprs goto 60
38: shift/reduce conflict (shift 33(0), red'n 9(10)) on '|'
38: shift/reduce conflict (shift 35(0), red'n 9(10)) on '&'
state 38
expr: '-' expr. (9) expr: '-' expr. (9)
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
@@ -490,12 +508,12 @@ state 37
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 9 (src line 88) . reduce 9 (src line 90)
38: shift/reduce conflict (shift 33(0), red'n 10(10)) on '|' 39: shift/reduce conflict (shift 33(0), red'n 10(10)) on '|'
38: shift/reduce conflict (shift 35(0), red'n 10(10)) on '&' 39: shift/reduce conflict (shift 35(0), red'n 10(10)) on '&'
state 38 state 39
expr: '!' expr. (10) expr: '!' expr. (10)
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
@@ -516,12 +534,12 @@ state 38
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 10 (src line 92) . reduce 10 (src line 94)
39: shift/reduce conflict (shift 33(0), red'n 11(10)) on '|' 40: shift/reduce conflict (shift 33(0), red'n 11(10)) on '|'
39: shift/reduce conflict (shift 35(0), red'n 11(10)) on '&' 40: shift/reduce conflict (shift 35(0), red'n 11(10)) on '&'
state 39 state 40
expr: '^' expr. (11) expr: '^' expr. (11)
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
@@ -542,10 +560,10 @@ state 39
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 11 (src line 96) . reduce 11 (src line 98)
state 40 state 41
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: '(' expr.')' expr: '(' expr.')'
expr: expr.'+' expr expr: expr.'+' expr
@@ -578,19 +596,19 @@ state 40
'*' shift 24 '*' shift 24
'/' shift 25 '/' shift 25
'%' shift 26 '%' shift 26
')' shift 59 ')' shift 62
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. error . error
state 41 state 42
stmts: stmts terms stmt. (4) stmts: stmts terms stmt. (4)
. reduce 4 (src line 59) . reduce 4 (src line 61)
state 42 state 43
stmt: expr '=' expr. (5) stmt: expr '=' expr. (5)
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
@@ -625,10 +643,10 @@ state 42
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 5 (src line 69) . reduce 5 (src line 71)
state 43 state 44
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr '?' expr.':' expr expr: expr '?' expr.':' expr
expr: expr.'+' expr expr: expr.'+' expr
@@ -654,7 +672,7 @@ state 43
OROR shift 34 OROR shift 34
ANDAND shift 36 ANDAND shift 36
'?' shift 21 '?' shift 21
':' shift 60 ':' shift 63
'>' shift 29 '>' shift 29
'<' shift 31 '<' shift 31
'+' shift 22 '+' shift 22
@@ -667,9 +685,9 @@ state 43
. error . error
44: shift/reduce conflict (shift 33(0), red'n 15(8)) on '|' 45: shift/reduce conflict (shift 33(0), red'n 15(8)) on '|'
44: shift/reduce conflict (shift 35(0), red'n 15(8)) on '&' 45: shift/reduce conflict (shift 35(0), red'n 15(8)) on '&'
state 44 state 45
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr '+' expr. (15) expr: expr '+' expr. (15)
@@ -693,12 +711,12 @@ state 44
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 15 (src line 112) . reduce 15 (src line 114)
45: shift/reduce conflict (shift 33(0), red'n 16(8)) on '|' 46: shift/reduce conflict (shift 33(0), red'n 16(8)) on '|'
45: shift/reduce conflict (shift 35(0), red'n 16(8)) on '&' 46: shift/reduce conflict (shift 35(0), red'n 16(8)) on '&'
state 45 state 46
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -722,12 +740,12 @@ state 45
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 16 (src line 116) . reduce 16 (src line 118)
46: shift/reduce conflict (shift 33(0), red'n 17(9)) on '|' 47: shift/reduce conflict (shift 33(0), red'n 17(9)) on '|'
46: shift/reduce conflict (shift 35(0), red'n 17(9)) on '&' 47: shift/reduce conflict (shift 35(0), red'n 17(9)) on '&'
state 46 state 47
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -748,12 +766,12 @@ state 46
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 17 (src line 120) . reduce 17 (src line 122)
47: shift/reduce conflict (shift 33(0), red'n 18(9)) on '|' 48: shift/reduce conflict (shift 33(0), red'n 18(9)) on '|'
47: shift/reduce conflict (shift 35(0), red'n 18(9)) on '&' 48: shift/reduce conflict (shift 35(0), red'n 18(9)) on '&'
state 47 state 48
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -774,12 +792,12 @@ state 47
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 18 (src line 124) . reduce 18 (src line 126)
48: shift/reduce conflict (shift 33(0), red'n 19(9)) on '|' 49: shift/reduce conflict (shift 33(0), red'n 19(9)) on '|'
48: shift/reduce conflict (shift 35(0), red'n 19(9)) on '&' 49: shift/reduce conflict (shift 35(0), red'n 19(9)) on '&'
state 48 state 49
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -800,12 +818,12 @@ state 48
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 19 (src line 128) . reduce 19 (src line 130)
49: shift/reduce conflict (shift 33(0), red'n 20(6)) on '|' 50: shift/reduce conflict (shift 33(0), red'n 20(6)) on '|'
49: shift/reduce conflict (shift 35(0), red'n 20(6)) on '&' 50: shift/reduce conflict (shift 35(0), red'n 20(6)) on '&'
state 49 state 50
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -837,12 +855,12 @@ state 49
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 20 (src line 132) . reduce 20 (src line 134)
50: shift/reduce conflict (shift 33(0), red'n 21(6)) on '|' 51: shift/reduce conflict (shift 33(0), red'n 21(6)) on '|'
50: shift/reduce conflict (shift 35(0), red'n 21(6)) on '&' 51: shift/reduce conflict (shift 35(0), red'n 21(6)) on '&'
state 50 state 51
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -874,12 +892,12 @@ state 50
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 21 (src line 136) . reduce 21 (src line 138)
51: shift/reduce conflict (shift 33(0), red'n 22(7)) on '|' 52: shift/reduce conflict (shift 33(0), red'n 22(7)) on '|'
51: shift/reduce conflict (shift 35(0), red'n 22(7)) on '&' 52: shift/reduce conflict (shift 35(0), red'n 22(7)) on '&'
state 51 state 52
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -905,12 +923,12 @@ state 51
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 22 (src line 140) . reduce 22 (src line 142)
52: shift/reduce conflict (shift 33(0), red'n 23(7)) on '|' 53: shift/reduce conflict (shift 33(0), red'n 23(7)) on '|'
52: shift/reduce conflict (shift 35(0), red'n 23(7)) on '&' 53: shift/reduce conflict (shift 35(0), red'n 23(7)) on '&'
state 52 state 53
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -936,12 +954,12 @@ state 52
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 23 (src line 144) . reduce 23 (src line 146)
53: shift/reduce conflict (shift 33(0), red'n 24(7)) on '|' 54: shift/reduce conflict (shift 33(0), red'n 24(7)) on '|'
53: shift/reduce conflict (shift 35(0), red'n 24(7)) on '&' 54: shift/reduce conflict (shift 35(0), red'n 24(7)) on '&'
state 53 state 54
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -967,12 +985,12 @@ state 53
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 24 (src line 148) . reduce 24 (src line 150)
54: shift/reduce conflict (shift 33(0), red'n 25(7)) on '|' 55: shift/reduce conflict (shift 33(0), red'n 25(7)) on '|'
54: shift/reduce conflict (shift 35(0), red'n 25(7)) on '&' 55: shift/reduce conflict (shift 35(0), red'n 25(7)) on '&'
state 54 state 55
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -998,26 +1016,26 @@ state 54
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 25 (src line 152) . reduce 25 (src line 154)
55: shift/reduce conflict (shift 27(6), red'n 26(0)) on EQEQ 56: shift/reduce conflict (shift 27(6), red'n 26(0)) on EQEQ
55: shift/reduce conflict (shift 28(6), red'n 26(0)) on NEQ 56: shift/reduce conflict (shift 28(6), red'n 26(0)) on NEQ
55: shift/reduce conflict (shift 30(7), red'n 26(0)) on GE 56: shift/reduce conflict (shift 30(7), red'n 26(0)) on GE
55: shift/reduce conflict (shift 32(7), red'n 26(0)) on LE 56: shift/reduce conflict (shift 32(7), red'n 26(0)) on LE
55: shift/reduce conflict (shift 34(3), red'n 26(0)) on OROR 56: shift/reduce conflict (shift 34(3), red'n 26(0)) on OROR
55: shift/reduce conflict (shift 36(4), red'n 26(0)) on ANDAND 56: shift/reduce conflict (shift 36(4), red'n 26(0)) on ANDAND
55: shift/reduce conflict (shift 21(2), red'n 26(0)) on '?' 56: shift/reduce conflict (shift 21(2), red'n 26(0)) on '?'
55: shift/reduce conflict (shift 29(7), red'n 26(0)) on '>' 56: shift/reduce conflict (shift 29(7), red'n 26(0)) on '>'
55: shift/reduce conflict (shift 31(7), red'n 26(0)) on '<' 56: shift/reduce conflict (shift 31(7), red'n 26(0)) on '<'
55: shift/reduce conflict (shift 22(8), red'n 26(0)) on '+' 56: shift/reduce conflict (shift 22(8), red'n 26(0)) on '+'
55: shift/reduce conflict (shift 23(8), red'n 26(0)) on '-' 56: shift/reduce conflict (shift 23(8), red'n 26(0)) on '-'
55: shift/reduce conflict (shift 24(9), red'n 26(0)) on '*' 56: shift/reduce conflict (shift 24(9), red'n 26(0)) on '*'
55: shift/reduce conflict (shift 25(9), red'n 26(0)) on '/' 56: shift/reduce conflict (shift 25(9), red'n 26(0)) on '/'
55: shift/reduce conflict (shift 26(9), red'n 26(0)) on '%' 56: shift/reduce conflict (shift 26(9), red'n 26(0)) on '%'
55: shift/reduce conflict (shift 33(0), red'n 26(0)) on '|' 56: shift/reduce conflict (shift 33(0), red'n 26(0)) on '|'
55: shift/reduce conflict (shift 35(0), red'n 26(0)) on '&' 56: shift/reduce conflict (shift 35(0), red'n 26(0)) on '&'
state 55 state 56
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -1052,12 +1070,12 @@ state 55
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 26 (src line 156) . reduce 26 (src line 158)
56: shift/reduce conflict (shift 33(0), red'n 27(3)) on '|' 57: shift/reduce conflict (shift 33(0), red'n 27(3)) on '|'
56: shift/reduce conflict (shift 35(0), red'n 27(3)) on '&' 57: shift/reduce conflict (shift 35(0), red'n 27(3)) on '&'
state 56 state 57
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -1090,26 +1108,26 @@ state 56
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 27 (src line 160) . reduce 27 (src line 162)
57: shift/reduce conflict (shift 27(6), red'n 28(0)) on EQEQ 58: shift/reduce conflict (shift 27(6), red'n 28(0)) on EQEQ
57: shift/reduce conflict (shift 28(6), red'n 28(0)) on NEQ 58: shift/reduce conflict (shift 28(6), red'n 28(0)) on NEQ
57: shift/reduce conflict (shift 30(7), red'n 28(0)) on GE 58: shift/reduce conflict (shift 30(7), red'n 28(0)) on GE
57: shift/reduce conflict (shift 32(7), red'n 28(0)) on LE 58: shift/reduce conflict (shift 32(7), red'n 28(0)) on LE
57: shift/reduce conflict (shift 34(3), red'n 28(0)) on OROR 58: shift/reduce conflict (shift 34(3), red'n 28(0)) on OROR
57: shift/reduce conflict (shift 36(4), red'n 28(0)) on ANDAND 58: shift/reduce conflict (shift 36(4), red'n 28(0)) on ANDAND
57: shift/reduce conflict (shift 21(2), red'n 28(0)) on '?' 58: shift/reduce conflict (shift 21(2), red'n 28(0)) on '?'
57: shift/reduce conflict (shift 29(7), red'n 28(0)) on '>' 58: shift/reduce conflict (shift 29(7), red'n 28(0)) on '>'
57: shift/reduce conflict (shift 31(7), red'n 28(0)) on '<' 58: shift/reduce conflict (shift 31(7), red'n 28(0)) on '<'
57: shift/reduce conflict (shift 22(8), red'n 28(0)) on '+' 58: shift/reduce conflict (shift 22(8), red'n 28(0)) on '+'
57: shift/reduce conflict (shift 23(8), red'n 28(0)) on '-' 58: shift/reduce conflict (shift 23(8), red'n 28(0)) on '-'
57: shift/reduce conflict (shift 24(9), red'n 28(0)) on '*' 58: shift/reduce conflict (shift 24(9), red'n 28(0)) on '*'
57: shift/reduce conflict (shift 25(9), red'n 28(0)) on '/' 58: shift/reduce conflict (shift 25(9), red'n 28(0)) on '/'
57: shift/reduce conflict (shift 26(9), red'n 28(0)) on '%' 58: shift/reduce conflict (shift 26(9), red'n 28(0)) on '%'
57: shift/reduce conflict (shift 33(0), red'n 28(0)) on '|' 58: shift/reduce conflict (shift 33(0), red'n 28(0)) on '|'
57: shift/reduce conflict (shift 35(0), red'n 28(0)) on '&' 58: shift/reduce conflict (shift 35(0), red'n 28(0)) on '&'
state 57 state 58
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -1144,12 +1162,12 @@ state 57
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 28 (src line 164) . reduce 28 (src line 166)
58: shift/reduce conflict (shift 33(0), red'n 29(4)) on '|' 59: shift/reduce conflict (shift 33(0), red'n 29(4)) on '|'
58: shift/reduce conflict (shift 35(0), red'n 29(4)) on '&' 59: shift/reduce conflict (shift 35(0), red'n 29(4)) on '&'
state 58 state 59
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr.'+' expr expr: expr.'+' expr
expr: expr.'-' expr expr: expr.'-' expr
@@ -1181,16 +1199,63 @@ state 58
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 29 (src line 168) . reduce 29 (src line 170)
state 59
expr: '(' expr ')'. (14)
. reduce 14 (src line 108)
state 60 state 60
expr: IDENT '(' exprs.')'
exprs: exprs.',' expr
',' shift 65
')' shift 64
. error
state 61
expr: expr.'?' expr ':' expr
expr: expr.'+' expr
expr: expr.'-' expr
expr: expr.'*' expr
expr: expr.'/' expr
expr: expr.'%' expr
expr: expr.EQEQ expr
expr: expr.NEQ expr
expr: expr.'>' expr
expr: expr.GE expr
expr: expr.'<' expr
expr: expr.LE expr
expr: expr.'|' expr
expr: expr.OROR expr
expr: expr.'&' expr
expr: expr.ANDAND expr
exprs: expr. (32)
EQEQ shift 27
NEQ shift 28
GE shift 30
LE shift 32
OROR shift 34
ANDAND shift 36
'?' shift 21
'>' shift 29
'<' shift 31
'+' shift 22
'-' shift 23
'*' shift 24
'/' shift 25
'%' shift 26
'|' shift 33
'&' shift 35
. reduce 32 (src line 183)
state 62
expr: '(' expr ')'. (14)
. reduce 14 (src line 110)
state 63
expr: expr '?' expr ':'.expr expr: expr '?' expr ':'.expr
IDENT shift 10 IDENT shift 10
@@ -1202,11 +1267,31 @@ state 60
'(' shift 16 '(' shift 16
. error . error
expr goto 61 expr goto 66
61: shift/reduce conflict (shift 33(0), red'n 13(2)) on '|' state 64
61: shift/reduce conflict (shift 35(0), red'n 13(2)) on '&' expr: IDENT '(' exprs ')'. (30)
state 61
. reduce 30 (src line 174)
state 65
exprs: exprs ','.expr
IDENT shift 10
NUMBER shift 11
STRING shift 15
'-' shift 12
'!' shift 13
'^' shift 14
'(' shift 16
. error
expr goto 67
66: shift/reduce conflict (shift 33(0), red'n 13(2)) on '|'
66: shift/reduce conflict (shift 35(0), red'n 13(2)) on '&'
state 66
expr: expr.'?' expr ':' expr expr: expr.'?' expr ':' expr
expr: expr '?' expr ':' expr. (13) expr: expr '?' expr ':' expr. (13)
expr: expr.'+' expr expr: expr.'+' expr
@@ -1241,18 +1326,56 @@ state 61
'%' shift 26 '%' shift 26
'|' shift 33 '|' shift 33
'&' shift 35 '&' shift 35
. reduce 13 (src line 104) . reduce 13 (src line 106)
37 terminals, 8 nonterminals state 67
36 grammar rules, 62/8000 states expr: expr.'?' expr ':' expr
expr: expr.'+' expr
expr: expr.'-' expr
expr: expr.'*' expr
expr: expr.'/' expr
expr: expr.'%' expr
expr: expr.EQEQ expr
expr: expr.NEQ expr
expr: expr.'>' expr
expr: expr.GE expr
expr: expr.'<' expr
expr: expr.LE expr
expr: expr.'|' expr
expr: expr.OROR expr
expr: expr.'&' expr
expr: expr.ANDAND expr
exprs: exprs ',' expr. (33)
EQEQ shift 27
NEQ shift 28
GE shift 30
LE shift 32
OROR shift 34
ANDAND shift 36
'?' shift 21
'>' shift 29
'<' shift 31
'+' shift 22
'-' shift 23
'*' shift 24
'/' shift 25
'%' shift 26
'|' shift 33
'&' shift 35
. reduce 33 (src line 187)
37 terminals, 9 nonterminals
40 grammar rules, 68/2000 states
66 shift/reduce, 0 reduce/reduce conflicts reported 66 shift/reduce, 0 reduce/reduce conflicts reported
57 working sets used 58 working sets used
memory: parser 35/120000 memory: parser 38/30000
46 extra closures 49 extra closures
390 shift entries, 5 exceptions 439 shift entries, 5 exceptions
34 goto entries 37 goto entries
2 entries saved by goto default 2 entries saved by goto default
Optimizer space used: output 249/120000 Optimizer space used: output 251/30000
249 table entries, 78 zero 251 table entries, 74 zero
maximum spread: 37, maximum offset: 60 maximum spread: 37, maximum offset: 65

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"os"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@@ -39,14 +40,6 @@ func (e *Error) Error() string {
// Func is function interface to reflect functions internaly. // Func is function interface to reflect functions internaly.
type Func func(args ...reflect.Value) (reflect.Value, error) 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. // Run executes statements in the specified environment.
func Run(stmts []ast.Stmt, env *Env) (reflect.Value, error) { func Run(stmts []ast.Stmt, env *Env) (reflect.Value, error) {
rv := NilValue rv := NilValue
@@ -386,6 +379,80 @@ func invokeExpr(expr ast.Expr, env *Env) (reflect.Value, error) {
default: default:
return NilValue, errors.New("Unknown operator") return NilValue, errors.New("Unknown operator")
} }
case *ast.CallExpr:
f, err := env.Get(e.Name)
if err != nil {
return f, err
}
args := []reflect.Value{}
for i, expr := range e.SubExprs {
arg, err := invokeExpr(expr, env)
if err != nil {
return arg, err
}
if i < f.Type().NumIn() {
if !f.Type().IsVariadic() {
it := f.Type().In(i)
if arg.Kind().String() == "unsafe.Pointer" {
arg = reflect.New(it).Elem()
}
if arg.Kind() != it.Kind() && arg.IsValid() && arg.Type().ConvertibleTo(it) {
arg = arg.Convert(it)
} else if arg.Kind() == reflect.Func {
if _, isFunc := arg.Interface().(Func); isFunc {
rfunc := arg
arg = reflect.MakeFunc(it, func(args []reflect.Value) []reflect.Value {
for i := range args {
args[i] = reflect.ValueOf(args[i])
}
return rfunc.Call(args)[:it.NumOut()]
})
}
} else if !arg.IsValid() {
arg = reflect.Zero(it)
}
}
}
if !arg.IsValid() {
arg = NilValue
}
args = append(args, arg)
}
ret := NilValue
fnc := func() {
defer func() {
if os.Getenv("KINAKO_DEBUG") == "" {
if ex := recover(); ex != nil {
if e, ok := ex.(error); ok {
err = e
} else {
err = errors.New(fmt.Sprint(ex))
}
}
}
}()
if f.Kind() == reflect.Interface {
f = f.Elem()
}
rets := f.Call(args)
if f.Type().NumOut() == 1 {
ret = rets[0]
} else {
var result []interface{}
for _, r := range rets {
result = append(result, r.Interface())
}
ret = reflect.ValueOf(result)
}
}
fnc()
if err != nil {
return ret, err
}
return ret, nil
case *ast.TernaryOpExpr: case *ast.TernaryOpExpr:
rv, err := invokeExpr(e.Expr, env) rv, err := invokeExpr(e.Expr, env)
if err != nil { if err != nil {

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

@@ -0,0 +1,54 @@
package vm
import (
"reflect"
"testing"
)
func TestExecute(t *testing.T) {
e := NewEnv()
e.Define("foo", int64(1))
e.Define("bar", int64(2))
e.Define("baz", int64(3))
tests := []struct {
input string
want interface{}
}{
{
input: "foo+bar",
want: int64(3),
},
{
input: "foo-bar",
want: int64(-1),
},
{
input: "foo*bar",
want: int64(2),
},
{
input: "foo/bar",
want: float64(0.5),
},
{
input: "baz*(foo+bar)",
want: int64(9),
},
{
input: "baz > 2 ? foo : bar",
want: int64(1),
},
}
for _, tt := range tests {
r, err := e.Execute(tt.input)
if err != nil {
t.Fatal(err)
}
got := r.Interface()
if !reflect.DeepEqual(got, tt.want) {
t.Fatalf("want %v, but %v:", tt.want, got)
}
}
}