<commit_before>package main

import (
	"fmt"
	"path/filepath"
	"time"

	"github.com/go-errors/errors"
	"github.com/itchio/butler/comm"
	"github.com/winlabs/gowin32"
)

func msiInfo(msiPath string) {
	must(doMsiInfo(msiPath))
}

/**
 * MSIInfoResult describes an MSI package's properties
 */
type MSIInfoResult struct {
	ProductCode  string `json:"productCode"`
	InstallState string `json:"installState"`
}

func doMsiInfo(msiPath string) error {
	initMsi()

	msiPath, err := filepath.Abs(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	pkg, err := gowin32.OpenInstallerPackage(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	defer pkg.Close()

	productCode, err := pkg.GetProductProperty("ProductCode")
	if err != nil {
		return errors.Wrap(err, 0)
	}
	comm.Debugf("Product code for %s: %s", msiPath, productCode)

	state := gowin32.GetInstalledProductState(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Debugf("Installed product state: %s", installStateToString(state))

	if *appArgs.json {
		comm.Result(&MSIInfoResult{
			ProductCode:  productCode,
			InstallState: installStateToString(state),
		})
	} else {
		comm.Statf("MSI product code: %s", productCode)
		comm.Statf("Install state: %s", installStateToString(state))
	}

	return nil
}

func msiProductInfo(productCode string) {
	must(doMsiProductInfo(productCode))
}

func doMsiProductInfo(productCode string) error {
	initMsi()

	state := gowin32.GetInstalledProductState(productCode)

	comm.Logf("Installed product state: %s", installStateToString(state))

	if *appArgs.json {
		comm.Result(&MSIInfoResult{
			ProductCode:  productCode,
			InstallState: installStateToString(state),
		})
	}

	return nil
}

func msiInstall(msiPath string, logPath string, target string) {
	must(doMsiInstall(msiPath, logPath, target))
}

func doMsiInstall(msiPath string, logPath string, target string) error {
	initMsi()

	startTime := time.Now()

	msiPath, err := filepath.Abs(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Debugf("Assessing state of %s", msiPath)

	var productCode string

	err = func() error {
		pkg, err := gowin32.OpenInstallerPackage(msiPath)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		defer pkg.Close()

		productCode, err = pkg.GetProductProperty("ProductCode")
		if err != nil {
			return errors.Wrap(err, 0)
		}
		comm.Debugf("Product code for %s: %s", msiPath, productCode)
		return nil
	}()
	if err != nil {
		return err
	}

	state := gowin32.GetInstalledProductState(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	repair := false
	if state == gowin32.InstallStateDefault {
		comm.Opf("Already installed, repairing from %s", msiPath)
		repair = true
	} else {
		comm.Opf("Installing %s", msiPath)
	}

	if logPath != "" {
		// equivalent to "/lv"
		logMode := gowin32.InstallLogModeVerbose
		logAttr := gowin32.InstallLogAttributesFlushEachLine
		err := gowin32.EnableInstallerLog(logMode, logPath, logAttr)
		if err != nil {
			return errors.Wrap(err, 0)
		}
		defer func() {
			gowin32.DisableInstallerLog()
		}()

		comm.Debugf("...will write log to %s", logPath)
	}

	// s = Recreate all shortcuts
	// m = Rewrite all HKLM and HKCR registry entries
	// u = Rewrite all HKCU and HKU registry entries
	// p = Reinstall only if the file is missing
	// We can't use "o", "e", "d", "c" (reinstall older, equal-or-older, different, checksum)
	// because they require the source package to be available
	// (which isn't always true, see https://github.com/itchio/itch/issues/1304)
	// We won't use "a" (reinstall all) because it's overkill.
	commandLine := "REINSTALLMODE=smup REBOOT=reallysuppress"

	if target != "" {
		// throw everything we got to try and get a local install
		commandLine += " ALLUSERS=2 MSIINSTALLPERUSER=1"
		commandLine += fmt.Sprintf(" TARGETDIR=\"%s\" INSTALLDIR=\"%s\" APPDIR=\"%s\"", target, target, target)
		comm.Debugf("...will install in folder %s", target)
	}

	comm.Debugf("Final command line: %s", commandLine)

	if repair {
		ilvl := gowin32.InstallLevelDefault
		istate := gowin32.InstallStateDefault
		err = gowin32.ConfigureInstalledProduct(productCode, ilvl, istate, commandLine)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		comm.Opf("Repaired in %s", time.Since(startTime))
	} else {
		err = gowin32.InstallProduct(msiPath, commandLine)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		comm.Opf("Installed in %s", time.Since(startTime))
	}

	return nil
}

func msiUninstall(productCode string) {
	must(doMsiUninstall(productCode))
}

func doMsiUninstall(productCode string) error {
	initMsi()

	comm.Opf("Uninstalling product %s", productCode)

	startTime := time.Now()

	err := gowin32.UninstallProduct(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Statf("Uninstalled in %s", time.Since(startTime))

	return nil
}
<commit_msg>Allow passing msi file to butler command-line<commit_after>package main

import (
	"fmt"
	"path/filepath"
	"strings"
	"time"

	"github.com/go-errors/errors"
	"github.com/itchio/butler/comm"
	"github.com/winlabs/gowin32"
)

func msiInfo(msiPath string) {
	must(doMsiInfo(msiPath))
}

/**
 * MSIInfoResult describes an MSI package's properties
 */
type MSIInfoResult struct {
	ProductCode  string `json:"productCode"`
	InstallState string `json:"installState"`
}

func doMsiInfo(msiPath string) error {
	initMsi()

	msiPath, err := filepath.Abs(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	pkg, err := gowin32.OpenInstallerPackage(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	defer pkg.Close()

	productCode, err := pkg.GetProductProperty("ProductCode")
	if err != nil {
		return errors.Wrap(err, 0)
	}
	comm.Debugf("Product code for %s: %s", msiPath, productCode)

	state := gowin32.GetInstalledProductState(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Debugf("Installed product state: %s", installStateToString(state))

	if *appArgs.json {
		comm.Result(&MSIInfoResult{
			ProductCode:  productCode,
			InstallState: installStateToString(state),
		})
	} else {
		comm.Statf("MSI product code: %s", productCode)
		comm.Statf("Install state: %s", installStateToString(state))
	}

	return nil
}

func msiProductInfo(productCode string) {
	must(doMsiProductInfo(productCode))
}

func doMsiProductInfo(productCode string) error {
	initMsi()

	state := gowin32.GetInstalledProductState(productCode)

	comm.Logf("Installed product state: %s", installStateToString(state))

	if *appArgs.json {
		comm.Result(&MSIInfoResult{
			ProductCode:  productCode,
			InstallState: installStateToString(state),
		})
	}

	return nil
}

func msiInstall(msiPath string, logPath string, target string) {
	must(doMsiInstall(msiPath, logPath, target))
}

func doMsiInstall(msiPath string, logPath string, target string) error {
	initMsi()

	startTime := time.Now()

	msiPath, err := filepath.Abs(msiPath)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Debugf("Assessing state of %s", msiPath)

	var productCode string

	err = func() error {
		pkg, err := gowin32.OpenInstallerPackage(msiPath)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		defer pkg.Close()

		productCode, err = pkg.GetProductProperty("ProductCode")
		if err != nil {
			return errors.Wrap(err, 0)
		}
		comm.Debugf("Product code for %s: %s", msiPath, productCode)
		return nil
	}()
	if err != nil {
		return err
	}

	state := gowin32.GetInstalledProductState(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	repair := false
	if state == gowin32.InstallStateDefault {
		comm.Opf("Already installed, repairing from %s", msiPath)
		repair = true
	} else {
		comm.Opf("Installing %s", msiPath)
	}

	if logPath != "" {
		// equivalent to "/lv"
		logMode := gowin32.InstallLogModeVerbose
		logAttr := gowin32.InstallLogAttributesFlushEachLine
		err := gowin32.EnableInstallerLog(logMode, logPath, logAttr)
		if err != nil {
			return errors.Wrap(err, 0)
		}
		defer func() {
			gowin32.DisableInstallerLog()
		}()

		comm.Debugf("...will write log to %s", logPath)
	}

	// s = Recreate all shortcuts
	// m = Rewrite all HKLM and HKCR registry entries
	// u = Rewrite all HKCU and HKU registry entries
	// p = Reinstall only if the file is missing
	// We can't use "o", "e", "d", "c" (reinstall older, equal-or-older, different, checksum)
	// because they require the source package to be available
	// (which isn't always true, see https://github.com/itchio/itch/issues/1304)
	// We won't use "a" (reinstall all) because it's overkill.
	commandLine := "REINSTALLMODE=smup REBOOT=reallysuppress"

	if target != "" {
		// throw everything we got to try and get a local install
		commandLine += " ALLUSERS=2 MSIINSTALLPERUSER=1"
		commandLine += fmt.Sprintf(" TARGETDIR=\"%s\" INSTALLDIR=\"%s\" APPDIR=\"%s\"", target, target, target)
		comm.Debugf("...will install in folder %s", target)
	}

	comm.Debugf("Final command line: %s", commandLine)

	if repair {
		ilvl := gowin32.InstallLevelDefault
		istate := gowin32.InstallStateDefault
		err = gowin32.ConfigureInstalledProduct(productCode, ilvl, istate, commandLine)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		comm.Opf("Repaired in %s", time.Since(startTime))
	} else {
		err = gowin32.InstallProduct(msiPath, commandLine)
		if err != nil {
			return errors.Wrap(err, 0)
		}

		comm.Opf("Installed in %s", time.Since(startTime))
	}

	return nil
}

func msiUninstall(productCode string) {
	must(doMsiUninstall(productCode))
}

func doMsiUninstall(productCode string) error {
	initMsi()

	if !strings.HasPrefix(productCode, "{") {
		comm.Logf("Argument doesn't look like a product ID, treating it like an MSI file")

		err := func() error {
			msiPath, err := filepath.Abs(productCode)
			if err != nil {
				return errors.Wrap(err, 0)
			}

			pkg, err := gowin32.OpenInstallerPackage(msiPath)
			if err != nil {
				return errors.Wrap(err, 0)
			}

			defer pkg.Close()

			fileProductCode, err := pkg.GetProductProperty("ProductCode")
			if err != nil {
				return errors.Wrap(err, 0)
			}
			productCode = fileProductCode
			return nil
		}()
		if err != nil {
			return err
		}
	}

	comm.Opf("Uninstalling product %s", productCode)

	startTime := time.Now()

	err := gowin32.UninstallProduct(productCode)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	comm.Statf("Uninstalled in %s", time.Since(startTime))

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package list

import (
	"net/http"
	"testing"

	"github.com/issue9/assert"
)

const countTestString = "/adfada/adfa/dd//adfadasd/ada/dfad/"

var (
	f1 = func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(1)
	}
	h1 = http.HandlerFunc(f1)
)

func TestList_Add_Remove(t *testing.T) {
	a := assert.New(t)
	l := New(false)

	a.NotError(l.Add("/posts/1", h1))
	a.NotError(l.Add("/posts/1/author", h1))
	a.NotError(l.Add("/posts/1/*", h1))
	a.Equal(l.entries[2].len(), 1)
	a.Equal(l.entries[3].len(), 1)
	a.Equal(l.entries[wildcardEntriesIndex].len(), 1)

	l.Remove("/posts/1")
	a.Equal(l.entries[2].len(), 0)
	l.Remove("/posts/1/*")
	a.Equal(l.entries[wildcardEntriesIndex].len(), 0)
}

func TestList_Clean(t *testing.T) {
	a := assert.New(t)
	l := New(false)

	a.NotError(l.Add("/posts/1", h1))
	a.NotError(l.Add("/posts/1/author", h1))
	a.NotError(l.Add("/posts/1/*", h1))
	a.NotError(l.Add("/posts/tags/*", h1))
	a.NotError(l.Add("/posts/author", h1))

	l.Clean("/posts/1")
	a.Equal(l.entries[2].len(), 1) // 还有 /posts/author
	a.Equal(l.entries[3].len(), 0)
	a.Equal(l.entries[wildcardEntriesIndex].len(), 1) // 还有 /posts/tags/*

	l.Clean("")
	a.Equal(len(l.entries), 0)
}

func TestList_Entry(t *testing.T) {
	a := assert.New(t)
	l := New(false)

	a.NotError(l.Add("/posts/1", h1))
	a.NotError(l.Add("/posts/tags/*", h1))

	a.Equal(l.entries[2].len(), 1)
	e, err := l.Entry("/posts/tags/*")
	a.NotError(err).NotNil(e)
	a.Equal(e.Pattern(), "/posts/tags/*")
	a.Equal(l.entries[2].len(), 1)

	// 不存在，自动添加
	a.Nil(l.entries[3])
	e, err = l.Entry("/posts/1/author")
	a.NotError(err).NotNil(e)
	a.Equal(e.Pattern(), "/posts/1/author")
	a.Equal(l.entries[3].len(), 1)
}

func TestList_Match(t *testing.T) {
	a := assert.New(t)
	l := New(false)
	a.NotNil(l)

	l.Add("/posts/{id}/*", h1) // 1
	l.Add("/posts/{id}/", h1)  // 2

	ety, err := l.Match("/posts/1/")
	a.NotError(err).NotNil(ety)
	a.Equal(ety.Pattern(), "/posts/{id}/")

	ety, err = l.Match("/posts/1/author")
	a.NotError(err).NotNil(ety)
	a.Equal(ety.Pattern(), "/posts/{id}/*")

	ety, err = l.Match("/posts/1/author/profile")
	a.NotError(err).NotNil(ety)
	a.Equal(ety.Pattern(), "/posts/{id}/*")
}

func TestList_entriesIndex(t *testing.T) {
	a := assert.New(t)
	l := &List{}
	a.Equal(l.entriesIndex(countTestString), 8)
	a.Equal(l.entriesIndex(countTestString+"*"), wildcardEntriesIndex)
}
<commit_msg>[internal/list] 修正测试代码错误<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package list

import (
	"net/http"
	"testing"

	"github.com/issue9/assert"
)

const countTestString = "/adfada/adfa/dd//adfadasd/ada/dfad/"

var (
	f1 = func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(1)
	}
	h1 = http.HandlerFunc(f1)
)

func TestList_Add_Remove(t *testing.T) {
	a := assert.New(t)
	l := New(false)

	a.NotError(l.Add("/posts/1", h1))
	a.NotError(l.Add("/posts/1/author", h1))
	a.NotError(l.Add("/posts/1/*", h1))
	a.Equal(l.entries[2].len(), 1)
	a.Equal(l.entries[3].len(), 1)
	a.Equal(l.entries[wildcardEntriesIndex].len(), 1)

	l.Remove("/posts/1")
	a.Equal(l.entries[2].len(), 0)
	l.Remove("/posts/1/*")
	a.Equal(l.entries[wildcardEntriesIndex].len(), 0)
}

func TestList_Clean(t *testing.T) {
	a := assert.New(t)
	l := New(false)

	a.NotError(l.Add("/posts/1", h1))
	a.NotError(l.Add("/posts/1/author", h1))
	a.NotError(l.Add("/posts/1/*", h1))
	a.NotError(l.Add("/posts/tags/*", h1))
	a.NotError(l.Add("/posts/author", h1))

	l.Clean("/posts/1")
	a.Equal(l.entries[2].len(), 1) // 还有 /posts/author
	a.Equal(l.entries[3].len(), 0)
	a.Equal(l.entries[wildcardEntriesIndex].len(), 1) // 还有 /posts/tags/*

	l.Clean("")
	a.Equal(len(l.entries), 0)
}

func TestList_Entry(t *testing.T) {
	a := assert.New(t)
	l := New(false)

	a.NotError(l.Add("/posts/1", h1))
	a.NotError(l.Add("/posts/tags/*", h1))

	a.Equal(l.entries[2].len(), 1)
	e, err := l.Entry("/posts/tags/*")
	a.NotError(err).NotNil(e)
	a.Equal(e.Pattern(), "/posts/tags/*")
	a.Equal(l.entries[2].len(), 1)

	// 不存在，自动添加
	a.Nil(l.entries[3])
	e, err = l.Entry("/posts/1/author")
	a.NotError(err).NotNil(e)
	a.Equal(e.Pattern(), "/posts/1/author")
	a.Equal(l.entries[3].len(), 1)
}

func TestList_Match(t *testing.T) {
	a := assert.New(t)
	l := New(false)
	a.NotNil(l)

	l.Add("/posts/{id}/*", h1) // 1
	l.Add("/posts/{id}/", h1)  // 2

	ety, ps := l.Match("/posts/1/")
	a.NotNil(ps).NotNil(ety)
	a.Equal(ety.Pattern(), "/posts/{id}/").
		Equal(ps, map[string]string{"id": "1"})

	ety, ps = l.Match("/posts/1/author")
	a.NotNil(ps).NotNil(ety)
	a.Equal(ety.Pattern(), "/posts/{id}/*").
		Equal(ps, map[string]string{"id": "1"})

	ety, ps = l.Match("/posts/1/author/profile")
	a.NotNil(ps).NotNil(ety)
	a.Equal(ety.Pattern(), "/posts/{id}/*").
		Equal(ps, map[string]string{"id": "1"})

	ety, ps = l.Match("/not-exists")
	a.Nil(ps).Nil(ety)
}

func TestList_entriesIndex(t *testing.T) {
	a := assert.New(t)
	l := &List{}
	a.Equal(l.entriesIndex(countTestString), 8)
	a.Equal(l.entriesIndex(countTestString+"*"), wildcardEntriesIndex)
}
<|endoftext|>


<commit_before>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cache

import (
	"context"
	"fmt"

	"golang.org/x/tools/go/packages"
	"golang.org/x/tools/internal/lsp/source"
	"golang.org/x/tools/internal/span"
)

func (v *view) loadParseTypecheck(ctx context.Context, f *goFile) ([]packages.Error, error) {
	v.mcache.mu.Lock()
	defer v.mcache.mu.Unlock()

	// If the AST for this file is trimmed, and we are explicitly type-checking it,
	// don't ignore function bodies.
	if f.astIsTrimmed() {
		v.pcache.mu.Lock()
		f.invalidateAST()
		v.pcache.mu.Unlock()
	}

	// Get the metadata for the file.
	meta, errs, err := v.checkMetadata(ctx, f)
	if err != nil {
		return errs, err
	}
	if meta == nil {
		return nil, nil
	}
	for id, m := range meta {
		imp := &importer{
			view:          v,
			seen:          make(map[packageID]struct{}),
			ctx:           ctx,
			fset:          v.session.cache.FileSet(),
			topLevelPkgID: id,
		}
		// Start prefetching direct imports.
		for importID := range m.children {
			go imp.getPkg(importID)
		}
		// Type-check package.
		pkg, err := imp.getPkg(imp.topLevelPkgID)
		if err != nil {
			return nil, err
		}
		if pkg == nil || pkg.IsIllTyped() {
			return nil, fmt.Errorf("loadParseTypecheck: %s is ill typed", m.pkgPath)
		}
	}
	if len(f.pkgs) == 0 {
		return nil, fmt.Errorf("no packages found for %s", f.URI())
	}
	return nil, nil
}

func sameSet(x, y map[packagePath]struct{}) bool {
	if len(x) != len(y) {
		return false
	}
	for k := range x {
		if _, ok := y[k]; !ok {
			return false
		}
	}
	return true
}

// checkMetadata determines if we should run go/packages.Load for this file.
// If yes, update the metadata for the file and its package.
func (v *view) checkMetadata(ctx context.Context, f *goFile) (map[packageID]*metadata, []packages.Error, error) {
	f.mu.Lock()
	defer f.mu.Unlock()

	// Save the metadata's current missing imports, if any.
	originalMissingImports := f.missingImports

	if !v.parseImports(ctx, f) {
		return f.meta, nil, nil
	}

	// Reset the file's metadata and type information if we are re-running `go list`.
	for k := range f.meta {
		delete(f.meta, k)
	}
	for k := range f.pkgs {
		delete(f.pkgs, k)
	}

	pkgs, err := packages.Load(v.buildConfig(), fmt.Sprintf("file=%s", f.filename()))
	if len(pkgs) == 0 {
		if err == nil {
			err = fmt.Errorf("go/packages.Load: no packages found for %s", f.filename())
		}
		// Return this error as a diagnostic to the user.
		return nil, []packages.Error{
			{
				Msg:  err.Error(),
				Kind: packages.UnknownError,
			},
		}, err
	}

	// Clear missing imports.
	for k := range f.missingImports {
		delete(f.missingImports, k)
	}
	for _, pkg := range pkgs {
		// If the package comes back with errors from `go list`,
		// don't bother type-checking it.
		if len(pkg.Errors) > 0 {
			return nil, pkg.Errors, fmt.Errorf("package %s has errors, skipping type-checking", pkg.PkgPath)
		}
		for importPath, importPkg := range pkg.Imports {
			if len(importPkg.Errors) > 0 {
				if f.missingImports == nil {
					f.missingImports = make(map[packagePath]struct{})
				}
				f.missingImports[packagePath(importPath)] = struct{}{}
			}
		}
		// Build the import graph for this package.
		v.link(ctx, packagePath(pkg.PkgPath), pkg, nil)
	}

	// If `go list` failed to get data for the file in question (this should never happen).
	if len(f.meta) == 0 {
		return nil, nil, fmt.Errorf("loadParseTypecheck: no metadata found for %v", f.filename())
	}

	// If we have already seen these missing imports before, and we still have type information,
	// there is no need to continue.
	if sameSet(originalMissingImports, f.missingImports) && len(f.pkgs) != 0 {
		return nil, nil, nil
	}

	return f.meta, nil, nil
}

// reparseImports reparses a file's package and import declarations to
// determine if they have changed.
func (v *view) parseImports(ctx context.Context, f *goFile) bool {
	if len(f.meta) == 0 || len(f.missingImports) > 0 {
		return true
	}
	// Get file content in case we don't already have it.
	parsed, _ := v.session.cache.ParseGoHandle(f.Handle(ctx), source.ParseHeader).Parse(ctx)
	if parsed == nil {
		return true
	}
	// TODO: Add support for re-running `go list` when the package name changes.

	// If the package's imports have changed, re-run `go list`.
	if len(f.imports) != len(parsed.Imports) {
		return true
	}

	for i, importSpec := range f.imports {
		if importSpec.Path.Value != parsed.Imports[i].Path.Value {
			return true
		}
	}
	return false
}

func (v *view) link(ctx context.Context, pkgPath packagePath, pkg *packages.Package, parent *metadata) *metadata {
	id := packageID(pkg.ID)
	m, ok := v.mcache.packages[id]

	// If a file was added or deleted we need to invalidate the package cache
	// so relevant packages get parsed and type-checked again.
	if ok && !filenamesIdentical(m.files, pkg.CompiledGoFiles) {
		v.pcache.mu.Lock()
		v.remove(id, make(map[packageID]struct{}))
		v.pcache.mu.Unlock()
	}

	// If we haven't seen this package before.
	if !ok {
		m = &metadata{
			pkgPath:    pkgPath,
			id:         id,
			typesSizes: pkg.TypesSizes,
			parents:    make(map[packageID]bool),
			children:   make(map[packageID]bool),
		}
		v.mcache.packages[id] = m
		v.mcache.ids[pkgPath] = id
	}
	// Reset any field that could have changed across calls to packages.Load.
	m.name = pkg.Name
	m.files = pkg.CompiledGoFiles
	for _, filename := range m.files {
		if f, _ := v.getFile(span.FileURI(filename)); f != nil {
			if gof, ok := f.(*goFile); ok {
				if gof.meta == nil {
					gof.meta = make(map[packageID]*metadata)
				}
				gof.meta[m.id] = m
			} else {
				v.Session().Logger().Errorf(ctx, "not a Go file: %s", f.URI())
			}
		}
	}
	// Connect the import graph.
	if parent != nil {
		m.parents[parent.id] = true
		parent.children[id] = true
	}
	for importPath, importPkg := range pkg.Imports {
		if _, ok := m.children[packageID(importPkg.ID)]; !ok {
			v.link(ctx, packagePath(importPath), importPkg, m)
		}
	}
	// Clear out any imports that have been removed.
	for importID := range m.children {
		child, ok := v.mcache.packages[importID]
		if !ok {
			continue
		}
		importPath := string(child.pkgPath)
		if _, ok := pkg.Imports[importPath]; ok {
			continue
		}
		delete(m.children, importID)
		delete(child.parents, id)
	}
	return m
}

// filenamesIdentical reports whether two sets of file names are identical.
func filenamesIdentical(oldFiles, newFiles []string) bool {
	if len(oldFiles) != len(newFiles) {
		return false
	}
	oldByName := make(map[string]struct{}, len(oldFiles))
	for _, filename := range oldFiles {
		oldByName[filename] = struct{}{}
	}
	for _, newFilename := range newFiles {
		if _, found := oldByName[newFilename]; !found {
			return false
		}
		delete(oldByName, newFilename)
	}
	return len(oldByName) == 0
}
<commit_msg>internal/lsp: modify check for a missing package<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cache

import (
	"context"
	"fmt"

	"golang.org/x/tools/go/packages"
	"golang.org/x/tools/internal/lsp/source"
	"golang.org/x/tools/internal/span"
)

func (v *view) loadParseTypecheck(ctx context.Context, f *goFile) ([]packages.Error, error) {
	v.mcache.mu.Lock()
	defer v.mcache.mu.Unlock()

	// If the AST for this file is trimmed, and we are explicitly type-checking it,
	// don't ignore function bodies.
	if f.astIsTrimmed() {
		v.pcache.mu.Lock()
		f.invalidateAST()
		v.pcache.mu.Unlock()
	}

	// Get the metadata for the file.
	meta, errs, err := v.checkMetadata(ctx, f)
	if err != nil {
		return errs, err
	}
	if meta == nil {
		return nil, nil
	}
	for id, m := range meta {
		imp := &importer{
			view:          v,
			seen:          make(map[packageID]struct{}),
			ctx:           ctx,
			fset:          v.session.cache.FileSet(),
			topLevelPkgID: id,
		}
		// Start prefetching direct imports.
		for importID := range m.children {
			go imp.getPkg(importID)
		}
		// Type-check package.
		pkg, err := imp.getPkg(imp.topLevelPkgID)
		if err != nil {
			return nil, err
		}
		if pkg == nil || pkg.IsIllTyped() {
			return nil, fmt.Errorf("loadParseTypecheck: %s is ill typed", m.pkgPath)
		}
	}
	if len(f.pkgs) == 0 {
		return nil, fmt.Errorf("no packages found for %s", f.URI())
	}
	return nil, nil
}

func sameSet(x, y map[packagePath]struct{}) bool {
	if len(x) != len(y) {
		return false
	}
	for k := range x {
		if _, ok := y[k]; !ok {
			return false
		}
	}
	return true
}

// checkMetadata determines if we should run go/packages.Load for this file.
// If yes, update the metadata for the file and its package.
func (v *view) checkMetadata(ctx context.Context, f *goFile) (map[packageID]*metadata, []packages.Error, error) {
	f.mu.Lock()
	defer f.mu.Unlock()

	// Save the metadata's current missing imports, if any.
	originalMissingImports := f.missingImports

	if !v.parseImports(ctx, f) {
		return f.meta, nil, nil
	}

	// Reset the file's metadata and type information if we are re-running `go list`.
	for k := range f.meta {
		delete(f.meta, k)
	}
	for k := range f.pkgs {
		delete(f.pkgs, k)
	}

	pkgs, err := packages.Load(v.buildConfig(), fmt.Sprintf("file=%s", f.filename()))
	if len(pkgs) == 0 {
		if err == nil {
			err = fmt.Errorf("go/packages.Load: no packages found for %s", f.filename())
		}
		// Return this error as a diagnostic to the user.
		return nil, []packages.Error{
			{
				Msg:  err.Error(),
				Kind: packages.UnknownError,
			},
		}, err
	}

	// Clear missing imports.
	for k := range f.missingImports {
		delete(f.missingImports, k)
	}
	for _, pkg := range pkgs {
		// If the package comes back with errors from `go list`,
		// don't bother type-checking it.
		if len(pkg.Errors) > 0 {
			return nil, pkg.Errors, fmt.Errorf("package %s has errors, skipping type-checking", pkg.PkgPath)
		}
		for importPath, importPkg := range pkg.Imports {
			// If we encounter a package we cannot import, mark it as missing.
			if len(importPkg.CompiledGoFiles) == 0 {
				if f.missingImports == nil {
					f.missingImports = make(map[packagePath]struct{})
				}
				f.missingImports[packagePath(importPath)] = struct{}{}
			}
		}
		// Build the import graph for this package.
		v.link(ctx, packagePath(pkg.PkgPath), pkg, nil)
	}

	// If `go list` failed to get data for the file in question (this should never happen).
	if len(f.meta) == 0 {
		return nil, nil, fmt.Errorf("loadParseTypecheck: no metadata found for %v", f.filename())
	}

	// If we have already seen these missing imports before, and we still have type information,
	// there is no need to continue.
	if sameSet(originalMissingImports, f.missingImports) && len(f.pkgs) != 0 {
		return nil, nil, nil
	}

	return f.meta, nil, nil
}

// reparseImports reparses a file's package and import declarations to
// determine if they have changed.
func (v *view) parseImports(ctx context.Context, f *goFile) bool {
	if len(f.meta) == 0 || len(f.missingImports) > 0 {
		return true
	}
	// Get file content in case we don't already have it.
	parsed, _ := v.session.cache.ParseGoHandle(f.Handle(ctx), source.ParseHeader).Parse(ctx)
	if parsed == nil {
		return true
	}
	// TODO: Add support for re-running `go list` when the package name changes.

	// If the package's imports have changed, re-run `go list`.
	if len(f.imports) != len(parsed.Imports) {
		return true
	}

	for i, importSpec := range f.imports {
		if importSpec.Path.Value != parsed.Imports[i].Path.Value {
			return true
		}
	}
	return false
}

func (v *view) link(ctx context.Context, pkgPath packagePath, pkg *packages.Package, parent *metadata) *metadata {
	id := packageID(pkg.ID)
	m, ok := v.mcache.packages[id]

	// If a file was added or deleted we need to invalidate the package cache
	// so relevant packages get parsed and type-checked again.
	if ok && !filenamesIdentical(m.files, pkg.CompiledGoFiles) {
		v.pcache.mu.Lock()
		v.remove(id, make(map[packageID]struct{}))
		v.pcache.mu.Unlock()
	}

	// If we haven't seen this package before.
	if !ok {
		m = &metadata{
			pkgPath:    pkgPath,
			id:         id,
			typesSizes: pkg.TypesSizes,
			parents:    make(map[packageID]bool),
			children:   make(map[packageID]bool),
		}
		v.mcache.packages[id] = m
		v.mcache.ids[pkgPath] = id
	}
	// Reset any field that could have changed across calls to packages.Load.
	m.name = pkg.Name
	m.files = pkg.CompiledGoFiles
	for _, filename := range m.files {
		if f, _ := v.getFile(span.FileURI(filename)); f != nil {
			if gof, ok := f.(*goFile); ok {
				if gof.meta == nil {
					gof.meta = make(map[packageID]*metadata)
				}
				gof.meta[m.id] = m
			} else {
				v.Session().Logger().Errorf(ctx, "not a Go file: %s", f.URI())
			}
		}
	}
	// Connect the import graph.
	if parent != nil {
		m.parents[parent.id] = true
		parent.children[id] = true
	}
	for importPath, importPkg := range pkg.Imports {
		if _, ok := m.children[packageID(importPkg.ID)]; !ok {
			v.link(ctx, packagePath(importPath), importPkg, m)
		}
	}
	// Clear out any imports that have been removed.
	for importID := range m.children {
		child, ok := v.mcache.packages[importID]
		if !ok {
			continue
		}
		importPath := string(child.pkgPath)
		if _, ok := pkg.Imports[importPath]; ok {
			continue
		}
		delete(m.children, importID)
		delete(child.parents, id)
	}
	return m
}

// filenamesIdentical reports whether two sets of file names are identical.
func filenamesIdentical(oldFiles, newFiles []string) bool {
	if len(oldFiles) != len(newFiles) {
		return false
	}
	oldByName := make(map[string]struct{}, len(oldFiles))
	for _, filename := range oldFiles {
		oldByName[filename] = struct{}{}
	}
	for _, newFilename := range newFiles {
		if _, found := oldByName[newFilename]; !found {
			return false
		}
		delete(oldByName, newFilename)
	}
	return len(oldByName) == 0
}
<|endoftext|>


<commit_before>// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package testing

import (
	"go/constant"

	"github.com/hajimehoshi/ebiten/internal/shaderir"
)

var (
	projectionMatrix = shaderir.Expr{
		Type: shaderir.Call,
		Exprs: []shaderir.Expr{
			{
				Type:        shaderir.BuiltinFuncExpr,
				BuiltinFunc: shaderir.Mat4F,
			},
			{
				Type: shaderir.Binary,
				Op:   shaderir.Div,
				Exprs: []shaderir.Expr{
					{
						Type:      shaderir.NumberExpr,
						Const:     constant.MakeFloat64(2),
						ConstType: shaderir.ConstTypeFloat,
					},
					{
						Type: shaderir.FieldSelector,
						Exprs: []shaderir.Expr{
							{
								Type:  shaderir.UniformVariable,
								Index: 0,
							},
							{
								Type:      shaderir.SwizzlingExpr,
								Swizzling: "x",
							},
						},
					},
				},
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type: shaderir.Binary,
				Op:   shaderir.Div,
				Exprs: []shaderir.Expr{
					{
						Type:      shaderir.NumberExpr,
						Const:     constant.MakeFloat64(2),
						ConstType: shaderir.ConstTypeFloat,
					},
					{
						Type: shaderir.FieldSelector,
						Exprs: []shaderir.Expr{
							{
								Type:  shaderir.UniformVariable,
								Index: 0,
							},
							{
								Type:      shaderir.SwizzlingExpr,
								Swizzling: "y",
							},
						},
					},
				},
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(1),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(-1),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(-1),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(1),
				ConstType: shaderir.ConstTypeFloat,
			},
		},
	}
	vertexPosition = shaderir.Expr{
		Type: shaderir.Call,
		Exprs: []shaderir.Expr{
			{
				Type:        shaderir.BuiltinFuncExpr,
				BuiltinFunc: shaderir.Vec4F,
			},
			{
				Type:  shaderir.LocalVariable,
				Index: 0,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(1),
				ConstType: shaderir.ConstTypeFloat,
			},
		},
	}
	defaultVertexFunc = shaderir.VertexFunc{
		Block: shaderir.Block{
			Stmts: []shaderir.Stmt{
				{
					Type: shaderir.Assign,
					Exprs: []shaderir.Expr{
						{
							Type:  shaderir.LocalVariable,
							Index: 5, // the varying variable
						},
						{
							Type:  shaderir.LocalVariable,
							Index: 1, // the 2nd attribute variable
						},
					},
				},
				{
					Type: shaderir.Assign,
					Exprs: []shaderir.Expr{
						{
							Type:  shaderir.LocalVariable,
							Index: 4, // gl_Position in GLSL
						},
						{
							Type: shaderir.Binary,
							Op:   shaderir.Mul,
							Exprs: []shaderir.Expr{
								projectionMatrix,
								vertexPosition,
							},
						},
					},
				},
			},
		},
	}
)

func defaultProgram() shaderir.Program {
	return shaderir.Program{
		Uniforms: []shaderir.Type{
			{Main: shaderir.Vec2}, // Viewport size. This must be the first uniform variable, and the values are set at graphicscommand driver.
		},
		Attributes: []shaderir.Type{
			{Main: shaderir.Vec2}, // Local var (0) in the vertex shader
			{Main: shaderir.Vec2}, // Local var (1) in the vertex shader
			{Main: shaderir.Vec4}, // Local var (2) in the vertex shader
			{Main: shaderir.Vec4}, // Local var (3) in the vertex shader
		},
		Varyings: []shaderir.Type{
			{Main: shaderir.Vec2}, // Local var (4) in the vertex shader, (0) in the fragment shader
		},
		VertexFunc: defaultVertexFunc,
	}
}

// ShaderProgramFill returns a shader intermediate representation to fill the frambuffer.
//
// Uniform variable's index and its value are:
//
//   0: the framebuffer size (Vec2)
func ShaderProgramFill(r, g, b, a byte) shaderir.Program {
	clr := shaderir.Expr{
		Type: shaderir.Call,
		Exprs: []shaderir.Expr{
			{
				Type:        shaderir.BuiltinFuncExpr,
				BuiltinFunc: shaderir.Vec4F,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(float64(r) / 0xff),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(float64(g) / 0xff),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(float64(b) / 0xff),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(float64(a) / 0xff),
				ConstType: shaderir.ConstTypeFloat,
			},
		},
	}

	p := defaultProgram()
	p.FragmentFunc = shaderir.FragmentFunc{
		Block: shaderir.Block{
			Stmts: []shaderir.Stmt{
				{
					Type: shaderir.Assign,
					Exprs: []shaderir.Expr{
						{
							Type:  shaderir.LocalVariable,
							Index: 2,
						},
						clr,
					},
				},
			},
		},
	}

	return p
}

// ShaderProgramImages returns a shader intermediate representation to render the frambuffer with the given images.
//
// Uniform variables's indices and their values are:
//
//   0:  the framebuffer size (Vec2)
//   1-: the images (Texture2D)
//
// The first image's size and region are represented in attribute variables.
//
// The size and region values are actually not used in this shader so far.
func ShaderProgramImages(imageNum int) shaderir.Program {
	if imageNum <= 0 {
		panic("testing: imageNum must be >= 1")
	}

	p := defaultProgram()

	for i := 0; i < imageNum; i++ {
		p.Uniforms = append(p.Uniforms, shaderir.Type{Main: shaderir.Texture2D})
		if i > 0 {
			p.Uniforms = append(p.Uniforms, shaderir.Type{Main: shaderir.Vec4})
		}
	}

	// In the fragment shader, local variables are:
	//
	//   0: gl_FragCoord
	//   1: Varying variables (vec2)
	//   2: gl_FragColor
	//   3: Actual local variables in the main function

	local := shaderir.Expr{
		Type:  shaderir.LocalVariable,
		Index: 3,
	}
	fragColor := shaderir.Expr{
		Type:  shaderir.LocalVariable,
		Index: 2,
	}
	texPos := shaderir.Expr{
		Type:  shaderir.LocalVariable,
		Index: 1,
	}

	var stmts []shaderir.Stmt
	for i := 0; i < imageNum; i++ {
		var rhs shaderir.Expr
		if i == 0 {
			rhs = shaderir.Expr{
				Type: shaderir.Call,
				Exprs: []shaderir.Expr{
					{
						Type:        shaderir.BuiltinFuncExpr,
						BuiltinFunc: shaderir.Texture2DF,
					},
					{
						Type:  shaderir.UniformVariable,
						Index: 1,
					},
					texPos,
				},
			}
		} else {
			rhs = shaderir.Expr{
				Type: shaderir.Binary,
				Op:   shaderir.Add,
				Exprs: []shaderir.Expr{
					local,
					{
						Type: shaderir.Call,
						Exprs: []shaderir.Expr{
							{
								Type:        shaderir.BuiltinFuncExpr,
								BuiltinFunc: shaderir.Texture2DF,
							},
							{
								Type:  shaderir.UniformVariable,
								Index: i + 1,
							},
							texPos,
						},
					},
				},
			}
		}
		stmts = append(stmts, shaderir.Stmt{
			Type: shaderir.Assign,
			Exprs: []shaderir.Expr{
				local,
				rhs,
			},
		})
	}

	stmts = append(stmts, shaderir.Stmt{
		Type: shaderir.Assign,
		Exprs: []shaderir.Expr{
			fragColor,
			local,
		},
	})

	p.FragmentFunc = shaderir.FragmentFunc{
		Block: shaderir.Block{
			LocalVars: []shaderir.Type{
				{Main: shaderir.Vec4},
			},
			Stmts: stmts,
		},
	}

	return p
}
<commit_msg>testing: Bug fix: Wrong uniform variables<commit_after>// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package testing

import (
	"go/constant"

	"github.com/hajimehoshi/ebiten/internal/shaderir"
)

var (
	projectionMatrix = shaderir.Expr{
		Type: shaderir.Call,
		Exprs: []shaderir.Expr{
			{
				Type:        shaderir.BuiltinFuncExpr,
				BuiltinFunc: shaderir.Mat4F,
			},
			{
				Type: shaderir.Binary,
				Op:   shaderir.Div,
				Exprs: []shaderir.Expr{
					{
						Type:      shaderir.NumberExpr,
						Const:     constant.MakeFloat64(2),
						ConstType: shaderir.ConstTypeFloat,
					},
					{
						Type: shaderir.FieldSelector,
						Exprs: []shaderir.Expr{
							{
								Type:  shaderir.UniformVariable,
								Index: 0,
							},
							{
								Type:      shaderir.SwizzlingExpr,
								Swizzling: "x",
							},
						},
					},
				},
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type: shaderir.Binary,
				Op:   shaderir.Div,
				Exprs: []shaderir.Expr{
					{
						Type:      shaderir.NumberExpr,
						Const:     constant.MakeFloat64(2),
						ConstType: shaderir.ConstTypeFloat,
					},
					{
						Type: shaderir.FieldSelector,
						Exprs: []shaderir.Expr{
							{
								Type:  shaderir.UniformVariable,
								Index: 0,
							},
							{
								Type:      shaderir.SwizzlingExpr,
								Swizzling: "y",
							},
						},
					},
				},
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(1),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(-1),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(-1),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(1),
				ConstType: shaderir.ConstTypeFloat,
			},
		},
	}
	vertexPosition = shaderir.Expr{
		Type: shaderir.Call,
		Exprs: []shaderir.Expr{
			{
				Type:        shaderir.BuiltinFuncExpr,
				BuiltinFunc: shaderir.Vec4F,
			},
			{
				Type:  shaderir.LocalVariable,
				Index: 0,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(0),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(1),
				ConstType: shaderir.ConstTypeFloat,
			},
		},
	}
	defaultVertexFunc = shaderir.VertexFunc{
		Block: shaderir.Block{
			Stmts: []shaderir.Stmt{
				{
					Type: shaderir.Assign,
					Exprs: []shaderir.Expr{
						{
							Type:  shaderir.LocalVariable,
							Index: 5, // the varying variable
						},
						{
							Type:  shaderir.LocalVariable,
							Index: 1, // the 2nd attribute variable
						},
					},
				},
				{
					Type: shaderir.Assign,
					Exprs: []shaderir.Expr{
						{
							Type:  shaderir.LocalVariable,
							Index: 4, // gl_Position in GLSL
						},
						{
							Type: shaderir.Binary,
							Op:   shaderir.Mul,
							Exprs: []shaderir.Expr{
								projectionMatrix,
								vertexPosition,
							},
						},
					},
				},
			},
		},
	}
)

func defaultProgram() shaderir.Program {
	return shaderir.Program{
		Uniforms: []shaderir.Type{
			{Main: shaderir.Vec2}, // Viewport size. This must be the first uniform variable, and the values are set at graphicscommand driver.
		},
		Attributes: []shaderir.Type{
			{Main: shaderir.Vec2}, // Local var (0) in the vertex shader
			{Main: shaderir.Vec2}, // Local var (1) in the vertex shader
			{Main: shaderir.Vec4}, // Local var (2) in the vertex shader
			{Main: shaderir.Vec4}, // Local var (3) in the vertex shader
		},
		Varyings: []shaderir.Type{
			{Main: shaderir.Vec2}, // Local var (4) in the vertex shader, (0) in the fragment shader
		},
		VertexFunc: defaultVertexFunc,
	}
}

// ShaderProgramFill returns a shader intermediate representation to fill the frambuffer.
//
// Uniform variable's index and its value are:
//
//   0: the framebuffer size (Vec2)
func ShaderProgramFill(r, g, b, a byte) shaderir.Program {
	clr := shaderir.Expr{
		Type: shaderir.Call,
		Exprs: []shaderir.Expr{
			{
				Type:        shaderir.BuiltinFuncExpr,
				BuiltinFunc: shaderir.Vec4F,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(float64(r) / 0xff),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(float64(g) / 0xff),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(float64(b) / 0xff),
				ConstType: shaderir.ConstTypeFloat,
			},
			{
				Type:      shaderir.NumberExpr,
				Const:     constant.MakeFloat64(float64(a) / 0xff),
				ConstType: shaderir.ConstTypeFloat,
			},
		},
	}

	p := defaultProgram()
	p.FragmentFunc = shaderir.FragmentFunc{
		Block: shaderir.Block{
			Stmts: []shaderir.Stmt{
				{
					Type: shaderir.Assign,
					Exprs: []shaderir.Expr{
						{
							Type:  shaderir.LocalVariable,
							Index: 2,
						},
						clr,
					},
				},
			},
		},
	}

	return p
}

// ShaderProgramImages returns a shader intermediate representation to render the frambuffer with the given images.
//
// Uniform variables's indices and their values are:
//
//   0:  the framebuffer size (Vec2)
//   1-: the images (Texture2D)
//
// The first image's size and region are represented in attribute variables.
//
// The size and region values are actually not used in this shader so far.
func ShaderProgramImages(imageNum int) shaderir.Program {
	if imageNum <= 0 {
		panic("testing: imageNum must be >= 1")
	}

	p := defaultProgram()

	for i := 0; i < imageNum; i++ {
		p.Uniforms = append(p.Uniforms, shaderir.Type{Main: shaderir.Texture2D})
	}

	// In the fragment shader, local variables are:
	//
	//   0: gl_FragCoord
	//   1: Varying variables (vec2)
	//   2: gl_FragColor
	//   3: Actual local variables in the main function

	local := shaderir.Expr{
		Type:  shaderir.LocalVariable,
		Index: 3,
	}
	fragColor := shaderir.Expr{
		Type:  shaderir.LocalVariable,
		Index: 2,
	}
	texPos := shaderir.Expr{
		Type:  shaderir.LocalVariable,
		Index: 1,
	}

	var stmts []shaderir.Stmt
	for i := 0; i < imageNum; i++ {
		var rhs shaderir.Expr
		if i == 0 {
			rhs = shaderir.Expr{
				Type: shaderir.Call,
				Exprs: []shaderir.Expr{
					{
						Type:        shaderir.BuiltinFuncExpr,
						BuiltinFunc: shaderir.Texture2DF,
					},
					{
						Type:  shaderir.UniformVariable,
						Index: 1,
					},
					texPos,
				},
			}
		} else {
			rhs = shaderir.Expr{
				Type: shaderir.Binary,
				Op:   shaderir.Add,
				Exprs: []shaderir.Expr{
					local,
					{
						Type: shaderir.Call,
						Exprs: []shaderir.Expr{
							{
								Type:        shaderir.BuiltinFuncExpr,
								BuiltinFunc: shaderir.Texture2DF,
							},
							{
								Type:  shaderir.UniformVariable,
								Index: i + 1,
							},
							texPos,
						},
					},
				},
			}
		}
		stmts = append(stmts, shaderir.Stmt{
			Type: shaderir.Assign,
			Exprs: []shaderir.Expr{
				local,
				rhs,
			},
		})
	}

	stmts = append(stmts, shaderir.Stmt{
		Type: shaderir.Assign,
		Exprs: []shaderir.Expr{
			fragColor,
			local,
		},
	})

	p.FragmentFunc = shaderir.FragmentFunc{
		Block: shaderir.Block{
			LocalVars: []shaderir.Type{
				{Main: shaderir.Vec4},
			},
			Stmts: stmts,
		},
	}

	return p
}
<|endoftext|>


<commit_before>// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !android && !ios && !js && !ebitencbackend
// +build !android,!ios,!js,!ebitencbackend

package ui

import (
	"image"

	"github.com/hajimehoshi/ebiten/v2/internal/glfw"
)

type Window struct {
	ui *userInterfaceImpl
}

func (w *Window) IsDecorated() bool {
	if !w.ui.isRunning() {
		return w.ui.isInitWindowDecorated()
	}
	v := false
	w.ui.t.Call(func() {
		v = w.ui.window.GetAttrib(glfw.Decorated) == glfw.True
	})
	return v
}

func (w *Window) SetDecorated(decorated bool) {
	if !w.ui.isRunning() {
		w.ui.setInitWindowDecorated(decorated)
		return
	}

	w.ui.t.Call(func() {
		if w.ui.isNativeFullscreen() {
			return
		}

		w.ui.setWindowDecorated(decorated)
	})
}

func (w *Window) ResizingMode() WindowResizingMode {
	if !w.ui.isRunning() {
		w.ui.m.Lock()
		mode := w.ui.windowResizingMode
		w.ui.m.Unlock()
		return mode
	}
	var mode WindowResizingMode
	w.ui.t.Call(func() {
		mode = w.ui.windowResizingMode
	})
	return mode
}

func (w *Window) SetResizingMode(mode WindowResizingMode) {
	if !w.ui.isRunning() {
		w.ui.m.Lock()
		w.ui.windowResizingMode = mode
		w.ui.m.Unlock()
		return
	}
	w.ui.t.Call(func() {
		if w.ui.isNativeFullscreen() {
			return
		}
		w.ui.setWindowResizingMode(mode)
	})
}

func (w *Window) IsFloating() bool {
	if !w.ui.isRunning() {
		return w.ui.isInitWindowFloating()
	}
	var v bool
	w.ui.t.Call(func() {
		v = w.ui.window.GetAttrib(glfw.Floating) == glfw.True
	})
	return v
}

func (w *Window) SetFloating(floating bool) {
	if !w.ui.isRunning() {
		w.ui.setInitWindowFloating(floating)
		return
	}
	w.ui.t.Call(func() {
		if w.ui.isNativeFullscreen() {
			return
		}
		w.ui.setWindowFloating(floating)
	})
}

func (w *Window) IsMaximized() bool {
	if !w.ui.isRunning() {
		return w.ui.isInitWindowMaximized()
	}
	if w.ResizingMode() != WindowResizingModeEnabled {
		return false
	}
	var v bool
	w.ui.t.Call(func() {
		v = w.ui.window.GetAttrib(glfw.Maximized) == glfw.True
	})
	return v
}

func (w *Window) Maximize() {
	// Do not allow maximizing the window when the window is not resizable.
	// On Windows, it is possible to restore the window from being maximized by mouse-dragging,
	// and this can be an unexpected behavior.
	if w.ResizingMode() != WindowResizingModeEnabled {
		panic("ui: a window to maximize must be resizable")
	}
	if !w.ui.isRunning() {
		w.ui.setInitWindowMaximized(true)
		return
	}
	w.ui.t.Call(w.ui.maximizeWindow)
}

func (w *Window) IsMinimized() bool {
	if !w.ui.isRunning() {
		return false
	}
	var v bool
	w.ui.t.Call(func() {
		v = w.ui.window.GetAttrib(glfw.Iconified) == glfw.True
	})
	return v
}

func (w *Window) Minimize() {
	if !w.ui.isRunning() {
		// Do nothing
		return
	}
	w.ui.t.Call(w.ui.iconifyWindow)
}

func (w *Window) Restore() {
	if !w.ui.isRunning() {
		// Do nothing
		return
	}
	w.ui.t.Call(w.ui.restoreWindow)
}

func (w *Window) Position() (int, int) {
	if !w.ui.isRunning() {
		panic("ui: WindowPosition can't be called before the main loop starts")
	}
	x, y := 0, 0
	w.ui.t.Call(func() {
		var wx, wy int
		if w.ui.isFullscreen() {
			wx, wy = w.ui.origWindowPos()
		} else {
			wx, wy = w.ui.window.GetPos()
		}
		m := w.ui.currentMonitor()
		mx, my := m.GetPos()
		wx -= mx
		wy -= my
		xf := w.ui.dipFromGLFWPixel(float64(wx), m)
		yf := w.ui.dipFromGLFWPixel(float64(wy), m)
		x, y = int(xf), int(yf)
	})
	return x, y
}

func (w *Window) SetPosition(x, y int) {
	if !w.ui.isRunning() {
		w.ui.setInitWindowPositionInDIP(x, y)
		return
	}
	w.ui.t.Call(func() {
		w.ui.setWindowPositionInDIP(x, y, w.ui.currentMonitor())
	})
}

func (w *Window) Size() (int, int) {
	if !w.ui.isRunning() {
		ww, wh := w.ui.getInitWindowSizeInDIP()
		return w.ui.adjustWindowSizeBasedOnSizeLimitsInDIP(ww, wh)
	}
	ww, wh := 0, 0
	w.ui.t.Call(func() {
		ww = w.ui.windowWidthInDIP
		wh = w.ui.windowHeightInDIP
	})
	return ww, wh
}

func (w *Window) SetSize(width, height int) {
	if !w.ui.isRunning() {
		w.ui.setInitWindowSizeInDIP(width, height)
		return
	}
	w.ui.t.Call(func() {
		// When a window is a native fullscreen, forcing to resize the window might leave unexpected image lags.
		// Forbid this.
		if w.ui.isNativeFullscreen() {
			return
		}

		w.ui.setWindowSizeInDIP(width, height, w.ui.isFullscreen())
	})
}

func (w *Window) SizeLimits() (minw, minh, maxw, maxh int) {
	return w.ui.getWindowSizeLimitsInDIP()
}

func (w *Window) SetSizeLimits(minw, minh, maxw, maxh int) {
	if !w.ui.setWindowSizeLimitsInDIP(minw, minh, maxw, maxh) {
		return
	}
	if !w.ui.isRunning() {
		return
	}

	w.ui.t.Call(w.ui.updateWindowSizeLimits)
}

func (w *Window) SetIcon(iconImages []image.Image) {
	// The icons are actually set at (*UserInterface).loop.
	w.ui.setIconImages(iconImages)
}

func (w *Window) SetTitle(title string) {
	if !w.ui.isRunning() {
		w.ui.m.Lock()
		w.ui.title = title
		w.ui.m.Unlock()
		return
	}
	w.ui.title = title
	w.ui.t.Call(func() {
		w.ui.setWindowTitle(title)
	})
}

func (w *Window) IsBeingClosed() bool {
	return w.ui.isWindowBeingClosed()
}

func (w *Window) SetClosingHandled(handled bool) {
	w.ui.setWindowClosingHandled(handled)
}

func (w *Window) IsClosingHandled() bool {
	return w.ui.isWindowClosingHandled()
}
<commit_msg>internal/ui: add comments<commit_after>// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !android && !ios && !js && !ebitencbackend
// +build !android,!ios,!js,!ebitencbackend

package ui

import (
	"image"

	"github.com/hajimehoshi/ebiten/v2/internal/glfw"
)

type Window struct {
	ui *userInterfaceImpl
}

func (w *Window) IsDecorated() bool {
	if !w.ui.isRunning() {
		return w.ui.isInitWindowDecorated()
	}
	v := false
	w.ui.t.Call(func() {
		v = w.ui.window.GetAttrib(glfw.Decorated) == glfw.True
	})
	return v
}

func (w *Window) SetDecorated(decorated bool) {
	if !w.ui.isRunning() {
		w.ui.setInitWindowDecorated(decorated)
		return
	}

	w.ui.t.Call(func() {
		if w.ui.isNativeFullscreen() {
			return
		}

		w.ui.setWindowDecorated(decorated)
	})
}

func (w *Window) ResizingMode() WindowResizingMode {
	if !w.ui.isRunning() {
		w.ui.m.Lock()
		mode := w.ui.windowResizingMode
		w.ui.m.Unlock()
		return mode
	}
	var mode WindowResizingMode
	w.ui.t.Call(func() {
		mode = w.ui.windowResizingMode
	})
	return mode
}

func (w *Window) SetResizingMode(mode WindowResizingMode) {
	if !w.ui.isRunning() {
		w.ui.m.Lock()
		w.ui.windowResizingMode = mode
		w.ui.m.Unlock()
		return
	}
	w.ui.t.Call(func() {
		if w.ui.isNativeFullscreen() {
			return
		}
		w.ui.setWindowResizingMode(mode)
	})
}

func (w *Window) IsFloating() bool {
	if !w.ui.isRunning() {
		return w.ui.isInitWindowFloating()
	}
	var v bool
	w.ui.t.Call(func() {
		v = w.ui.window.GetAttrib(glfw.Floating) == glfw.True
	})
	return v
}

func (w *Window) SetFloating(floating bool) {
	if !w.ui.isRunning() {
		w.ui.setInitWindowFloating(floating)
		return
	}
	w.ui.t.Call(func() {
		if w.ui.isNativeFullscreen() {
			return
		}
		w.ui.setWindowFloating(floating)
	})
}

func (w *Window) IsMaximized() bool {
	if !w.ui.isRunning() {
		return w.ui.isInitWindowMaximized()
	}
	if w.ResizingMode() != WindowResizingModeEnabled {
		return false
	}
	var v bool
	w.ui.t.Call(func() {
		v = w.ui.window.GetAttrib(glfw.Maximized) == glfw.True
	})
	return v
}

func (w *Window) Maximize() {
	// Do not allow maximizing the window when the window is not resizable.
	// On Windows, it is possible to restore the window from being maximized by mouse-dragging,
	// and this can be an unexpected behavior.
	if w.ResizingMode() != WindowResizingModeEnabled {
		panic("ui: a window to maximize must be resizable")
	}
	if !w.ui.isRunning() {
		w.ui.setInitWindowMaximized(true)
		return
	}
	w.ui.t.Call(w.ui.maximizeWindow)
}

func (w *Window) IsMinimized() bool {
	if !w.ui.isRunning() {
		return false
	}
	var v bool
	w.ui.t.Call(func() {
		v = w.ui.window.GetAttrib(glfw.Iconified) == glfw.True
	})
	return v
}

func (w *Window) Minimize() {
	if !w.ui.isRunning() {
		// Do nothing
		return
	}
	w.ui.t.Call(w.ui.iconifyWindow)
}

func (w *Window) Restore() {
	if !w.ui.isRunning() {
		// Do nothing
		return
	}
	w.ui.t.Call(w.ui.restoreWindow)
}

func (w *Window) Position() (int, int) {
	if !w.ui.isRunning() {
		panic("ui: WindowPosition can't be called before the main loop starts")
	}
	x, y := 0, 0
	w.ui.t.Call(func() {
		var wx, wy int
		if w.ui.isFullscreen() {
			wx, wy = w.ui.origWindowPos()
		} else {
			wx, wy = w.ui.window.GetPos()
		}
		m := w.ui.currentMonitor()
		mx, my := m.GetPos()
		wx -= mx
		wy -= my
		xf := w.ui.dipFromGLFWPixel(float64(wx), m)
		yf := w.ui.dipFromGLFWPixel(float64(wy), m)
		x, y = int(xf), int(yf)
	})
	return x, y
}

func (w *Window) SetPosition(x, y int) {
	if !w.ui.isRunning() {
		w.ui.setInitWindowPositionInDIP(x, y)
		return
	}
	w.ui.t.Call(func() {
		w.ui.setWindowPositionInDIP(x, y, w.ui.currentMonitor())
	})
}

func (w *Window) Size() (int, int) {
	if !w.ui.isRunning() {
		ww, wh := w.ui.getInitWindowSizeInDIP()
		return w.ui.adjustWindowSizeBasedOnSizeLimitsInDIP(ww, wh)
	}
	ww, wh := 0, 0
	w.ui.t.Call(func() {
		// Unlike origWindowPos, windowWidth/HeightInDPI is always updated via the callback.
		ww = w.ui.windowWidthInDIP
		wh = w.ui.windowHeightInDIP
	})
	return ww, wh
}

func (w *Window) SetSize(width, height int) {
	if !w.ui.isRunning() {
		w.ui.setInitWindowSizeInDIP(width, height)
		return
	}
	w.ui.t.Call(func() {
		// When a window is a native fullscreen, forcing to resize the window might leave unexpected image lags.
		// Forbid this.
		// TODO: Remove this condition (#1590).
		if w.ui.isNativeFullscreen() {
			return
		}

		w.ui.setWindowSizeInDIP(width, height, w.ui.isFullscreen())
	})
}

func (w *Window) SizeLimits() (minw, minh, maxw, maxh int) {
	return w.ui.getWindowSizeLimitsInDIP()
}

func (w *Window) SetSizeLimits(minw, minh, maxw, maxh int) {
	if !w.ui.setWindowSizeLimitsInDIP(minw, minh, maxw, maxh) {
		return
	}
	if !w.ui.isRunning() {
		return
	}

	w.ui.t.Call(w.ui.updateWindowSizeLimits)
}

func (w *Window) SetIcon(iconImages []image.Image) {
	// The icons are actually set at (*UserInterface).loop.
	w.ui.setIconImages(iconImages)
}

func (w *Window) SetTitle(title string) {
	if !w.ui.isRunning() {
		w.ui.m.Lock()
		w.ui.title = title
		w.ui.m.Unlock()
		return
	}
	w.ui.title = title
	w.ui.t.Call(func() {
		w.ui.setWindowTitle(title)
	})
}

func (w *Window) IsBeingClosed() bool {
	return w.ui.isWindowBeingClosed()
}

func (w *Window) SetClosingHandled(handled bool) {
	w.ui.setWindowClosingHandled(handled)
}

func (w *Window) IsClosingHandled() bool {
	return w.ui.isWindowClosingHandled()
}
<|endoftext|>


<commit_before>package glisp

import (
	"reflect"
	"strings"
	"strconv"
)

type Sexp interface {
	SexpString() string
}

type SexpSentinel int
const (
	SexpNull SexpSentinel = iota
	SexpEnd
)

func (sent SexpSentinel) SexpString() string {
	if sent == SexpNull {
		return "()"
	}
	if sent == SexpEnd {
		return "End"
	}

	return ""
}

type SexpPair struct {
	head Sexp
	tail Sexp
}

func (pair SexpPair) SexpString() string {
	str := "("

	for {
		switch pair.tail.(type) {
		case SexpPair:
			str += pair.head.SexpString() + " "
			pair = pair.tail.(SexpPair)
			continue
		}
		break
	}

	str += pair.head.SexpString()

	if pair.tail == SexpNull {
		str += ")"
	} else {
		str += " . " + pair.tail.SexpString() + ")"
	}

	return str
}

type SexpArray []Sexp
type SexpInt int
type SexpBool bool
type SexpFloat float64
type SexpChar rune
type SexpStr string

var SexpIntSize = reflect.TypeOf(SexpInt(0)).Bits()
var SexpFloatSize = reflect.TypeOf(SexpFloat(0.0)).Bits()

func (arr SexpArray) SexpString() string {
	if len(arr) == 0 {
		return "[]"
	}

	str := "[" + arr[0].SexpString()
	for _, sexp := range arr[1:] {
		str += " " + sexp.SexpString()
	}
	str += "]"
	return str
}

func (b SexpBool) SexpString() string {
	if b {
		return "true"
	}
	return "bool:false"
}

func (i SexpInt) SexpString() string {
	return strconv.Itoa(int(i))
}

func (f SexpFloat) SexpString() string {
	return strconv.FormatFloat(float64(f), 'g', 5, SexpFloatSize)
}

func (c SexpChar) SexpString() string {
	return "#" + strings.Trim(strconv.QuoteRune(rune(c)), "'")
}

func (s SexpStr) SexpString() string {
	return strconv.Quote(string(s))
}

type SexpSymbol struct {
	name   string
	number int
}

func (sym SexpSymbol) SexpString() string {
	return sym.name + ":" + strconv.Itoa(sym.number)
}

func IsTruthy(expr Sexp) bool {
	switch e := expr.(type) {
	case SexpBool:
		return bool(e)
	case SexpInt:
		return e != 0
	case SexpChar:
		return e != 0
	case SexpSentinel:
		return e != SexpNull
	}
	return true
}
<commit_msg>fix bool and symbol SexpString methods<commit_after>package glisp

import (
	"reflect"
	"strings"
	"strconv"
)

type Sexp interface {
	SexpString() string
}

type SexpSentinel int
const (
	SexpNull SexpSentinel = iota
	SexpEnd
)

func (sent SexpSentinel) SexpString() string {
	if sent == SexpNull {
		return "()"
	}
	if sent == SexpEnd {
		return "End"
	}

	return ""
}

type SexpPair struct {
	head Sexp
	tail Sexp
}

func (pair SexpPair) SexpString() string {
	str := "("

	for {
		switch pair.tail.(type) {
		case SexpPair:
			str += pair.head.SexpString() + " "
			pair = pair.tail.(SexpPair)
			continue
		}
		break
	}

	str += pair.head.SexpString()

	if pair.tail == SexpNull {
		str += ")"
	} else {
		str += " . " + pair.tail.SexpString() + ")"
	}

	return str
}

type SexpArray []Sexp
type SexpInt int
type SexpBool bool
type SexpFloat float64
type SexpChar rune
type SexpStr string

var SexpIntSize = reflect.TypeOf(SexpInt(0)).Bits()
var SexpFloatSize = reflect.TypeOf(SexpFloat(0.0)).Bits()

func (arr SexpArray) SexpString() string {
	if len(arr) == 0 {
		return "[]"
	}

	str := "[" + arr[0].SexpString()
	for _, sexp := range arr[1:] {
		str += " " + sexp.SexpString()
	}
	str += "]"
	return str
}

func (b SexpBool) SexpString() string {
	if b {
		return "true"
	}
	return "false"
}

func (i SexpInt) SexpString() string {
	return strconv.Itoa(int(i))
}

func (f SexpFloat) SexpString() string {
	return strconv.FormatFloat(float64(f), 'g', 5, SexpFloatSize)
}

func (c SexpChar) SexpString() string {
	return "#" + strings.Trim(strconv.QuoteRune(rune(c)), "'")
}

func (s SexpStr) SexpString() string {
	return strconv.Quote(string(s))
}

type SexpSymbol struct {
	name   string
	number int
}

func (sym SexpSymbol) SexpString() string {
	return sym.name
}

func IsTruthy(expr Sexp) bool {
	switch e := expr.(type) {
	case SexpBool:
		return bool(e)
	case SexpInt:
		return e != 0
	case SexpChar:
		return e != 0
	case SexpSentinel:
		return e != SexpNull
	}
	return true
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package codegen

import (
	"fmt"
	"strings"

	"github.com/pkg/errors"
	"go.uber.org/thriftrw/compile"
)

// TypeConverter can generate a function body that converts two thriftrw
// FieldGroups from one to another. It's assumed that the converted code
// operates on two variables, "in" and "out" and that both are a go struct.
type TypeConverter struct {
	Lines  []string
	Helper PackageNameResolver
}

// PackageNameResolver interface allows for resolving what the
// package name for a thrift file is. This depends on where the
// thrift-based structs are generated.
type PackageNameResolver interface {
	TypePackageName(thriftFile string) (string, error)
}

func (c *TypeConverter) getGoTypeName(
	valueType compile.TypeSpec,
) (string, error) {
	switch s := valueType.(type) {
	case *compile.BoolSpec:
		return "bool", nil
	case *compile.I8Spec:
		return "int8", nil
	case *compile.I16Spec:
		return "int16", nil
	case *compile.I32Spec:
		return "int32", nil
	case *compile.I64Spec:
		return "int64", nil
	case *compile.DoubleSpec:
		return "float64", nil
	case *compile.StringSpec:
		return "string", nil
	case *compile.BinarySpec:
		return "[]byte", nil
	case *compile.MapSpec:
		panic("Not Implemented")
	case *compile.SetSpec:
		panic("Not Implemented")
	case *compile.ListSpec:
		panic("Not Implemented")
	case *compile.EnumSpec, *compile.StructSpec, *compile.TypedefSpec:
		return c.getIdentifierName(s)
	default:
		panic(fmt.Sprintf("Unknown type (%T) %v", valueType, valueType))
	}
}

func (c *TypeConverter) getIdentifierName(
	fieldType compile.TypeSpec,
) (string, error) {
	pkgName, err := c.Helper.TypePackageName(fieldType.ThriftFile())
	if err != nil {
		return "", errors.Wrapf(
			err,
			"could not lookup fieldType when building converter for %s :",
			fieldType.ThriftName(),
		)
	}
	typeName := pkgName + "." + fieldType.ThriftName()
	return typeName, nil
}

func (c *TypeConverter) genConverterForStruct(
	toFieldName string,
	toFieldType *compile.StructSpec,
	fromFieldType compile.TypeSpec,
	fromIdentifier string,
	keyPrefix string,
	indent string,
) error {
	toIdentifier := "out." + keyPrefix

	typeName, err := c.getIdentifierName(toFieldType)
	if err != nil {
		return err
	}
	subToFields := toFieldType.Fields

	fromFieldStruct, ok := fromFieldType.(*compile.StructSpec)
	if !ok {
		return errors.Errorf(
			"could not convert struct fields, "+
				"incompatible type for %s :",
			toFieldName,
		)
	}

	line := indent + "if " + fromIdentifier + " != nil {"
	c.Lines = append(c.Lines, line)

	line = indent + "	" + toIdentifier + " = &" + typeName + "{}"
	c.Lines = append(c.Lines, line)

	subFromFields := fromFieldStruct.Fields
	err = c.genStructConverter(
		keyPrefix+".",
		indent+"	",
		subFromFields,
		subToFields,
	)
	if err != nil {
		return err
	}

	line = indent + "} else {"
	c.Lines = append(c.Lines, line)

	line = indent + "	" + toIdentifier + " = nil"
	c.Lines = append(c.Lines, line)

	line = indent + "}"
	c.Lines = append(c.Lines, line)

	return nil
}

func (c *TypeConverter) genConverterForPrimitive(
	toField *compile.FieldSpec,
	toIdentifier string,
	fromIdentifier string,
) error {
	var line string
	typeName, err := c.getGoTypeName(toField.Type)
	if err != nil {
		return err
	}

	if toField.Required {
		line = toIdentifier + " = " + typeName + "(" + fromIdentifier + ")"
	} else {
		line = toIdentifier + " = (*" + typeName + ")(" + fromIdentifier + ")"
	}
	c.Lines = append(c.Lines, line)
	return nil
}

func (c *TypeConverter) genConverterForList(
	toFieldType *compile.ListSpec,
	toField *compile.FieldSpec,
	fromField *compile.FieldSpec,
	toIdentifier string,
	fromIdentifier string,
	keyPrefix string,
	indent string,
) error {
	typeName, err := c.getGoTypeName(toFieldType.ValueSpec)
	if err != nil {
		return err
	}

	valueStruct, isStruct := toFieldType.ValueSpec.(*compile.StructSpec)
	if isStruct {
		line := toIdentifier + " = make([]*" +
			typeName + ", len(" + fromIdentifier + "))"
		c.Lines = append(c.Lines, line)
	} else {
		line := toIdentifier + " = make([]" +
			typeName + ", len(" + fromIdentifier + "))"
		c.Lines = append(c.Lines, line)
	}

	line := "for index, value := range " + fromIdentifier + " {"
	c.Lines = append(c.Lines, line)

	if isStruct {
		fromFieldType, ok := fromField.Type.(*compile.ListSpec)
		if !ok {
			return errors.Errorf(
				"Could not convert field (%s): type is not list",
				fromField.Name,
			)
		}

		err = c.genConverterForStruct(
			toField.Name,
			valueStruct,
			fromFieldType.ValueSpec,
			"value",
			keyPrefix+strings.Title(toField.Name)+"[index]",
			"	"+indent,
		)
		if err != nil {
			return err
		}
	} else {
		line = "	" + toIdentifier + "[index] = " + typeName + "(value)"
		c.Lines = append(c.Lines, line)
	}

	line = "}"
	c.Lines = append(c.Lines, line)
	return nil
}

func (c *TypeConverter) genConverterForMap(
	toFieldType *compile.MapSpec,
	toField *compile.FieldSpec,
	fromField *compile.FieldSpec,
	toIdentifier string,
	fromIdentifier string,
	keyPrefix string,
	indent string,
) error {
	typeName, err := c.getGoTypeName(toFieldType.ValueSpec)
	if err != nil {
		return err
	}

	_, isStringKey := toFieldType.KeySpec.(*compile.StringSpec)
	if !isStringKey {
		return errors.Errorf(
			"could not convert key (%s), map is not string-keyed.",
			toField.Name,
		)
	}

	valueStruct, isStruct := toFieldType.ValueSpec.(*compile.StructSpec)
	if isStruct {
		line := toIdentifier + " = make(map[string]*" +
			typeName + ", len(" + fromIdentifier + "))"
		c.Lines = append(c.Lines, line)
	} else {
		line := toIdentifier + " = make(map[string]" +
			typeName + ", len(" + fromIdentifier + "))"
		c.Lines = append(c.Lines, line)
	}

	line := "for key, value := range " + fromIdentifier + " {"
	c.Lines = append(c.Lines, line)

	if isStruct {
		fromFieldType, ok := fromField.Type.(*compile.MapSpec)
		if !ok {
			return errors.Errorf(
				"Could not convert field (%s): type is not map",
				fromField.Name,
			)
		}

		err = c.genConverterForStruct(
			toField.Name,
			valueStruct,
			fromFieldType.ValueSpec,
			"value",
			keyPrefix+strings.Title(toField.Name)+"[key]",
			"	"+indent,
		)
		if err != nil {
			return err
		}
	} else {
		line = "	" + toIdentifier + "[key] = " + typeName + "(value)"
		c.Lines = append(c.Lines, line)
	}

	line = "}"
	c.Lines = append(c.Lines, line)
	return nil
}

func (c *TypeConverter) genStructConverter(
	keyPrefix string,
	indent string,
	fromFields []*compile.FieldSpec,
	toFields []*compile.FieldSpec,
) error {
	for i := 0; i < len(toFields); i++ {
		toField := toFields[i]

		var fromField *compile.FieldSpec
		for j := 0; j < len(fromFields); j++ {
			if fromFields[j].Name == toField.Name {
				fromField = fromFields[j]
				break
			}
		}

		if fromField == nil {
			return errors.Errorf(
				"cannot map by name for the field %s",
				toField.Name,
			)
		}

		toIdentifier := indent + "out." + keyPrefix + strings.Title(toField.Name)
		fromIdentifier := "in." + keyPrefix + strings.Title(fromField.Name)

		// Override thrift type names to avoid naming collisions between endpoint
		// and client types.
		switch toFieldType := toField.Type.(type) {
		case
			*compile.BoolSpec,
			*compile.I8Spec,
			*compile.I16Spec,
			*compile.I32Spec,
			*compile.EnumSpec,
			*compile.I64Spec,
			*compile.DoubleSpec,
			*compile.StringSpec:

			err := c.genConverterForPrimitive(
				toField, toIdentifier, fromIdentifier,
			)
			if err != nil {
				return err
			}
		case *compile.BinarySpec:
			line := toIdentifier + " = []byte(" + fromIdentifier + ")"
			c.Lines = append(c.Lines, line)
		case *compile.TypedefSpec:
			typeName, err := c.getIdentifierName(toField.Type)
			if err != nil {
				return err
			}

			var line string
			// TODO: typedef for struct is invalid here ...
			if toField.Required {
				line = toIdentifier + " = " + typeName + "(" + fromIdentifier + ")"
			} else {
				line = toIdentifier + " = (*" + typeName + ")(" + fromIdentifier + ")"
			}
			c.Lines = append(c.Lines, line)

		case *compile.StructSpec:
			err := c.genConverterForStruct(
				toField.Name,
				toFieldType,
				fromField.Type,
				fromIdentifier,
				keyPrefix+strings.Title(toField.Name),
				indent,
			)
			if err != nil {
				return err
			}
		case *compile.ListSpec:
			err := c.genConverterForList(
				toFieldType,
				toField,
				fromField,
				toIdentifier,
				fromIdentifier,
				keyPrefix,
				indent,
			)
			if err != nil {
				return err
			}
		case *compile.MapSpec:
			err := c.genConverterForMap(
				toFieldType,
				toField,
				fromField,
				toIdentifier,
				fromIdentifier,
				keyPrefix,
				indent,
			)
			if err != nil {
				return err
			}
		default:
			// fmt.Printf("Unknown type %s for field %s \n",
			// 	toField.Type.TypeCode().String(), toField.Name,
			// )

			// pkgName, err := h.TypePackageName(toField.Type.ThriftFile())
			// if err != nil {
			// 	return nil, err
			// }
			// typeName := pkgName + "." + toField.Type.ThriftName()
			// line := toIdentifier + "(*" + typeName + ")" + postfix
			// c.Lines = append(c.Lines, line)
		}
	}

	return nil
}

// GenStructConverter will add lines to the TypeConverter for mapping
// from one go struct to another based on two thriftrw.FieldGroups
func (c *TypeConverter) GenStructConverter(
	fromFields []*compile.FieldSpec,
	toFields []*compile.FieldSpec,
) error {
	err := c.genStructConverter("", "", fromFields, toFields)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>codegen/type_converter: add coverag ignore for panics<commit_after>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package codegen

import (
	"fmt"
	"strings"

	"github.com/pkg/errors"
	"go.uber.org/thriftrw/compile"
)

// TypeConverter can generate a function body that converts two thriftrw
// FieldGroups from one to another. It's assumed that the converted code
// operates on two variables, "in" and "out" and that both are a go struct.
type TypeConverter struct {
	Lines  []string
	Helper PackageNameResolver
}

// PackageNameResolver interface allows for resolving what the
// package name for a thrift file is. This depends on where the
// thrift-based structs are generated.
type PackageNameResolver interface {
	TypePackageName(thriftFile string) (string, error)
}

func (c *TypeConverter) getGoTypeName(
	valueType compile.TypeSpec,
) (string, error) {
	switch s := valueType.(type) {
	case *compile.BoolSpec:
		return "bool", nil
	case *compile.I8Spec:
		return "int8", nil
	case *compile.I16Spec:
		return "int16", nil
	case *compile.I32Spec:
		return "int32", nil
	case *compile.I64Spec:
		return "int64", nil
	case *compile.DoubleSpec:
		return "float64", nil
	case *compile.StringSpec:
		return "string", nil
	case *compile.BinarySpec:
		return "[]byte", nil
	case *compile.MapSpec:
		/* coverage ignore next line */
		panic("Not Implemented")
	case *compile.SetSpec:
		/* coverage ignore next line */
		panic("Not Implemented")
	case *compile.ListSpec:
		/* coverage ignore next line */
		panic("Not Implemented")
	case *compile.EnumSpec, *compile.StructSpec, *compile.TypedefSpec:
		return c.getIdentifierName(s)
	default:
		/* coverage ignore next line */
		panic(fmt.Sprintf("Unknown type (%T) %v", valueType, valueType))
	}
}

func (c *TypeConverter) getIdentifierName(
	fieldType compile.TypeSpec,
) (string, error) {
	pkgName, err := c.Helper.TypePackageName(fieldType.ThriftFile())
	if err != nil {
		return "", errors.Wrapf(
			err,
			"could not lookup fieldType when building converter for %s :",
			fieldType.ThriftName(),
		)
	}
	typeName := pkgName + "." + fieldType.ThriftName()
	return typeName, nil
}

func (c *TypeConverter) genConverterForStruct(
	toFieldName string,
	toFieldType *compile.StructSpec,
	fromFieldType compile.TypeSpec,
	fromIdentifier string,
	keyPrefix string,
	indent string,
) error {
	toIdentifier := "out." + keyPrefix

	typeName, err := c.getIdentifierName(toFieldType)
	if err != nil {
		return err
	}
	subToFields := toFieldType.Fields

	fromFieldStruct, ok := fromFieldType.(*compile.StructSpec)
	if !ok {
		return errors.Errorf(
			"could not convert struct fields, "+
				"incompatible type for %s :",
			toFieldName,
		)
	}

	line := indent + "if " + fromIdentifier + " != nil {"
	c.Lines = append(c.Lines, line)

	line = indent + "	" + toIdentifier + " = &" + typeName + "{}"
	c.Lines = append(c.Lines, line)

	subFromFields := fromFieldStruct.Fields
	err = c.genStructConverter(
		keyPrefix+".",
		indent+"	",
		subFromFields,
		subToFields,
	)
	if err != nil {
		return err
	}

	line = indent + "} else {"
	c.Lines = append(c.Lines, line)

	line = indent + "	" + toIdentifier + " = nil"
	c.Lines = append(c.Lines, line)

	line = indent + "}"
	c.Lines = append(c.Lines, line)

	return nil
}

func (c *TypeConverter) genConverterForPrimitive(
	toField *compile.FieldSpec,
	toIdentifier string,
	fromIdentifier string,
) error {
	var line string
	typeName, err := c.getGoTypeName(toField.Type)
	if err != nil {
		return err
	}

	if toField.Required {
		line = toIdentifier + " = " + typeName + "(" + fromIdentifier + ")"
	} else {
		line = toIdentifier + " = (*" + typeName + ")(" + fromIdentifier + ")"
	}
	c.Lines = append(c.Lines, line)
	return nil
}

func (c *TypeConverter) genConverterForList(
	toFieldType *compile.ListSpec,
	toField *compile.FieldSpec,
	fromField *compile.FieldSpec,
	toIdentifier string,
	fromIdentifier string,
	keyPrefix string,
	indent string,
) error {
	typeName, err := c.getGoTypeName(toFieldType.ValueSpec)
	if err != nil {
		return err
	}

	valueStruct, isStruct := toFieldType.ValueSpec.(*compile.StructSpec)
	if isStruct {
		line := toIdentifier + " = make([]*" +
			typeName + ", len(" + fromIdentifier + "))"
		c.Lines = append(c.Lines, line)
	} else {
		line := toIdentifier + " = make([]" +
			typeName + ", len(" + fromIdentifier + "))"
		c.Lines = append(c.Lines, line)
	}

	line := "for index, value := range " + fromIdentifier + " {"
	c.Lines = append(c.Lines, line)

	if isStruct {
		fromFieldType, ok := fromField.Type.(*compile.ListSpec)
		if !ok {
			return errors.Errorf(
				"Could not convert field (%s): type is not list",
				fromField.Name,
			)
		}

		err = c.genConverterForStruct(
			toField.Name,
			valueStruct,
			fromFieldType.ValueSpec,
			"value",
			keyPrefix+strings.Title(toField.Name)+"[index]",
			"	"+indent,
		)
		if err != nil {
			return err
		}
	} else {
		line = "	" + toIdentifier + "[index] = " + typeName + "(value)"
		c.Lines = append(c.Lines, line)
	}

	line = "}"
	c.Lines = append(c.Lines, line)
	return nil
}

func (c *TypeConverter) genConverterForMap(
	toFieldType *compile.MapSpec,
	toField *compile.FieldSpec,
	fromField *compile.FieldSpec,
	toIdentifier string,
	fromIdentifier string,
	keyPrefix string,
	indent string,
) error {
	typeName, err := c.getGoTypeName(toFieldType.ValueSpec)
	if err != nil {
		return err
	}

	_, isStringKey := toFieldType.KeySpec.(*compile.StringSpec)
	if !isStringKey {
		return errors.Errorf(
			"could not convert key (%s), map is not string-keyed.",
			toField.Name,
		)
	}

	valueStruct, isStruct := toFieldType.ValueSpec.(*compile.StructSpec)
	if isStruct {
		line := toIdentifier + " = make(map[string]*" +
			typeName + ", len(" + fromIdentifier + "))"
		c.Lines = append(c.Lines, line)
	} else {
		line := toIdentifier + " = make(map[string]" +
			typeName + ", len(" + fromIdentifier + "))"
		c.Lines = append(c.Lines, line)
	}

	line := "for key, value := range " + fromIdentifier + " {"
	c.Lines = append(c.Lines, line)

	if isStruct {
		fromFieldType, ok := fromField.Type.(*compile.MapSpec)
		if !ok {
			return errors.Errorf(
				"Could not convert field (%s): type is not map",
				fromField.Name,
			)
		}

		err = c.genConverterForStruct(
			toField.Name,
			valueStruct,
			fromFieldType.ValueSpec,
			"value",
			keyPrefix+strings.Title(toField.Name)+"[key]",
			"	"+indent,
		)
		if err != nil {
			return err
		}
	} else {
		line = "	" + toIdentifier + "[key] = " + typeName + "(value)"
		c.Lines = append(c.Lines, line)
	}

	line = "}"
	c.Lines = append(c.Lines, line)
	return nil
}

func (c *TypeConverter) genStructConverter(
	keyPrefix string,
	indent string,
	fromFields []*compile.FieldSpec,
	toFields []*compile.FieldSpec,
) error {
	for i := 0; i < len(toFields); i++ {
		toField := toFields[i]

		var fromField *compile.FieldSpec
		for j := 0; j < len(fromFields); j++ {
			if fromFields[j].Name == toField.Name {
				fromField = fromFields[j]
				break
			}
		}

		if fromField == nil {
			return errors.Errorf(
				"cannot map by name for the field %s",
				toField.Name,
			)
		}

		toIdentifier := indent + "out." + keyPrefix + strings.Title(toField.Name)
		fromIdentifier := "in." + keyPrefix + strings.Title(fromField.Name)

		// Override thrift type names to avoid naming collisions between endpoint
		// and client types.
		switch toFieldType := toField.Type.(type) {
		case
			*compile.BoolSpec,
			*compile.I8Spec,
			*compile.I16Spec,
			*compile.I32Spec,
			*compile.EnumSpec,
			*compile.I64Spec,
			*compile.DoubleSpec,
			*compile.StringSpec:

			err := c.genConverterForPrimitive(
				toField, toIdentifier, fromIdentifier,
			)
			if err != nil {
				return err
			}
		case *compile.BinarySpec:
			line := toIdentifier + " = []byte(" + fromIdentifier + ")"
			c.Lines = append(c.Lines, line)
		case *compile.TypedefSpec:
			typeName, err := c.getIdentifierName(toField.Type)
			if err != nil {
				return err
			}

			var line string
			// TODO: typedef for struct is invalid here ...
			if toField.Required {
				line = toIdentifier + " = " + typeName + "(" + fromIdentifier + ")"
			} else {
				line = toIdentifier + " = (*" + typeName + ")(" + fromIdentifier + ")"
			}
			c.Lines = append(c.Lines, line)

		case *compile.StructSpec:
			err := c.genConverterForStruct(
				toField.Name,
				toFieldType,
				fromField.Type,
				fromIdentifier,
				keyPrefix+strings.Title(toField.Name),
				indent,
			)
			if err != nil {
				return err
			}
		case *compile.ListSpec:
			err := c.genConverterForList(
				toFieldType,
				toField,
				fromField,
				toIdentifier,
				fromIdentifier,
				keyPrefix,
				indent,
			)
			if err != nil {
				return err
			}
		case *compile.MapSpec:
			err := c.genConverterForMap(
				toFieldType,
				toField,
				fromField,
				toIdentifier,
				fromIdentifier,
				keyPrefix,
				indent,
			)
			if err != nil {
				return err
			}
		default:
			// fmt.Printf("Unknown type %s for field %s \n",
			// 	toField.Type.TypeCode().String(), toField.Name,
			// )

			// pkgName, err := h.TypePackageName(toField.Type.ThriftFile())
			// if err != nil {
			// 	return nil, err
			// }
			// typeName := pkgName + "." + toField.Type.ThriftName()
			// line := toIdentifier + "(*" + typeName + ")" + postfix
			// c.Lines = append(c.Lines, line)
		}
	}

	return nil
}

// GenStructConverter will add lines to the TypeConverter for mapping
// from one go struct to another based on two thriftrw.FieldGroups
func (c *TypeConverter) GenStructConverter(
	fromFields []*compile.FieldSpec,
	toFields []*compile.FieldSpec,
) error {
	err := c.genStructConverter("", "", fromFields, toFields)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package bonsai

import "net"
import "net/rpc"

type Interpreter struct {
	peers chan *rpc.Client
	techniques []Technique
	techniquesLock chan empty
	queue chan *Node
}

func (i *Interpreter) interpret() *Node {
	i.queue = make(chan *Node)

	listener, err := net.Listen("tcp", ":1234") // TODO: port should be configurable and rpc should be optional
	if err != nil {
		panic(err)
	}

	rpc.Register(i)
	go rpc.Accept(listener)

	root := &Node{lock: make(chan empty, 1), label: "^", children: make([]*Node, 0)}
	go i.enqueue(root)

	sendToPeer := make(chan empty)
	go i.processQueue(sendToPeer)
	go i.monitorResources(sendToPeer)

	<-root.lock
	return root
}

func (i *Interpreter) AddPeer(address string, reply *bool) error {
	peer, err := rpc.Dial("tcp", address)
	if err != nil {
		panic(err)
	}
	i.peers <- peer
	return nil
}

func (i *Interpreter) Enqueue(node *Node, transformation *Node) error {
	go i.enqueue(node)
	<-node.lock
	transformation = node
	return nil
}

func (i *Interpreter) sendToPeer(node *Node) {
	select {
	case peer := <-i.peers:
		i.peers <- peer

		var transformation *Node
		err := peer.Call("Interpreter.Enqueue", node, transformation)
		if err != nil {
			panic(err)
		}

		node.children = transformation.children
		i.enqueue(node)

	default:
		i.transform(node)
	}
}

func (i *Interpreter) enqueue(node *Node) {
	for _, child := range node.children {
		go i.enqueue(child)
	}
	i.queue <- node
}

func (i *Interpreter) processQueue(sendToPeer chan empty) {
	for {
		node := <-i.queue
		go func() {
			for _, child := range node.children {
				<-child.lock
			}
			select {
			case <-sendToPeer:
				i.sendToPeer(node)
			default:
				i.transform(node)
			}
		}()
	}
}

// TODO: continuously monitor resources and use sendToPeer to say how many nodes to send away
func (i *Interpreter) monitorResources(sendToPeer chan empty) {
	sendToPeer <- empty{}
}

func (i *Interpreter) transform(node *Node) {
	done := make(chan empty, 1)
	//techniquesDone := make(chan empty, len(i.techniques))
	techniquesDone := make(chan int, len(i.techniques))
	go func() {
		for index := range i.techniques {
			//<-techniquesDone
			techniquesDone <- index // TODO: can we iterate over a range w/o using the var?
		}
		done <- empty{}
	}()

	transformations := make(chan []*Node)
	matched := make(map[*Rule]bool)
	mismatched := make(map[*Rule]bool)

	for _, technique := range i.techniques {
		go func() {
			children, techniques, continueUsing := technique.Transform(node, &matched, &mismatched)

			if children == nil {
				//techniquesDone <- empty{}
				<-techniquesDone
			} else {
				transformations <- children
			}

			if techniques != nil {
				i.learnTechniques(techniques)
			}

			if !continueUsing {
				i.unlearnTechnique(&technique)
			}
		}()
	}

	select {
	case children := <-transformations:
		node.children = children
		i.enqueue(node)
	case <-done:
		node.lock <- empty{}
	}
}

// TODO: this needs to work with rpc peers
func (i *Interpreter) learnTechniques(techniques []Technique) {
	i.techniquesLock <- empty{}
	i.techniques = append(i.techniques, techniques...)
	<-i.techniquesLock
}

func (i *Interpreter) unlearnTechnique(technique *Technique) {
	i.techniquesLock <- empty{}
	for index, technique := range i.techniques {
		if i.techniques[index] == technique {
			i.techniques[index] = i.techniques[len(i.techniques) - 1]
			i.techniques = i.techniques[:len(i.techniques) - 1]
			return
		}
	}
	<-i.techniquesLock
}
<commit_msg>expose Interpreter.Techniques<commit_after>package bonsai

import "net"
import "net/rpc"

type Interpreter struct {
	peers chan *rpc.Client
	Techniques []Technique
	techniquesLock chan empty
	queue chan *Node
}

func (i *Interpreter) interpret() *Node {
	i.queue = make(chan *Node)

	listener, err := net.Listen("tcp", ":1234") // TODO: port should be configurable and rpc should be optional
	if err != nil {
		panic(err)
	}

	rpc.Register(i)
	go rpc.Accept(listener)

	root := &Node{lock: make(chan empty, 1), label: "^", children: make([]*Node, 0)}
	go i.enqueue(root)

	sendToPeer := make(chan empty)
	go i.processQueue(sendToPeer)
	go i.monitorResources(sendToPeer)

	<-root.lock
	return root
}

func (i *Interpreter) AddPeer(address string, reply *bool) error {
	peer, err := rpc.Dial("tcp", address)
	if err != nil {
		panic(err)
	}
	i.peers <- peer
	return nil
}

func (i *Interpreter) Enqueue(node *Node, transformation *Node) error {
	go i.enqueue(node)
	<-node.lock
	transformation = node
	return nil
}

func (i *Interpreter) sendToPeer(node *Node) {
	select {
	case peer := <-i.peers:
		i.peers <- peer

		var transformation *Node
		err := peer.Call("Interpreter.Enqueue", node, transformation)
		if err != nil {
			panic(err)
		}

		node.children = transformation.children
		i.enqueue(node)

	default:
		i.transform(node)
	}
}

func (i *Interpreter) enqueue(node *Node) {
	for _, child := range node.children {
		go i.enqueue(child)
	}
	i.queue <- node
}

func (i *Interpreter) processQueue(sendToPeer chan empty) {
	for {
		node := <-i.queue
		go func() {
			for _, child := range node.children {
				<-child.lock
			}
			select {
			case <-sendToPeer:
				i.sendToPeer(node)
			default:
				i.transform(node)
			}
		}()
	}
}

// TODO: continuously monitor resources and use sendToPeer to say how many nodes to send away
func (i *Interpreter) monitorResources(sendToPeer chan empty) {
	sendToPeer <- empty{}
}

func (i *Interpreter) transform(node *Node) {
	done := make(chan empty, 1)
	//techniquesDone := make(chan empty, len(i.techniques))
	techniquesDone := make(chan int, len(i.Techniques))
	go func() {
		for index := range i.Techniques {
			//<-techniquesDone
			techniquesDone <- index // TODO: can we iterate over a range w/o using the var?
		}
		done <- empty{}
	}()

	transformations := make(chan []*Node)
	matched := make(map[*Rule]bool)
	mismatched := make(map[*Rule]bool)

	for _, technique := range i.Techniques {
		go func() {
			children, techniques, continueUsing := technique.Transform(node, &matched, &mismatched)

			if children == nil {
				//techniquesDone <- empty{}
				<-techniquesDone
			} else {
				transformations <- children
			}

			if techniques != nil {
				i.learnTechniques(techniques)
			}

			if !continueUsing {
				i.unlearnTechnique(&technique)
			}
		}()
	}

	select {
	case children := <-transformations:
		node.children = children
		i.enqueue(node)
	case <-done:
		node.lock <- empty{}
	}
}

// TODO: this needs to work with rpc peers
func (i *Interpreter) learnTechniques(techniques []Technique) {
	i.techniquesLock <- empty{}
	i.Techniques = append(i.Techniques, techniques...)
	<-i.techniquesLock
}

func (i *Interpreter) unlearnTechnique(technique *Technique) {
	i.techniquesLock <- empty{}
	for index, technique := range i.Techniques {
		if i.Techniques[index] == technique {
			i.Techniques[index] = i.Techniques[len(i.Techniques) - 1]
			i.Techniques = i.Techniques[:len(i.Techniques) - 1]
			return
		}
	}
	<-i.techniquesLock
}
<|endoftext|>


<commit_before>package handler

import (
	"errors"
	"fmt"
	"net/http"
	"runtime/debug"

	"github.com/kataras/iris"
	"github.com/mlanin/go-apierr"
)

// Config for the Handler.
type Config struct {
	EnvGetter   func() string
	DebugGetter func() bool
}

// Handler for APIErrors.
type Handler struct {
	Config Config
}

// New restores the server on internal server errors (panics)
// returns the middleware
//
// is here for compatiblity
func New(cfg Config) *Handler {
	return &Handler{Config: cfg}
}

// Serve the middleware.
func (h *Handler) Serve(ctx *iris.Context) {
	defer func() {
		if err := recover(); err != nil {
			fail := h.convertToAPIError(err)

			if h.needToReport(fail) {
				ctx.Log("[apierr.APIError] %+v [%+v]", err, fail.Context)
				if h.needToAddTrace(fail) {
					ctx.Log("[Trace] %s", debug.Stack())
				}
			}

			ctx.JSON(fail.HTTPCode, fail)
		}
	}()

	ctx.Next()
}

// Converts catched error to internal apierr.APIError instance.
func (h *Handler) convertToAPIError(err interface{}) *apierr.APIError {
	var fail *apierr.APIError

	switch err := err.(type) {
	case *apierr.APIError:
		fail = err
	case error:
		fail = h.NewAPIError(err.(error))
	case string:
		fail = h.NewAPIError(errors.New(err))
	default:
		fail = h.NewAPIError(fmt.Errorf("%v", err))
	}

	return fail
}

// NewAPIError makes new API error.
func (h *Handler) NewAPIError(err error) *apierr.APIError {
	// Don't show unknown error text to user when in production.
	if h.isProduction() {
		return apierr.InternalServerError
	}

	return &apierr.APIError{
		Body: apierr.Body{
			ID:      "internal_server_error",
			Message: err.Error(),
		},
		HTTPCode:     http.StatusInternalServerError,
		ShouldReport: true,
	}
}

// Check if we neer to report the error.
func (h *Handler) needToReport(fail *apierr.APIError) bool {
	return fail.WantsToBeReported() || !h.isProduction()
}

// Check if we neer to report the error.
func (h *Handler) needToAddTrace(fail *apierr.APIError) bool {
	return fail.WantsToShowTrace() || !h.isDebugOn()
}

// Check if app in production mode.
func (h *Handler) isProduction() bool {
	return h.Config.EnvGetter() == "production"
}

// Check if app in debug mode is on.
func (h *Handler) isDebugOn() bool {
	return h.Config.DebugGetter()
}
<commit_msg>Show possible panic line<commit_after>package handler

import (
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"runtime"
	"runtime/debug"

	"github.com/kataras/iris"
	"github.com/mlanin/go-apierr"
)

// Config for the Handler.
type Config struct {
	EnvGetter   func() string
	DebugGetter func() bool
}

// Handler for APIErrors.
type Handler struct {
	Config Config
}

// New restores the server on internal server errors (panics)
// returns the middleware
//
// is here for compatiblity
func New(cfg Config) *Handler {
	return &Handler{Config: cfg}
}

// Serve the middleware.
func (h *Handler) Serve(ctx *iris.Context) {
	defer func() {
		messages := make([]interface{}, 0)

		if err := recover(); err != nil {
			fail := h.convertToAPIError(err)

			if h.needToReport(fail) {
				messages = append(messages, fmt.Sprintf("[apierr.APIError] %+v\n", h.thrower()))
				messages = append(messages, fmt.Sprintf("%+v [%+v]\n", err, fail.Context))
				if h.needToAddTrace(fail) {
					messages = append(messages, string(debug.Stack()))
				}
				ctx.Log(fmt.Sprintln(messages...))
			}

			ctx.JSON(fail.HTTPCode, fail)
		}
	}()

	ctx.Next()
}

// Converts catched error to internal apierr.APIError instance.
func (h *Handler) convertToAPIError(err interface{}) *apierr.APIError {
	var fail *apierr.APIError

	switch err := err.(type) {
	case *apierr.APIError:
		fail = err
	case error:
		fail = h.NewAPIError(err.(error))
	case string:
		fail = h.NewAPIError(errors.New(err))
	default:
		fail = h.NewAPIError(fmt.Errorf("%v", err))
	}

	return fail
}

// NewAPIError makes new API error.
func (h *Handler) NewAPIError(err error) *apierr.APIError {
	// Don't show unknown error text to user when in production.
	if h.isProduction() {
		return apierr.InternalServerError
	}

	return &apierr.APIError{
		Body: apierr.Body{
			ID:      "internal_server_error",
			Message: err.Error(),
		},
		HTTPCode:     http.StatusInternalServerError,
		ShouldReport: true,
	}
}

// Check if we neer to report the error.
func (h *Handler) needToReport(fail *apierr.APIError) bool {
	return fail.WantsToBeReported() || !h.isProduction()
}

// Check if we neer to report the error.
func (h *Handler) needToAddTrace(fail *apierr.APIError) bool {
	return fail.WantsToShowTrace() || !h.isDebugOn()
}

// Check if app in production mode.
func (h *Handler) isProduction() bool {
	return h.Config.EnvGetter() == "production"
}

// Check if app in debug mode is on.
func (h *Handler) isDebugOn() bool {
	return h.Config.DebugGetter()
}

// Return a possible line, where panic was thrown.
func (h *Handler) thrower() string {
	var goSrcRegexp = regexp.MustCompile(`(mlanin/apierr-handler/.*.go)|(libexec/src/runtime)`)
	var goTestRegexp = regexp.MustCompile(`mlanin/apierr-handler/.*test.go`)

	for i := 2; i < 15; i++ {
		_, file, line, ok := runtime.Caller(i)
		if ok && (!goSrcRegexp.MatchString(file) || goTestRegexp.MatchString(file)) {
			return fmt.Sprintf("%v:%v", file, line)
		}
	}

	return ""
}
<|endoftext|>


<commit_before>package hamming

func Distance(a, b string) (d int) {
	return
}
<commit_msg>hamming.Distance example; passes all tests.<commit_after>package hamming

func Distance(a, b string) (d int) {
	if len(b) < len(a) {
		a, b = b, a
	}

	for i, _ := range a {
		if a[i] != b[i] {
			d++
		}
	}

	return
}
<|endoftext|>