trygo

package module
v0.0.0-...-038ac2f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2019 License: MIT Imports: 18 Imported by: 0

README

TryGo: Go with 'try' operator

This is a translator of 'TryGo' as my experiment to see what happens if Go were having try() function. Basic idea of try() came from Rust's try! macro (or ? operator). try() handles if err != nil check implicitly.

This package provides a code translator from TryGo (Go with try()) to Go.

Go:

func CreateFileInSubdir(subdir, filename string, content []byte) error {
    cwd, err := os.Getwd()
    if err != nil {
        return err
    }

    if err := os.Mkdir(filepath.Join(cwd, subdir)); err != nil {
        return err
    }

    p := filepath.Join(cwd, subdir, filename)
    f, err := os.Create(p)
    if err != nil {
        return err
    }
    defer f.Close()

    if _, err := f.Write(content); err != nil {
        return err
    }

    fmt.Println("Created:", p)
    return nil
}

TryGo:

func CreateFileInSubdir(subdir, filename string, content []byte) error {
    cwd := try(os.Getwd())

    try(os.Mkdir(filepath.Join(cwd, subdir)))

    p := filepath.Join(cwd, subdir, filename)
    f := try(os.Create(p))
    defer f.Close()

    try(f.Write(content))

    fmt.Println("Created:", p)
    return nil
}

There is only one difference between Go and TryGo. Special magic function try() is provided in TryGo.

Spec

try looks function, but actually it is a special operator. It has variadic parameters and variadic return values. In terms of Go, try looks like:

func try(ret... interface{}, err error) (... interface{})

Actually try() is a set of macros which takes one function call and expands it to a code with error check. It takes one function call as argument since Go only allows multiple values as return values of function call.

In following subsections, $zerovals is expanded to zero-values of return values of the function. For example, when try() is used in func () (int, error), $zerovals will be 0. When it is used in func () (*SomeStruct, SomeInterface, SomeStruct, error), $zerovals will be nil, nil, SomeStruct{}.

Implementation:

  • Definition statement
  • Assignment statement
  • Call statement
  • Call Expression
Definition statement
$Vars := try($CallExpr)

var $Vars = try($CallExpr)

Expanded to:

$Vars, err := $CallExpr
if err != nil {
    return $zerovals, err
}

var $Vars, err = $CallExpr
if err != nil {
    return $zerovals, err
}
Assignment statement
$Assignee = try($CallExpr)

Expanded to:

var err error
$Assignee, err = $CallExpr
if err != nil {
    return $zerovals, err
}

Assignment operation x op= y (e.g. x += y) is supported.

$Assignee op= try($CallExpr)

Expanded to:

$tmp, err := $CallExpr
if err != nil {
    return $zerovals, err
}
$Assignee op= $tmp
Call statement
try($CallExpr)

Expanded to:

if $underscores, err := $CallExpr; err != nil {
    return err
}

$underscores, is a set of _s which ignores all return values from $CallExpr. For example, when calling func() (int, error), it is expanded to _. When calling func() (A, B, error) in try(), it is expanded to _, _. When calling func() error in try(), it is expanded to an empty.

Call Expression

try() call except for toplevel in block

1 + try($CallExpr)

Expanded to:

$tmp, err := $CallExpr
if err != nil {
    return $zerovals, err
}
1 + $tmp

This should allow nest. For example,

1 + try(Foo(try($CallExpr), arg))
$tmp1, err := $CallExpr
if err != nil {
    return $zerovals, err
}
$tmp2, err := Foo($tmp1, arg)
if err != nil {
    return $zerovals, err
}
1 + $tmp2

The order of evaluation must be preserved. For example, when try() is used in a slice literal element, elements before the element must be calculated before the if err != nil check of the try().

For example,

ss := []string{"aaa", s1 + "x", try(f()), s2[:n]}

will be translated to

tmp1 := "aaa"
tmp2 := s1 + "x"
tmp3, err := f()
if err != nil {
    return $zerovals, err
}
ss := []string{tmp1, tmp2, tmp3, s2[:n]}
Ill-formed cases
  • try() cannot take other than function call. For example, try(42) is ill-formed.
  • try() is expanded to code including return. Using it outside functions is ill-formed.
  • When function called in try() invocation does not return error as last of return values, it is ill-formed.

These ill-formed code should be detected by translator and it will raise an error.

Why try() 'function'? Why not ? operator?

Following code may look even better. At least I think so.

func CreateFile(subdir, filename string, content []byte) error {
    cwd := os.Getwd()?
    os.Mkdir(filepath.Join(cwd, subdir))?
    f := os.Create(filepath.Join(cwd, subdir, filename))?
    defer f.Close()
    f.Write(content)?
    return nil
}

The reason why I adopted try() function is that ...TODO

Installation

Download an executable binary from release page (NOT YET).

To build from source:

$ go get -u github.com/rhysd/trygo/cmd/trygo

Usage

$ trygo -o {outpath} {inpaths}

{inpaths} is a list of directory paths of Go packages you want to translate. The directories are translated recursively. For example, when dir is passed and there are 2 packages dir and dir/nested, both packages will be translated.

{outpath} is a directory path where translated Go packages are put. For example, when dir is specified as {inpaths} and out is specified as {outpath}, dir/** packages are translated as out/dir/**.

License

MIT License

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Check

func Check(pkgs []*Package) error

Check checks given packages. It eliminates all try() calls then runs type check against packages. Returning nil means check was OK.

func InitLog

func InitLog(enabled bool)

InitLog initializes logging instance. When true is given as enabled, all logs are output to stderr while code generations.

func Translate

func Translate(pkgs []*Package) error

Translate translates all given TryGo packages by modifying given slice of Packages directly. After translation, the given packages are translated to Go packages. Each Package instance's Node and Files fields must be set with the results of an AST and tokens parsed from TryGo source. And Birth must be set correctly as package directory of the TryGo source. When translation failed, it returns an error as soon as possible. Given Package instances may be no longer correct.

Types

type Gen

type Gen struct {
	// OutDir is a directory path to output directory. This value must be an absolute path
	OutDir string
	// Writer is a writer to output messages
	Writer io.Writer
}

Gen represents a generator of trygo

Example
package main

import (
	"fmt"
	"github.com/rhysd/trygo"
	"io/ioutil"
	"path/filepath"
)

func main() {
	pkgDir := filepath.Join("testdata", "example")
	outDir := filepath.Join(pkgDir, "out")

	// Create a code generator for TryGo to Go translation
	gen, err := trygo.NewGen(outDir)
	if err != nil {
		panic(err)
	}

	// Generate() outputs translated file paths by default. If you don't want them, please set
	// ioutil.Discard as writer.
	gen.Writer = ioutil.Discard

	// Translate TryGo package into Go and generate it at outDir with output verification.
	// It generates testdata/example/out.
	if err := gen.Generate([]string{pkgDir}, true); err != nil {
		panic(err)
	}

	fmt.Println("OK")
}
Output:

OK

func NewGen

func NewGen(outDir string) (*Gen, error)

NewGen creates a new Gen instance with given output directory. All translated packages are generated under the output directory. When the output directory does not exist, it is automatically created.

func (*Gen) Check

func (gen *Gen) Check(paths []string) error

Check checks packages in given paths. Nothing is generated. When check was OK, it returns nil.

func (*Gen) Generate

func (gen *Gen) Generate(paths []string, verify bool) error

Generate collects all TryGo packages under given paths, translates all the TryGo packages specified with directory paths and generates translated Go files with the same directory structures under output directory. When 'verify' argument is set to true, translated packages are verified with type checks after generating the Go files. When the verification reports some errors, generated Go files would be broken. This verification is mainly used for debugging. When collecting TryGo packages from paths failed, packages parsing TryGo sources failed or the translations failed, translated Go file could not be written, this function returns an error.

func (*Gen) GeneratePackages

func (gen *Gen) GeneratePackages(pkgDirs []string, verify bool) error

GeneratePackages translates all TryGo packages specified with directory paths and generates translated Go files with the same directory structures under output directory. When 'verify' argument is set to true, translated packages are verified with type checks after generating the Go files. When the verification reports some errors, generated Go files would be broken. This verification is mainly used for debugging. When parsing Go(TryGo) sources failed or the translations failed, translated Go file could not be written, this function returns an error.

func (*Gen) PackageDirs

func (gen *Gen) PackageDirs(paths []string) ([]string, error)

PackageDirs collects package directories under given paths. If paths argument is empty, it collects a package directory as `go generate` runs trygo. If no Go package is found or pacakge directory cannot be read, this function returns an error.

func (*Gen) ParsePackages

func (gen *Gen) ParsePackages(pkgDirs []string) ([]*Package, error)

ParsePackages parses given package directories and returns parsed packages. Output directory where translated package is put is calculated based on output directory.

func (*Gen) TranslatePackages

func (gen *Gen) TranslatePackages(pkgDirs []string) ([]*Package, error)

TranslatePackages translates all packages specified with directory paths. It returns slice of Package which represent translated packages. When parsing Go(TryGo) sources failed or the translations failed, this function returns an error.

type Package

type Package struct {
	// Files is a token file set to get position information of nodes.
	Files *token.FileSet
	// Node is an AST package node which was parsed from TryGo code. AST will be directly modified
	// by translations.
	Node *ast.Package
	// Path is a package path where this translated package *will* be created.
	Path string
	// Birth is a pacakge path where this translated package was translated from.
	Birth string
	// Types is a type information of the package. This field is nil by default and set as the result
	// of verification. So this field is non-nil only when verification was performed.
	Types *types.Package
	// contains filtered or unexported fields
}

Package represents tranlated package. It contains tokens and AST of all Go files in the package

func NewPackage

func NewPackage(node *ast.Package, srcPath, destPath string, fs *token.FileSet) *Package

NewPackage creates a new Package instance containing additional information to AST node

func (*Package) Modified

func (pkg *Package) Modified() bool

Modified returns the package was modified by translation

func (*Package) Verify

func (pkg *Package) Verify() error

Verify verifies the package is valid by type check. When there are some errors, it returns an error created by unifying all errors into one error.

func (*Package) Write

func (pkg *Package) Write() error

func (*Package) WriteFileTo

func (pkg *Package) WriteFileTo(out io.Writer, fpath string) error

WriteFileTo writes translated Go file to the given writer. If given file path does not indicate any translated source, it returns an error.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL