Compare commits

...

89 Commits

Author SHA1 Message Date
Marc Vertes
d494f9e420 interp: support calling goto from sub-scope
As opposed to other symbols, goto labels must be searched in included
scopes, not upper ones. Implement scope.lookdown to perform this,
to allow calls to goto to be embedded in included scopes where label
is defined.

Fixes #953.
2020-11-19 12:48:03 +01:00
Marc Vertes
6da1107c39 fix: do not confuse function call with type conversion
Use node action to better discriminate between function call and type
conversion which have the same pattern at AST level.

Fixes #960.
2020-11-18 14:56:05 +01:00
Marc Vertes
38a7331bf9 interp: fix type check on function signature
Perform function declaration type check from the upper level scope (the scope where the
function is declared), to avoid possible collisions of local variables with package names.

Fixes #957.
2020-11-13 18:02:04 +01:00
Marc Vertes
13783889cb interp: avoid useless interface wrapping
in `callBin`, call arguments are converted to the corresponding
parameter type. In a case of an interface, the original concrete type
should be preserved instead, and only wrapped to an interface type for
internal interpreter types, as runtime values should already implement the
interface.

This change removes the interface wrapping when parameter is a runtime
value (valueT or ptrT to valueT).

This removes some overhead when handling runtime values, and keep a
similar behavior between interpreted and pre-compiled code. For
example, `io.Copy` preserves its internal optimisations when passed a
`bytes.Buffer`.
2020-11-12 10:48:04 +01:00
mpl
ed626f3fb9 interp: support conversion of runtime func into interp func
Conversion of interp func into runtime func already worked, but added a
test anyway to watch out for regressions.

Fixes #941
2020-11-10 00:40:04 +01:00
Marc Vertes
d0a34d467b interp: fix getting unsigned constant value
The function vUint, used to get the unsigned integer value of a value,
variable (frame) or constant, was broken for constant.Value expression.

Fixes #948.
2020-11-09 17:40:04 +01:00
Nicholas Wiersma
83676577ac fix: use the import path for types
When running GTA, the type `path` was set to `rpath`. This equates to the package path (`github.com/traefik/yaegi`) in most cases. In the vendored case the `rpath` is the sub package path `something/vendor/github.com/traefik/yaegi` causing issues in typecheck and likely further down the line. By using the `importPath` it makes this consistent.

**Note:** I have no clue how to test this decently. I am open to options here.

Potentially Fixes #916
2020-11-05 13:42:03 +01:00
mpl
f0fc907269 extract: support building/running yaegi with Go devel
Fixes #928
2020-11-05 11:40:04 +01:00
Marc Vertes
61f4704925 interp: fix CFG in case of for loop with empty init and cond
Refactor `for` variants for clarity. Ensure all possible 8 combinations
are covered.

Fixes #942.
2020-11-05 11:00:04 +01:00
Marc Vertes
b1ccfbf47f interp: apply type conversion on untyped variable at run
Fixes #938.
2020-11-04 18:16:04 +01:00
Marc Vertes
0ed4b362dc interp: implement conversion for interpreter function types
Interpreter function types are represented internally by the AST node
of their definition. The conversion operation creates a new node with
the type field pointing to the target type.

Fixes #936.
2020-11-03 17:48:03 +01:00
Marc Vertes
98807387a4 interp: resolve type for untyped shift expressions
A non-constant shift expression can be untyped, requiring to apply a
type from inherited context. This change insures that such context is
propagated during CFG pre-order walk, to be used if necessary.
    
Fixes #927.
2020-11-02 18:08:04 +01:00
Marc Vertes
c817823ba1 interp: fix incorrect infinite loop on for statement
Add a for statement variant for the case of a "for" with an init, no
condition and no post-increment.

Fixes #933.
2020-11-02 17:52:03 +01:00
mpl
3cb8bca81a interp: on panic, look for node where offending exec originated from
The execution flow is such that a node can end up running several chained exec
funcs, some of which actually originate from other nodes. For example, in:

var m []int // L0
println("hello world") // L1
m[0] = 1 // L2

the offending code is on a node on line 2 (out of range error). However, since
the assignment to m is part of the execution flow of the variable m, we'll get
the panic when running all the chained exec funcs attached to the node for m on
line 0.

Which is why, when that happens, we need to look for the actual node (the one on
L2) where the offending instruction originates from, in order to
properly report the origin of the panic.

Fixes #546
2020-11-02 10:26:04 +01:00
Marc Vertes
a38d19288f interp: fix testing for nil interface values
Fixes #924.
2020-10-30 16:20:04 +01:00
Marc Vertes
7f8ffa6719 interp: handle explicit nil values in literal composite values
Fixes #922.
2020-10-27 11:24:04 +01:00
Marc Vertes
513f5e37aa interp: fix type conversion for constant expressions
The case of a constant arithmetic expression being of float kind because
of quotient was not handled correctly. Simplify constant extraction to
reflect.Value first, then conversion to target type, and let reflect Convert
method panic if conversion is not valid.

Fixes #920.
2020-10-23 10:44:04 +02:00
Marc Vertes
9520a92241 interp: fix type in assign of shift operations
Type checking on shift operands was failing for untyped variable values.

Fix propagation of type in assignment. Optimize assignment of arithmetic
operations on variables by skipping the assign and writing directly to
destination frame value in the operator function.

Skip some slow tests when given -short test option.

Fixes #917.
2020-10-23 10:32:03 +02:00
rsteube
d47821bfaa interp: add indexExpr check for reflect.Array
In typecheck.go, type check was failing for expressions like `[2]int{0, 1}[0:]`.
```
cannot slice type [2]int
```

Fixes #914 .
2020-10-21 23:00:04 +02:00
rsteube
d7ede8ed5c interp: added sliceExpr and starExpr
Type check was failing for expression such as: `&(*tree)[node:][0]`, as in

```go
tree, _ := huffmanTreePool.Get().(*[]huffmanTree)
...

initHuffmanTree(&(*tree)[node:][0], histogram[l], -1, int16(l))
```

see c3da72aa01/brotli_bit_stream.go (L469)
2020-10-21 18:14:03 +02:00
Marc Vertes
22c63b225c interp: fix array size definition in case of forward declared constant
In type.go, the symbol lookup on constant id was not performed. Handle
the ident kind explicitely to allow that.

Fixes #911.
2020-10-21 13:08:03 +02:00
mpl
c0eaab0891 interp: fix assignable check
The assignable check used to be too strict as it lacked the property that
if an untyped const can be represented as a T, then it is assignable to T.

And we can now use that fixed check to add a missing check: in a return
statement, we now make sure that any of the returned elements are
assignable to what the signature tells us they should be.
2020-10-21 10:06:03 +02:00
rsteube
c74d050c5a interp: added kind to log for typecheck (#910) 2020-10-21 09:46:05 +02:00
Marc Vertes
804664c631 interp: fix type check in constant unary operations
In unary constant operations, the test for unsigned was defeated by
testing for int first, which is true also for unsigned. Make sure that
testing for unsigned precedes testing for int.

Fixes #907.
2020-10-19 10:32:03 +02:00
Kamil Samigullin
f6d8261a8a chore: add homebrew-tap recipe
Publish homebrew-tap recipe to https://github.com/traefik/homebrew-tap
2020-10-17 05:54:04 +02:00
Marc Vertes
68c02ce054 feature: expose extract package, previously internal 2020-10-15 18:58:03 +02:00
Marc Vertes
4b3e9ee231 feature: use environment in addition to flags to control test options
This applies to -syscall, -unsafe and -unrestricted flags with the
corresponding env variables YAEGI_SYSCALL, YAEGI_UNSAFE and
YAEGI_UNRESTRICTED, already used in the same way for the run
sub-command.
2020-10-15 18:42:04 +02:00
mpl
8916618a81 interp: API change for Symbols method
Often enough when debugging, one does not know exactly what argument
should be given to Symbols, as it's not always clear in which
scope/namespace the symbol one is looking for is stored in.

Therefore, this change enables the Symbols method to now take the empty
string as an argument, which results in all the known symbols to be
returned (keyed by import path).

As a consequence, when an non-empty argument is given, the returned
result should be similar to what we had before, except it is now
returned as the sole entry of an encompassing map.

In addition, the "binary" symbols (i.e. the ones ingested through a
Use call), are now also taken into account.
2020-10-15 10:02:04 +02:00
mpl
57b49f40d6 interp: make Symbols method take into account type objects too 2020-10-14 17:44:09 +02:00
Marc Vertes
190dade469 fix: detect a wrong number of arguments at return
Also fix error ouptut in the run command to avoid displaying twice
the same error.

Fixes #819.
2020-10-14 17:30:04 +02:00
Marc Vertes
6b652ea485 feature: provide a version sub-command
A version sub-command is implemented.
A local install target is added to Makefile, to set version from git at build.

Fixes #840.
2020-10-14 16:24:05 +02:00
Marc Vertes
473bc63588 fix: behavior of select and time ticker
There was several issues:
- access to field on pointer to struct from runtime: fix in
  lookupBinField
- assign operation was skipped when performed in a comm clause
- the direction of comm clause was wrong if a channel send operation was
  performed in a body of a receive comm clause

Fixes #884.
2020-10-14 15:50:04 +02:00
Marc Vertes
e32da38ad0 fix: append from / to runtime defined array
Check first for runtime defined array (typ.cat of valueT)
to avoid checking inexisting properties (an panic), when
deciding to use appendSlice or not.

Fixes #880.
2020-10-13 17:14:04 +02:00
Marc Vertes
b2b519c2fd fix: correctly return constant expressions in functions
In the case of a function returning a single value, a constant
result could be ignored, and the function would return a zero value.

Fixes #889.
2020-10-13 17:02:04 +02:00
Marc Vertes
9491e58920 fix: correct handling of select comm clause with empty body
Fixes #887.
2020-10-13 11:24:04 +02:00
Marc Vertes
f362237ac5 fix: perform constant type conversion when required
Fixes #893.
2020-10-12 19:06:03 +02:00
Marc Vertes
a83ec1f925 fix: allow yaegi command to interpret itself
Since the introduction of restricted stdlib and syscall symbols, the
capability of yaegi to interpret itself was broken.
The use of unrestricted symbols is now also controlled by environment
variables, to allow propagation accross nested interpreters.
The interpreter Panic symbol was not wrapped, this is fixed now.
the import path resolution was failing if the working directory was
outside of GOPATH.
The documentation and readme have been ajusted.

Fixes #890.
2020-10-09 11:48:04 +02:00
mpl
155ca4e6ad interp: support implicit-type slice of pointers of struct composite literal
Also fix same case for implicit-type maps of etc.

Fixes #883
2020-10-09 10:32:03 +02:00
mpl
ca196a5768 interp: fix implicit type for bin composite lit case
When the type is implicit, the first element in the list of children is
not the type of the composite literal, but it is one of the actual
children, so it should not be discarded.

Fixes #862
2020-10-05 16:42:03 +02:00
Marc Vertes
b78d55c66b fix: compute array type size from constant expression
Fixes #875.
2020-10-05 16:12:04 +02:00
mpl
16f5586a11 interp: apply integer division when appropriate
When working with an untyped const expression involving a division, if
the default type of the result should be an int (for example because the
default types of all the operands are ints as well), then we should make
sure that the operation that is applied is indeed an integer division,
and that the type of the result is not a float.

This is achieved by using the QUO_ASSIGN operator, instead of the QUO
operator.

This should fix several problems lurking around, and it notably fixes
one of the visible consequences, which is a systematic panic when using
the REPL as a "calculator".

This incidentally also allows us to revert what was done in
5dfc3b86dc since it now turns out it was
just a hack to fix one of the symptoms.

Fixes #864
2020-10-05 10:50:03 +02:00
Julien Breux
c3cf301c60 fix: replace playground link in docs 2020-09-30 10:26:03 +02:00
Marc Vertes
0dde990d0b feature: improve extract, add unrestricted syscalls 2020-09-29 18:42:05 +02:00
Marc Vertes
4cfeb1946e fix: concurrent eval test 2020-09-29 15:22:04 +02:00
Nicholas Wiersma
ec64b006cf fix: convert struct tags properly 2020-09-29 09:22:04 +02:00
Nicholas Wiersma
f36d4e01eb fix: yaegi os.Args should contain the script name 2020-09-28 10:54:05 +02:00
mpl
5dfc3b86dc interp: fix division for const 2020-09-28 10:42:05 +02:00
mpl
3ae01a2af3 interp: refactor doComposite cases 2020-09-22 17:26:03 +02:00
Marc Vertes
c06f83f34a fix: correct access to parameter type for variadic binary methods 2020-09-22 15:18:03 +02:00
Ludovic Fernandez
a6d9c84a30 chore: adds a downloader script. 2020-09-20 18:26:03 +02:00
Marc Vertes
6346d11286 chore: move to new organization 2020-09-16 10:58:04 +02:00
Marc Vertes
1edb6a1424 fix: native build of syscalls on android and illumos
As per https://golang.org/cmd/go/#hdr-Build_constraints,
using GOOS=android also matches tags and files for GOOS=linux,
so exclude it explicetly to avoid collisions.

Also using GOOS=illumos matches tags and files for GOOS=solaris.

Fixes #843.
2020-09-14 17:48:05 +02:00
mpl
a2f56431ea interp: fix data race for composite literal creation
This change fixes two data races related to composite literal creation.

The first one isn't controversial as it is just about initializing the
variable that contains the values in the right place, i.e. within the
n.exec, so that this variable is local to each potential goroutine,
instead of being racily shared by all goroutines.

The second one is more worrying, i.e. having to protect the node typ
with a mutex, because a call to func (t *itype) refType actually
modifies the itype itself, which means it is not concurrent safe.
The change seems to work, and does not seem to introduce regression, but
it is still a concern as it probably is a sign that more similar
guarding has to be done in several other places.
2020-09-14 16:22:04 +02:00
Marc Vertes
42abedb25d fix: keep atomic counter aligned on 64 bits boundary
Fixes #845.
2020-09-14 15:32:03 +02:00
Marc Vertes
151699ef9f feature: test subcommand to run test and benchmark functions
This change allows the interpreter to execute tests and benchmarks
functions provided by packages.

The test subcommand is similar to the "go test" command and
all the relevant flags have been kept.

The ability to evaluate a directory or a package has also been added.

A new method Symbol to access exported symbol values of an interpreted
package has been added. This method is used by the test subcommand.

An EvalTest method has been added to evaluate all Go files, including "*_test.go".

The testing packages from the standard library have been added to stdlib used
symbols.
2020-09-14 11:14:04 +02:00
Marc Vertes
f1f3ca7e06 fix: handle interface fields in literal composite structs
Struct fields of type interface must be converted in wrapper
values to be reachable by the runtime. Call genInterfaceWrapper
on such values.

Fixes #832.
2020-09-09 13:44:04 +02:00
Marc Vertes
9ddecfa121 fix: correct index for embedded binary method receiver
When searching for a binary method on structures, look up on embedded
fields first, otherwise the resulting index is incorrect, as
reflect.Type.MethodByName succeeds also on container struct.

Fixes #834.
2020-09-09 12:22:03 +02:00
mpl
04770a4b81 interp: fix data races (#839)
This change fixes two distinct data races:

1) some global vars of type *itype of the interp package are actually
mutated during the lifecycle of an Interpreter. Even worse: if more than
one Interpreter instance are created and used at a given time, they are
actually racing each other for these global vars.
Therefore, this change replaces these global vars with generator
functions that create the needed type on the fly.

2) the symbols given as argument of Interpreter.Use were directly copied
as reference (since they're maps) when mapped inside an Interpreter
instance. Since the usual case is to give the symbols from the stdlib
package, it means when the interpreter mutates its own symbols in
fixStdio, it would actually mutate the corresponding global vars of the
stdlib package. Again, this is at least racy as soon as several
instances of an Intepreter are concurrently running.
This change fixes the race by making sure Interpreter.Use actually
copies the symbol values instead of copying the references.
2020-09-09 11:59:07 +02:00
Marc Vertes
341c69d922 feat: configure stdin, stdout and stderr per interpreter
The goal is to provide greater control of input, output and error
streams of the interpreter. It is now possible to specify those
as options when creating a new interpreter. The provided values
are propagated to relevant stdlib symbols (i.e fmt.Print, etc).
Care is taken to not update the global variables os.Stdout, os.Stdin
and os.Stderr, as to not interfere with the host process.

The REPL function is now simplified. The deprecated version is removed.

The tests are updated to take advantage of the simplified access
to the interpreter output and errors.

Fixes #752.
2020-08-31 15:42:03 +02:00
mpl
f4cc059e3e TestEvalScanner: "fix" data race
When running TestEvalScanner with -race=true, one can observe a data race such as:

```
WARNING: DATA RACE
Read at 0x0000029f3d68 by goroutine 52:
  github.com/containous/yaegi/interp.(*itype).defaultType()
      /Users/mpl/src/github.com/containous/yaegi/interp/type.go:1466 +0x572
...
  github.com/containous/yaegi/interp.(*Interpreter).EvalWithContext.func1()
      /Users/mpl/src/github.com/containous/yaegi/interp/interp.go:501 +0xf0

Previous write at 0x0000029f3d68 by goroutine 43:
  github.com/containous/yaegi/interp.(*itype).refType()
      /Users/mpl/src/github.com/containous/yaegi/interp/type.go:1419 +0x854
  github.com/containous/yaegi/interp.(*itype).TypeOf()
      /Users/mpl/src/github.com/containous/yaegi/interp/type.go:1427 +0xa6
...
  github.com/containous/yaegi/interp.(*Interpreter).EvalWithContext.func1()
      /Users/mpl/src/github.com/containous/yaegi/interp/interp.go:501 +0xf0
```

Before this change, since closing the pipe to the REPL is done in a defer, it
means that all the i.REPL calls (and hence each goroutine for each of these
calls) are kept running and alive until the very end of the test. It should not
matter, since a new interpreter is created for each test case, and thus all the
i.REPL calls should be completely independent from each other.

And yet, by wrapping each test case in a function call, and thus making each
i.REPL call terminate as soon as the test case is over, the data race seems to
be fixed. This could suggest that the separate i.REPL calls from separate
interpreter instances are somehow sharing some memory, but I do not know how to
explain that.

The problem has yet to be fully understood, but at least this change restores
the test, without making the CI fail again.
2020-08-31 12:46:03 +02:00
mpl
535e7e1c42 interp: enable declaration errors detection at parsing time
The Go parser is able to detect some (but not all) (re-)decleration errors,
if the DeclarationErrors flag is enabled, which was not done so far.

This PR therefore enables that flag, which allows the removal of some of
the now unneeded code that was recently added to support redeclarations.

Fixes #811
2020-08-28 14:36:00 +02:00
mpl
cb0f3a77bb REPL: retry with full wrapping for anonymous func calls
In interactive mode, a line starting with the "func" keyword is usually
"wrapped", by prepending to it a "package main" statement, to make it
a valid piece of Go source code.

However, when the line is actually an anonymous function call, such as:

func() { println(3) }()

then this wrapping is not enough, as this is not valid Go in the global
context. Therefore, this kind of of expression must also be wrapped
inside a main func (as is the default case for most REPL inputs).

Since the detection and handling of such a case turned out to be quite
unelegant, this PR instead introduces a retrying phase when a parsing
error occurs for a particular class of cases. That is to say, when a
"func expression" wrapped in a main package fails to be parsed, it is
then wrapped in a main func before parsing is retried.

N.B. TestEvalScanner has been disabled for this change, because the additional test cases revealed a (most-likely already existing) data race.

Fixes #721
2020-08-28 10:28:15 +02:00
Marc Vertes
b1279d0a21 feature: improve handling of interrupt signal in REPL
The input scanning is now performed in a sub goroutine and
the interrupt is listened in another goroutine, either to cancel Eval
or to cancel the current line scan.
Entering a '\n' after a 'Ctrl-C` to get the prompt is
not necessary anymore.
2020-08-27 15:04:04 +02:00
Nicholas Wiersma
f3f9ffaea7 feat: add builtin type checking
This adds type checking for builtin functions. It also refactors builtin names into constants due to the number of times they are used.
2020-08-27 14:02:04 +02:00
Nicholas Wiersma
e332a6b3be fix: check array size symbol kind
When determining the size of an array and a symbol is found, the symbol must be a const for the type to be valid. 

While it makes sense for this check to be done in type checking, the type can be determined by GTA which would then fail. For now this check is done when getting the node type.

Fixes #825
2020-08-27 11:52:04 +02:00
Nicholas Wiersma
358a57b4b9 feat: add star expression type checking
This adds type checking to StarExpr. This also fixes a bug in assignment where the symbol type was updated but not the scope type associated with it.
2020-08-21 10:56:03 +02:00
Nicholas Wiersma
3640f2f820 feat: add type assertion expression type checking
This adds type checking to TypeAssertExpr. In order to allow for this, method types now have a receiver type in both reflect and native cases.
2020-08-20 17:06:05 +02:00
mpl
3faa47c61e interp: take into account embedded property of struct field
The trick is that in reflect, the field is called Anonymous, which is
actually a different notion in the Go spec/vocable.

n.b. only works for non-recursive types for now.

Fixes #781
2020-08-20 14:51:14 +02:00
mpl
896bfeb5a1 interp: new EvalPath API
The recent changes that added some redeclaration checks implicitly added more
strictness related to namespaces and scopes which, among other things, broke
some uses that "accidentally" used to work.

For example, given

const script1 = `
	import "fmt"

	// more code
`
const script2 = `
	import "fmt"

	// some other code
`
If one Evals script1, then script2, with the same interpreter, without
specifying any scope, as the two fragments would be considered part of the same
(.go file) scope by default, a redeclaration error would be triggered because
import "fmt" is seen twice.

A work-around would have been to specify (a different) i.Name before each Eval
call, so that each script is considered as coming from a different .go file, and
hence are respectively in different scopes with respect to imports.

That lead us to realize we had to make specifying things such as file-scope, and
"incremental mode" (aka REPL), more obvious in the context of an Eval call.

In addition, we want to lay down the foundations for Yaegi being able to behave
more like the go tool wrt to various inputs, i.e. it should be able to take a
package directory, or an import path, as input, instead of just a .go file.

Hence the introduction of a new kind of Eval method (whose signature is not fixed yet):

func (interp *Interpreter) EvalPath(path string) (res reflect.Value, err error)

It partially solves the problem described above because:

1. the path given to EvalPath can be used as the file-scope hint mentioned
above, for now (even though the related implementation details might change).
2. Eval always runs in incremental mode, whereas EvalPath always runs in
non-incremental mode, hence clarifying the situation in that respect.

And to avoid confusion, the Name field of Interpreter is now non-exported,
since it is somewhat redundant with the path argument of EvalPath.

Note that #731 is not fully fixed (and might never be), as a requirement of the
proposed solution is to move the input bits of code into respective files
(instead of leaving them as strings).

Finally, some related bugfixes, documention changes, and some refactoring have
been included. Notably, there is no "empty scope" anymore, i.e. name defaults
to "_.go" when it is not specified.

Updates #731
Fixes #778
Fixes #798
Fixes #789 

Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-08-20 13:14:15 +02:00
Nicholas Wiersma
1029d102e5 feat: add slice expression type checking
This adds type checking to SliceExpr as well as handling any required constant type conversion.

It should be noted that the check that `high` and `max` are not nil in a 3-index slice has been left out as this check is handled before type checking. Tests have been included for these cases.
2020-08-19 16:04:05 +02:00
Marc Vertes
065d4fa4d7 fix: add mutual exclusion locks for cancelable select
In a couple of occurences, tests with enabled race detector exposed
some concurrent accesses to the cancelation callback used in select and
channel operations send and recv for EvalWithContext. This change ensure
that all accesses to this object are protected by mutex.

Fixes #815.
2020-08-19 13:52:04 +02:00
mpl
332becf95d interp: more tests for ignoreScannerError
They cover the extra cases that were "sneakily" added
after 611a8c37fa
2020-08-14 15:04:11 +02:00
Nicholas Wiersma
da9e6a0d6c fix: composite literal type check
In the case of a pointer or alias composite literal expression, `compositeGenerator` changes the type to remove the pointer or alias. This causes a nested composite literal to have the wrong type.

Instead of changing the node type, the removal of the pointer or alias is moved to the runtime, allowing the node type to remain unchanged. This fixes potential issues in the type checking.
2020-08-14 12:14:03 +02:00
Nicholas Wiersma
913680d1ed feat: add call expression (not builtin) type checking
This adds type checking to CallExpr (excluding builtin type checking, as that is a PR in its own right) as well as handling any required constant type conversion.

This also changes constant strings and runes to be represented as `constant.Value`. Runes change `rval` type at CFG typing time to avoid having to type at AST time. There are also changes to importSpecs and `stdlib` to account for the string change. With this all `untyped` types should now be `constant.Value`s, although errors are still not returned if this is not the case to be sure we do not break things.

This also fixed a bug in `itype.methods` that would panic if the type was recursive.
2020-08-14 12:02:04 +02:00
Ludovic Fernandez
a004809fc2 fix: main command for goreleaser. 2020-08-13 14:22:03 +02:00
Marc Vertes
b0cd93a936 fix: correct interrupt signal handling in REPL
Avoid goroutines leak, accumulation of defered functions and
spurious resets of signal handlers. Effectively catch interrupt
signal (Ctrl-C) to cancel current eval.

Fixes #713.
2020-08-12 22:22:03 +02:00
mpl
611a8c37fa interp: make REPL stricter about parsing errors
So far the REPL loop was treating any parsing error coming from
go/parser to generate the AST, as having occurred because the source
code was not yet complete (unfinished block). And it was therefore
ignoring all of them.

However, some of these errors are legitimate, and must be caught as soon
as they occur, otherwise the REPL cycle would stay in an errored state
forever (even when the block terminates), without the user getting any
feedback about it.

Therefore, this change adds an extra check when a parsing error occurs,
i.e. it verifies that it looks like an "EOF" error (unfinished block)
before it ignores it (as the user is supposed to terminate the block
eventually). Otherwise the error is treated just like a "non-parsing"
(cfg, gta, ...) error and printed out.

Fixes #637
2020-08-12 18:44:21 +02:00
Marc Vertes
e71ddc7edd chore: update golangci-lint config (#810)
Disable nlreturn. Fix one comment.
2020-08-12 17:46:28 +02:00
Ludovic Fernandez
1fe75f149d feat: update stdlib mapping for go1.15
- drop stdlib for go1.13
- generate stdlib for go1.15
-  update CI configuration.
2020-08-12 12:38:04 +02:00
Nicholas Wiersma
cdc352cee2 feat: add index and composite literal type checking
This adds type checking to both `IndexExpr` and `CompositeLitExpr` as well as handling any required constant type conversion.

This includes a change to the type propagation to the children of a composite literal. Previously in most cases the composite literal type was propagated to its children. This does not work with type checking as the actual child type is needed.
2020-08-11 15:58:04 +02:00
Nicholas Wiersma
88569f5df7 fix: interface call regression from #787
Fix #787 changes how interfaces are set on a struct (compositeSparce). This change however makes calling the interface panic. 

This PR reverts part of the change in #787 and adds a test to ensure it does not break again.
2020-08-10 16:32:05 +02:00
Marc Vertes
2ac0c6f70b feature: command line provide sub-commands
The Yaegi command line has been changed to provide subcommands.

The following sub-commands are provided:
- extract (formerly goexports)
- help
- run
- test

The previous behaviour is now implemented in run command which
is the default, so the change should be transparent.

In run command, prepare the ability to run a package or a directory
in addition to a file. Not implemented yet

The test command is not implemented yet.

The extract command is meant to generate wrappers to non stdlib
packages.

Fixes #639
2020-08-10 16:20:05 +02:00
Nicholas Wiersma
bd4ce37baa feat: refactor type checking
The previous type checking was off and did not do untyped type conversion. This endeavours to fix this with better type checking in its own type.
2020-07-31 14:00:03 +02:00
Nicholas Wiersma
9c4d3d1e5a chore: updated linter 2020-07-30 11:18:04 +02:00
Marc Vertes
25c681c1e6 fix: regression on range following #787 2020-07-30 10:52:05 +02:00
Marc Vertes
9c51f6bb69 fix: correct range on arrays of interface objects 2020-07-23 12:25:04 +02:00
Marc Vertes
589b2a0cd2 fix: correct conversion to int in slice index expressions. 2020-07-23 12:05:03 +02:00
Marc Vertes
68911f8b4e fix: type assertion expression was not forwarding type
The detection of special cases of interpreter functions and interfaces
is more precise. It allows at least to complete parsing of
code where type is derived from a type assertion expression.

Fixes #770.
2020-07-23 11:51:53 +02:00
mpl
e5a7b0de11 extract: new package to extract symbols from a dependency
cmd/goexports is now based on it.

Updates #639 

Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-07-23 10:58:33 +02:00
649 changed files with 16057 additions and 9387 deletions

2
.gitignore vendored
View File

@@ -3,7 +3,7 @@
*.dot
.idea/
/yaegi
cmd/goexports/goexports
internal/cmd/extract/extract
example/inception/inception
_test/tmp/
/dist

View File

@@ -41,6 +41,8 @@
"testpackage",
"goerr113",
"nestif",
"exhaustive",
"nlreturn",
]
[issues]

View File

@@ -3,32 +3,7 @@ project_name: yaegi
builds:
- id: yaegi
binary: yaegi
main: ./cmd/yaegi/yaegi.go
goos:
- darwin
- linux
# - windows
- freebsd
- openbsd
- solaris
goarch:
- amd64
- 386
- arm
- arm64
goarm:
- 7
- 6
- 5
ignore:
- goos: darwin
goarch: 386
- id: goexports
binary: goexports
main: ./cmd/goexports/goexports.go
main: ./cmd/yaegi/
goos:
- darwin
@@ -70,3 +45,19 @@ archives:
format: zip
files:
- LICENSE
brews:
- github:
owner: traefik
name: homebrew-tap
commit_author:
name: traefiker
email: 30906710+traefiker@users.noreply.github.com
folder: Formula
homepage: https://github.com/traefik/yaegi
description: |
Yaegi is Another Elegant Go Interpreter.
It powers executable Go scripts and plugins, in embedded interpreters
or interactive shells, on top of the Go runtime.
test: |
system "#{bin}/yaegi version"

View File

@@ -19,15 +19,15 @@ cache:
matrix:
fast_finish: true
include:
- go: 1.13.x
- go: 1.14.x
- go: 1.15.x
env: STABLE=true
env:
global:
- GO111MODULE=on
go_import_path: github.com/containous/yaegi
go_import_path: github.com/traefik/yaegi
before_install:
# Install linters and misspell

View File

@@ -3,7 +3,7 @@
Yaegi is an open source project, and your feedback and contributions
are needed and always welcome.
[Issues] and [Pull Requests] are opened at https://github.com/containous/yaegi.
[Issues] and [Pull Requests] are opened at https://github.com/traefik/yaegi.
Non trivial changes should be discussed with the project maintainers by
opening a [Feature Request] clearly explaining rationale, background
@@ -13,12 +13,12 @@ discussions.
Once the proposal is approved, a Pull Request can be opened. If you want
to provide early visibility to reviewers, create a [Draft Pull Request].
We will also require you to sign the [Containous Contributor License Agreement]
We will also require you to sign the [Traefik Contributor License Agreement]
after you submit your first pull request to this project. The link to sign the
agreement will be presented to you in the web interface of the pull request.
[Issues]: https://github.com/containous/yaegi/issues
[Pull Requests]: https://github.com/containous/yaegi/issues
[Feature Request]: https://github.com/containous/yaegi/issues/new?template=feature_request.md
[Issues]: https://github.com/traefik/yaegi/issues
[Pull Requests]: https://github.com/traefik/yaegi/issues
[Feature Request]: https://github.com/traefik/yaegi/issues/new?template=feature_request.md
[Draft Pull Request]: https://github.blog/2019-02-14-introducing-draft-pull-requests/
[Containous Contributor License Agreement]: https://cla-assistant.io/containous/yaegi
[Traefik Labs Contributor License Agreement]: https://cla-assistant.io/traefik/yaegi

View File

@@ -187,6 +187,7 @@
identification within third-party archives.
Copyright 2019 Containous SAS
Copyright 2020 Traefik Labs SAS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -3,21 +3,28 @@ check:
golangci-lint run
# Generate stdlib/syscall/syscall_GOOS_GOARCH.go for all platforms
gen_all_syscall: cmd/goexports/goexports
@cd stdlib/syscall && \
for v in $$(go tool dist list); do \
gen_all_syscall: internal/cmd/extract/extract
@for v in $$(go tool dist list); do \
echo syscall_$${v%/*}_$${v#*/}.go; \
GOOS=$${v%/*} GOARCH=$${v#*/} go generate; \
GOOS=$${v%/*} GOARCH=$${v#*/} go generate ./stdlib/syscall ./stdlib/unrestricted; \
done
cmd/goexports/goexports: cmd/goexports/goexports.go
go generate cmd/goexports/goexports.go
internal/cmd/extract/extract:
rm -f internal/cmd/extract/extract
go generate ./internal/cmd/extract
generate: gen_all_syscall
go generate
tests:
GO111MODULE=off go test -v ./...
GO111MODULE=off go test -race ./interp
install:
GOFLAGS=-ldflags=-X=main.version=$$(git describe --tags) go install ./...
.PHONY: check gen_all_syscall gen_tests
tests:
go test -v ./...
go test -race ./interp
# https://github.com/goreleaser/godownloader
install.sh: .goreleaser.yml
godownloader --repo=traefik/yaegi -o install.sh .goreleaser.yml
.PHONY: check gen_all_syscall gen_tests generate_downloader internal/cmd/extract/extract install

View File

@@ -2,9 +2,9 @@
<img width="400" src="doc/images/yaegi.png" alt="Yaegi" title="Yaegi" />
</p>
[![release](https://img.shields.io/github/tag-date/containous/yaegi.svg?label=alpha)](https://github.com/containous/yaegi/releases)
[![Build Status](https://travis-ci.com/containous/yaegi.svg?branch=master)](https://travis-ci.com/containous/yaegi)
[![GoDoc](https://godoc.org/github.com/containous/yaegi?status.svg)](https://godoc.org/github.com/containous/yaegi)
[![release](https://img.shields.io/github/tag-date/traefik/yaegi.svg?label=alpha)](https://github.com/traefik/yaegi/releases)
[![Build Status](https://travis-ci.com/traefik/yaegi.svg?branch=master)](https://travis-ci.com/traefik/yaegi)
[![GoDoc](https://godoc.org/github.com/traefik/yaegi?status.svg)](https://godoc.org/github.com/traefik/yaegi)
[![Discourse status](https://img.shields.io/discourse/https/community.containo.us/status?label=Community&style=social)](https://community.containo.us/c/yaegi)
Yaegi is Another Elegant Go Interpreter.
@@ -25,18 +25,24 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
### Go package
```go
import "github.com/containous/yaegi/interp"
import "github.com/traefik/yaegi/interp"
```
### Command-line executable
```bash
go get -u github.com/containous/yaegi/cmd/yaegi
go get -u github.com/traefik/yaegi/cmd/yaegi
```
Note that you can use [rlwrap](https://github.com/hanslub42/rlwrap) (install with your favorite package manager),
and alias the `yaegi` command in `alias yaegi='rlwrap yaegi'` in your `~/.bashrc`, to have history and command line edition.
### CI Integration
```bash
curl -sfL https://raw.githubusercontent.com/traefik/yaegi/master/install.sh | bash -s -- -b $GOPATH/bin v0.9.0
```
## Usage
### As an embedded interpreter
@@ -47,8 +53,8 @@ Create an interpreter with `New()`, run Go code with `Eval()`:
package main
import (
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
func main() {
@@ -68,7 +74,7 @@ func main() {
}
```
[Go Playground](https://play.golang.org/p/zzvw4VlerLP)
[Go Playground](https://play.golang.org/p/2n-EpZbMYI9)
### As a dynamic extension framework
@@ -81,7 +87,7 @@ The following program is compiled ahead of time, except `bar()` which is interpr
```go
package main
import "github.com/containous/yaegi/interp"
import "github.com/traefik/yaegi/interp"
const src = `package foo
func Bar(s string) string { return s + "-Foo" }`
@@ -106,7 +112,7 @@ func main() {
}
```
[Go Playground](https://play.golang.org/p/6SEAoaO7n0U)
[Go Playground](https://play.golang.org/p/WvwH4JqrU-p)
### As a command-line interpreter
@@ -122,10 +128,20 @@ Hello World
>
```
Or interpret Go files:
Note that in interactive mode, all stdlib package are pre-imported,
you can use them directly:
```console
$ yaegi cmd/yaegi/yaegi.go
$ yaegi
> reflect.TypeOf(time.Date)
: func(int, time.Month, int, int, int, int, int, *time.Location) time.Time
>
```
Or interpret Go packages, directories or files, including itself:
```console
$ yaegi -syscall -unsafe -unrestricted github.com/traefik/yaegi/cmd/yaegi
>
```
@@ -155,11 +171,11 @@ Documentation about Yaegi commands and libraries can be found at usual [godoc.or
Beside the known [bugs] which are supposed to be fixed in the short term, there are some limitations not planned to be addressed soon:
- assembly files (`.s`) are not supported
- calling C code is not supported (no virtual "C" package)
- interfaces to be used from the pre-compiled code can not be added dynamically, as it is required to pre-compile interface wrappers
- representation of types by `reflect` and printing values using %T may give different results between compiled mode and interpreted mode
- interpreting computation intensive code is likely to remain significantly slower than in compiled mode
- Assembly files (`.s`) are not supported.
- Calling C code is not supported (no virtual "C" package).
- Interfaces to be used from the pre-compiled code can not be added dynamically, as it is required to pre-compile interface wrappers.
- Representation of types by `reflect` and printing values using %T may give different results between compiled mode and interpreted mode.
- Interpreting computation intensive code is likely to remain significantly slower than in compiled mode.
## Contributing
@@ -170,7 +186,7 @@ Beside the known [bugs] which are supposed to be fixed in the short term, there
[Apache 2.0][License].
[specs]: https://golang.org/ref/spec
[docs]: https://godoc.org/github.com/containous/yaegi
[license]: https://github.com/containous/yaegi/blob/master/LICENSE
[github]: https://github.com/containous/yaegi
[bugs]: https://github.com/containous/yaegi/issues?q=is%3Aissue+is%3Aopen+label%3Abug
[docs]: https://godoc.org/github.com/traefik/yaegi
[license]: https://github.com/traefik/yaegi/blob/master/LICENSE
[github]: https://github.com/traefik/yaegi
[bugs]: https://github.com/traefik/yaegi/issues?q=is%3Aissue+is%3Aopen+label%3Abug

16
_test/a42.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"encoding/binary"
"fmt"
)
func main() {
var b [8]byte
binary.LittleEndian.PutUint64(b[:], uint64(1))
fmt.Println(b)
}
// Output:
// [1 0 0 0 0 0 0 0]

17
_test/a43.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
type T [l1 + l2]int
const (
l1 = 2
l2 = 3
)
func main() {
fmt.Println(T{})
}
// Output:
// [0 0 0 0 0]

12
_test/a44.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var a = [max]int{}
const max = 32
func main() {
println(len(a))
}
// Output:
// 32

25
_test/addr0.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"net/http"
)
type extendedRequest struct {
http.Request
Data string
}
func main() {
r := extendedRequest{}
req := &r.Request
fmt.Println(r)
fmt.Println(req)
}
// Output:
// {{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>} }
// &{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>}

22
_test/addr1.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "fmt"
type T struct {
A int
B int
}
func main() {
a := &[]T{
{1, 2},
{3, 4},
}
fmt.Println("a:", a)
x := &(*a)[1:][0]
fmt.Println("x:", x)
}
// Output:
// a: &[{1 2} {3 4}]
// x: &{3 4}

15
_test/append1.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"bufio"
"bytes"
)
func main() {
s := bufio.NewScanner(bytes.NewReader([]byte("Hello\nTest\nLine3")))
s.Scan()
println(string(append(s.Bytes(), []byte(" World")...)))
}
// Output:
// Hello World

15
_test/append2.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"bufio"
"bytes"
)
func main() {
s := bufio.NewScanner(bytes.NewReader([]byte("Hello\nTest\nLine3")))
s.Scan()
println(string(append(s.Bytes(), " World"...)))
}
// Output:
// Hello World

15
_test/assign16.go Normal file
View File

@@ -0,0 +1,15 @@
package main
type H struct {
bits uint
}
func main() {
h := &H{8}
var x uint = (1 << h.bits) >> 6
println(x)
}
// Output:
// 4

View File

@@ -1,6 +1,6 @@
package foo
import bar "github.com/containous/yaegi/_test/b2/foo"
import bar "github.com/traefik/yaegi/_test/b2/foo"
var Desc = "in b1/foo"

View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"image"
)
func main() {
v := map[string]*image.Point{
"foo": {X: 3, Y: 2},
"bar": {X: 4, Y: 5},
}
fmt.Println(v["foo"], v["bar"])
}
// Output:
// (3,2) (4,5)

View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"image"
)
func main() {
v := []*image.Point{
{X: 3, Y: 2},
{X: 4, Y: 5},
}
fmt.Println(v)
}
// Output:
// [(3,2) (4,5)]

16
_test/binstruct_slice0.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"fmt"
"image"
)
func main() {
v := []image.Point{
{X: 3, Y: 2},
}
fmt.Println(v)
}
// Output:
// [(3,2)]

View File

@@ -1,5 +1,5 @@
package c1
import "github.com/containous/yaegi/_test/c2"
import "github.com/traefik/yaegi/_test/c2"
var C1 = c2.C2 + "x"

View File

@@ -1,5 +1,5 @@
package c2
import "github.com/containous/yaegi/_test/c1"
import "github.com/traefik/yaegi/_test/c1"
var C2 = c1.C1 + "Y"

51
_test/cli4.go Normal file
View File

@@ -0,0 +1,51 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)
type mw1 struct {
next http.Handler
}
func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
m.next.ServeHTTP(rw, rq)
}
type mw0 struct{}
func (m *mw0) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Welcome to my website!")
}
func main() {
m0 := &mw0{}
m1 := &mw1{m0}
mux := http.NewServeMux()
mux.HandleFunc("/", m1.ServeHTTP)
server := httptest.NewServer(mux)
defer server.Close()
client(server.URL)
}
func client(uri string) {
resp, err := http.Get(uri)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
// Output:
// Welcome to my website!

51
_test/cli5.go Normal file
View File

@@ -0,0 +1,51 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)
type mw1 struct {
next http.Handler
}
func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
m.next.ServeHTTP(rw, rq)
}
type mw0 struct{}
func (m *mw0) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Welcome to my website!")
}
func main() {
m0 := &mw0{}
m1 := &mw1{next: m0}
mux := http.NewServeMux()
mux.HandleFunc("/", m1.ServeHTTP)
server := httptest.NewServer(mux)
defer server.Close()
client(server.URL)
}
func client(uri string) {
resp, err := http.Get(uri)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
// Output:
// Welcome to my website!

52
_test/cli6.go Normal file
View File

@@ -0,0 +1,52 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)
type T struct {
http.ResponseWriter
}
type mw1 struct {
next http.Handler
}
func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
t := &T{
ResponseWriter: rw,
}
x := t.Header()
fmt.Fprint(rw, "Welcome to my website!", x)
}
func main() {
m1 := &mw1{}
mux := http.NewServeMux()
mux.HandleFunc("/", m1.ServeHTTP)
server := httptest.NewServer(mux)
defer server.Close()
client(server.URL)
}
func client(uri string) {
resp, err := http.Get(uri)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
// Output:
// Welcome to my website!map[]

19
_test/composite12.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type A struct {
C D
}
type D struct {
E string
}
func main() {
a := A{}
a.C = D{"bb"}
println(a.C.E)
}
// Output:
// bb

19
_test/composite13.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type A struct {
C D
}
type D struct {
E string
}
func main() {
a := A{}
a.C = D{E: "bb"}
println(a.C.E)
}
// Output:
// bb

15
_test/composite14.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "fmt"
type T struct {
b []byte
}
func main() {
t := T{nil}
fmt.Println(t)
}
// Output:
// {[]}

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/containous/yaegi/_test/ct1"
"github.com/traefik/yaegi/_test/ct1"
)
type T struct {

16
_test/composite8bis.go Normal file
View File

@@ -0,0 +1,16 @@
package main
type T struct{ I int }
func main() {
t := []*T{}
s := []int{1, 2}
for _, e := range s {
x := &T{I: e}
t = append(t, x)
}
println(t[0].I, t[1].I)
}
// Output:
// 1 2

12
_test/const16.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import (
"fmt"
)
func main() {
fmt.Println(7 / 3)
}
// Output:
// 2

14
_test/const17.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var t [7/3]int
func main() {
t[0] = 3/2
t[1] = 5/2
fmt.Println(t)
}
// Output:
// [1 2]

11
_test/const18.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "time"
func main() {
a := int64(time.Second)
println(a)
}
// Output:
// 1000000000

17
_test/const19.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"time"
)
func get10Hours() time.Duration {
return 10 * time.Hour
}
func main() {
fmt.Println(get10Hours().String())
}
// Output:
// 10h0m0s

12
_test/const20.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
const maxLen = int64(int(^uint(0) >> 1))
func main() {
fmt.Println(maxLen)
}
// Output:
// 9223372036854775807

12
_test/const21.go Normal file
View File

@@ -0,0 +1,12 @@
package main
const a = 64
var b uint = a * a / 2
func main() {
println(b)
}
// Output:
// 2048

21
_test/convert0.go Normal file
View File

@@ -0,0 +1,21 @@
package main
type T struct {
v int
}
type comparator func(T, T) bool
func sort(items []T, comp comparator) {
println("in sort")
}
func compT(t0, t1 T) bool { return t0.v < t1.v }
func main() {
a := []T{}
sort(a, comparator(compT))
}
// Output:
// in sort

17
_test/convert1.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "strconv"
type atoidef func(s string) (int, error)
func main() {
stdatoi := atoidef(strconv.Atoi)
n, err := stdatoi("7")
if err != nil {
panic(err)
}
println(n)
}
// Output:
// 7

19
_test/convert2.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "bufio"
func fakeSplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
return 7, nil, nil
}
func main() {
splitfunc := bufio.SplitFunc(fakeSplitFunc)
n, _, err := splitfunc(nil, true)
if err != nil {
panic(err)
}
println(n)
}
// Output:
// 7

View File

@@ -2,13 +2,14 @@ package main
import (
"log"
"os"
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
func main() {
log.SetFlags(log.Lshortfile)
i := interp.New(interp.Options{})
i := interp.New(interp.Options{Stdout: os.Stdout})
if _, err := i.Eval(`func f() (int, int) { return 1, 2 }`); err != nil {
log.Fatal(err)
}

View File

@@ -1,6 +1,6 @@
package foo
import "github.com/containous/yaegi/_test/foo/boo"
import "github.com/traefik/yaegi/_test/foo/boo"
var Bar = "BARR"
var Boo = boo.Boo

View File

@@ -1,5 +1,5 @@
package foo
import "github.com/containous/yaegi/_test/foo/boo"
import "github.com/traefik/yaegi/_test/foo/boo"
var Bir = boo.Boo + "22"

16
_test/for15.go Normal file
View File

@@ -0,0 +1,16 @@
package main
func f() int { println("in f"); return 1 }
func main() {
for i := f(); ; {
println("in loop")
if i > 0 {
break
}
}
}
// Output:
// in f
// in loop

16
_test/for16.go Normal file
View File

@@ -0,0 +1,16 @@
package main
func main() {
max := 1
for ; ; max-- {
if max == 0 {
break
}
println("in for")
}
println("bye")
}
// Output:
// in for
// bye

10
_test/fun23.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x int) { return x }
func main() {
print("hello")
}
// Error:
// 3:17: too many arguments to return

10
_test/fun24.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x int) (int, int) { return x, "foo" }
func main() {
print("hello")
}
// Error:
// cannot use "foo" (type stringT) as type intT in return argument

10
_test/fun25.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x string) (a int, b int) { return x, 5 }
func main() {
print("hello")
}
// Error:
// cannot use x (type stringT) as type intT in return argument

17
_test/fun26.go Normal file
View File

@@ -0,0 +1,17 @@
package main
type F func() (int, error)
func f1() (int, error) { return 3, nil }
func f2(a string, f F) {
c, _ := f()
println(a, c)
}
func main() {
f2("hello", F(f1))
}
// Output:
// hello 3

12
_test/goto1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func main() {
if true {
goto here
}
here:
println("ok")
}
// Output:
// ok

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/foo"
import "github.com/traefik/yaegi/_test/foo"
func main() { println(foo.Bar, foo.Boo) }

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/p1"
import "github.com/traefik/yaegi/_test/p1"
func main() { println("num:", p1.Uint32()) }

View File

@@ -1,6 +1,6 @@
package main
import boo "github.com/containous/yaegi/_test/foo"
import boo "github.com/traefik/yaegi/_test/foo"
func main() { println(boo.Bar, boo.Boo, boo.Bir) }

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/c1"
import "github.com/traefik/yaegi/_test/c1"
func main() {
println(c1.C1)
@@ -8,4 +8,4 @@ func main() {
// Error:
// import cycle not allowed
// imports github.com/containous/yaegi/_test/c1
// imports github.com/traefik/yaegi/_test/c1

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/foo-bar"
import bar "github.com/traefik/yaegi/_test/foo-bar"
func main() {
println(bar.Name)

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/b1/foo"
import "github.com/traefik/yaegi/_test/b1/foo"
func main() {
println(foo.Desc)

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/baz-bat"
import "github.com/traefik/yaegi/_test/baz-bat"
func main() {
println(baz.Name)

View File

@@ -3,14 +3,14 @@ package main
import (
"log"
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
func main() {
log.SetFlags(log.Lshortfile)
i := interp.New(interp.Options{})
i.Use(interp.Symbols)
if _, err := i.Eval(`import "github.com/containous/yaegi/interp"`); err != nil {
if _, err := i.Eval(`import "github.com/traefik/yaegi/interp"`); err != nil {
log.Fatal(err)
}
if _, err := i.Eval(`i := interp.New(interp.Options{})`); err != nil {
@@ -20,6 +20,3 @@ func main() {
log.Fatal(err)
}
}
// Output:
// 42

35
_test/interface46.go Normal file
View File

@@ -0,0 +1,35 @@
package main
import "fmt"
type I interface {
Foo() string
}
type Printer struct {
i I
}
func New(i I) *Printer {
return &Printer{
i: i,
}
}
func (p *Printer) Print() {
fmt.Println(p.i.Foo())
}
type T struct{}
func (t *T) Foo() string {
return "test"
}
func main() {
g := New(&T{})
g.Print()
}
// Output:
// test

View File

@@ -1,7 +1,7 @@
package main
import (
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
func main() {

View File

@@ -1,13 +1,13 @@
package main
import (
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
func main() {
i := interp.New(interp.Opt{})
i.Use(interp.ExportValue, interp.ExportType)
i.Eval(`import "github.com/containous/yaegi/interp"`)
i.Eval(`import "github.com/traefik/yaegi/interp"`)
i.Eval(`i := interp.New(interp.Opt{})`)
i.Eval(`i.Eval("println(42)")`)
}

16
_test/ipp_as_key.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"guthib.com/toto" // pkg name is actually titi
"guthib.com/tata" // pkg name is actually tutu
)
func main() {
println("Hello", titi.Quux())
println("Hello", tutu.Quux())
}
// GOPATH:testdata/redeclaration-global7
// Output:
// Hello bar
// Hello baz

25
_test/issue-770.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import "reflect"
type I interface {
Foo() int
}
type T struct {
Name string
}
func (t T) Foo() int { return 0 }
func f(v reflect.Value) int {
i := v.Interface().(I)
return i.Foo()
}
func main() {
println("hello")
}
// Output:
// hello

14
_test/issue-782.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
from := uint32(2)
to := uint32(4)
b := a[from:to]
fmt.Print(b)
}
// Output:
// [3 4]

39
_test/issue-784.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import "fmt"
// Filter is a filter interface
type Filter interface {
Bounds(srcBounds string) (dstBounds string)
}
// GIFT type
type GIFT struct {
Filters []Filter
}
// New creates a new filter list and initializes it with the given slice of filters.
func New(filters ...Filter) *GIFT {
return &GIFT{
Filters: filters,
}
}
// Bounds calculates the appropriate bounds for the result image after applying all the added filters.
func (g *GIFT) Bounds(srcBounds string) (dstBounds string) {
dstBounds = srcBounds
for _, f := range g.Filters {
dstBounds = f.Bounds(dstBounds)
}
return dstBounds
}
func main() {
var filters []Filter
bounds := "foo"
g := New(filters...)
fmt.Println(g.Bounds(bounds))
}
// Output:
// foo

23
_test/issue-880.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"bufio"
"bytes"
)
func main() {
var buf1 = make([]byte, 1024)
var buf2 []byte
buf1 = []byte("Hallo\nTest\nLine3")
s := bufio.NewScanner(bytes.NewReader(buf1))
for s.Scan() {
buf2 = append(buf2, append(s.Bytes(), []byte("\n")...)...)
}
print(string(buf2))
}
// Output:
// Hallo
// Test
// Line3

14
_test/m1/main.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"testing"
)
func main() {
fmt.Println("vim-go")
}
func TestWeird(t *testing.T) {
fmt.Println("in TestWeird")
}

17
_test/m1/main_test.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"math/rand"
"testing"
)
func TestMain(t *testing.T) {
fmt.Println("in test")
}
func BenchmarkMain(b *testing.B) {
for i := 0; i < b.N; i++ {
rand.Int()
}
}

7
_test/m2/m2_test.go Normal file
View File

@@ -0,0 +1,7 @@
package m2
import "testing"
func TestM2(t *testing.T) {
t.Errorf("got %s, want %s", "AAA", "BBB")
}

32
_test/math3.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"crypto/md5"
"fmt"
)
func md5Crypt(password, salt, magic []byte) []byte {
d := md5.New()
d.Write(password)
d.Write(magic)
d.Write(salt)
d2 := md5.New()
d2.Write(password)
d2.Write(salt)
for i, mixin := 0, d2.Sum(nil); i < len(password); i++ {
d.Write([]byte{mixin[i%16]})
}
return d.Sum(nil)
}
func main() {
b := md5Crypt([]byte("1"), []byte("2"), []byte("3"))
fmt.Println(b)
}
// Output:
// [187 141 73 89 101 229 33 106 226 63 117 234 117 149 230 21]

14
_test/method35.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "strconv"
func main() {
var err error
_, err = strconv.Atoi("erwer")
if _, ok := err.(*strconv.NumError); ok {
println("here")
}
}
// Output:
// here

28
_test/nil3.go Normal file
View File

@@ -0,0 +1,28 @@
package main
type I interface {
Hello()
}
type T struct {
h I
}
func (t *T) Hello() { println("Hello") }
func main() {
t := &T{}
println(t.h != nil)
println(t.h == nil)
t.h = t
println(t.h != nil)
println(t.h == nil)
t.h.Hello()
}
// Output:
// false
// true
// true
// false
// Hello

View File

@@ -7,4 +7,4 @@ func main() {
}
// Error:
// 5:2: illegal operand types for '+=' operator
// 5:2: invalid operation: mismatched types int and float64

16
_test/pkgname0.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"guthib.com/bar" // pkg name is actually quux
baz "guthib.com/baz" // pkg name is also quux, force it to baz.
)
func main() {
println("Hello", quux.Quux())
println("Hello", baz.Quux())
}
// GOPATH:testdata/redeclaration-global7
// Output:
// Hello bar
// Hello baz

13
_test/pkgname1.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"guthib.com/bar" // pkg name is actually quux
)
func main() {
println("Hello", bar.Quux()) // bar should not be a known symbol.
}
// GOPATH:testdata/redeclaration-global7
// Error:
// ../_test/pkgname1.go:8:19: undefined: bar

13
_test/pkgname2.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"guthib.com/toto" // pkg name is actually titi
)
func main() {
println("Hello", titi.Quux())
}
// GOPATH:testdata/redeclaration-global7
// Output:
// Hello bar

View File

@@ -12,4 +12,5 @@ func main() {
}
// Error:
// ../_test/redeclaration-global5.go:5:1: time redeclared in this block
// ../_test/redeclaration-global5.go:5:6: time redeclared in this block
// previous declaration at ../_test/redeclaration-global5.go:3:5

View File

@@ -0,0 +1,14 @@
package main
import (
"guthib.com/bar" // pkg name is actually quux
"guthib.com/baz" // pkg name is also quux
)
func main() {
println("Hello", quux.Quux())
}
// GOPATH:testdata/redeclaration-global7
// Error:
// ../_test/redeclaration-global7.go:5:2: quux/redeclaration-global7.go redeclared as imported package name

12
_test/rune1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
t := make([]byte, 2)
t[0] = '$'
fmt.Println(t)
}
// Output:
// [36 0]

View File

@@ -1,18 +1,20 @@
package main
import "time"
import "fmt"
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1e9)
time.Sleep(1e7)
c1 <- "one"
}()
go func() {
time.Sleep(2e9)
time.Sleep(2e7)
c2 <- "two"
}()

44
_test/select14.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"fmt"
"time"
)
const (
period = 100 * time.Millisecond
precision = 5 * time.Millisecond
)
func main() {
counter := 0
p := time.Now()
ticker := time.NewTicker(period)
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
select {
case t := <-ticker.C:
counter = counter + 1
ch <- counter
if d := t.Sub(p) - period; d < -precision || d > precision {
fmt.Println("wrong delay", d)
}
p = t
}
}
ch <- 0
}()
for c := range ch {
if c == 0 {
break
}
println(c)
}
}
// Output:
// 1
// 2
// 3

23
_test/select15.go Normal file
View File

@@ -0,0 +1,23 @@
package main
type T struct {
c1 chan string
c2 chan string
}
func main() {
t := &T{}
t.c2 = make(chan string)
go func(c chan string) { c <- "done" }(t.c2)
select {
case msg := <-t.c1:
println("received from c1:", msg)
case <-t.c2:
}
println("Bye")
}
// Output:
// Bye

11
_test/slice.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a := [2][2]int{{0, 1}, {2, 3}}
fmt.Println(a[0][0:])
}
// Output:
// [0 1]

22
_test/struct55.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import (
"log"
"os"
)
type Logger struct {
m []*log.Logger
}
func (l *Logger) Infof(format string, args ...interface{}) {
l.m[0].Printf(format, args...)
}
func main() {
l := &Logger{m: []*log.Logger{log.New(os.Stdout, "", log.Lmsgprefix)}}
l.Infof("test %s", "test")
}
// Output:
// test test

23
_test/struct56.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"encoding/json"
"fmt"
)
type A struct {
IA InnerA
}
type InnerA struct {
Timestamp int64
}
func main() {
a := &A{}
b, _ := json.Marshal(a)
fmt.Println(string(b))
}
// Output:
// {"IA":{"Timestamp":0}}

23
_test/struct57.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"encoding/json"
"fmt"
)
type A struct {
InnerA
}
type InnerA struct {
Timestamp int64
}
func main() {
a := &A{}
b, _ := json.Marshal(a)
fmt.Println(string(b))
}
// Output:
// {"Timestamp":0}

24
_test/struct58.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"fmt"
"reflect"
)
type A struct {
Test string `tag:"test"`
}
func main() {
a := A{}
t := reflect.TypeOf(a)
f, ok := t.FieldByName("Test")
if !ok {
return
}
fmt.Println(f.Tag.Get("tag"))
}
// Output:
// test

View File

@@ -3,7 +3,7 @@
package main
import _ "github.com/containous/yaegi/_test/ct"
import _ "github.com/traefik/yaegi/_test/ct"
func main() {
println("bye")

View File

@@ -0,0 +1,5 @@
package quux
func Quux() string {
return "bar"
}

View File

@@ -0,0 +1,5 @@
package quux
func Quux() string {
return "baz"
}

View File

@@ -0,0 +1,7 @@
package tutu
import "guthib.com/baz"
func Quux() string {
return quux.Quux()
}

View File

@@ -0,0 +1,7 @@
package titi
import "guthib.com/bar"
func Quux() string {
return quux.Quux()
}

20
_test/time14.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"time"
)
var t time.Time
func f() time.Time {
time := t
return time
}
func main() {
fmt.Println(f())
}
// Output:
// 0001-01-01 00:00:00 +0000 UTC

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/vars"
import "github.com/traefik/yaegi/_test/vars"
func main() {
println(vars.A)

16
_test/variadic10.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"log"
"os"
)
func main() {
logger := log.New(os.Stdout, "test ", log.Lmsgprefix)
logger.Printf("args: %v %v", 1, "truc")
logger.Printf("args: %v %v %v", 1, "truc", 2)
}
// Output:
// test args: 1 truc
// test args: 1 truc 2

View File

@@ -1,402 +0,0 @@
//go:generate go build
/*
Goexports generates wrappers of package exported symbols.
Output files are written in the current directory, and prefixed with the go version.
Usage:
goexports package...
Example:
goexports github.com/containous/yaegi/interp
The same goexport program is used for all target operating systems and architectures.
The GOOS and GOARCH environment variables set the desired target.
*/
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"go/constant"
"go/format"
"go/importer"
"go/token"
"go/types"
"io/ioutil"
"log"
"math/big"
"os"
"path"
"runtime"
"strconv"
"strings"
"text/template"
)
const model = `// Code generated by 'goexports {{.PkgName}}'. DO NOT EDIT.
{{.License}}
{{if .BuildTags}}// +build {{.BuildTags}}{{end}}
package {{.Dest}}
import (
{{- range $key, $value := .Imports }}
{{- if $value}}
"{{$key}}"
{{- end}}
{{- end}}
"{{.PkgName}}"
"reflect"
)
func init() {
Symbols["{{.PkgName}}"] = map[string]reflect.Value{
{{- if .Val}}
// function, constant and variable definitions
{{range $key, $value := .Val -}}
{{- if $value.Addr -}}
"{{$key}}": reflect.ValueOf(&{{$value.Name}}).Elem(),
{{else -}}
"{{$key}}": reflect.ValueOf({{$value.Name}}),
{{end -}}
{{end}}
{{- end}}
{{- if .Typ}}
// type definitions
{{range $key, $value := .Typ -}}
"{{$key}}": reflect.ValueOf((*{{$value}})(nil)),
{{end}}
{{- end}}
{{- if .Wrap}}
// interface wrapper definitions
{{range $key, $value := .Wrap -}}
"_{{$key}}": reflect.ValueOf((*{{$value.Name}})(nil)),
{{end}}
{{- end}}
}
}
{{range $key, $value := .Wrap -}}
// {{$value.Name}} is an interface wrapper for {{$key}} type
type {{$value.Name}} struct {
{{range $m := $value.Method -}}
W{{$m.Name}} func{{$m.Param}} {{$m.Result}}
{{end}}
}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
{{end}}
{{end}}
`
// Val store the value name and addressable status of symbols.
type Val struct {
Name string // "package.name"
Addr bool // true if symbol is a Var
}
// Method store information for generating interface wrapper method.
type Method struct {
Name, Param, Result, Arg, Ret string
}
// Wrap store information for generating interface wrapper.
type Wrap struct {
Name string
Method []Method
}
// restricted map defines symbols for which a special implementation is provided.
var restricted = map[string]bool{
"osExit": true,
"osFindProcess": true,
"logFatal": true,
"logFatalf": true,
"logFatalln": true,
"logLogger": true,
"logNew": true,
}
func genContent(dest, pkgName, license string, skip map[string]bool) ([]byte, error) {
p, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgName)
if err != nil {
return nil, err
}
prefix := "_" + pkgName + "_"
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_").Replace(prefix)
typ := map[string]string{}
val := map[string]Val{}
wrap := map[string]Wrap{}
imports := map[string]bool{}
sc := p.Scope()
for _, pkg := range p.Imports() {
imports[pkg.Path()] = false
}
qualify := func(pkg *types.Package) string {
if pkg.Path() != pkgName {
imports[pkg.Path()] = true
}
return pkg.Name()
}
for _, name := range sc.Names() {
o := sc.Lookup(name)
if !o.Exported() {
continue
}
pname := path.Base(pkgName) + "." + name
if skip[pname] {
continue
}
if rname := path.Base(pkgName) + name; restricted[rname] {
// Restricted symbol, locally provided by stdlib wrapper.
pname = rname
}
switch o := o.(type) {
case *types.Const:
if b, ok := o.Type().(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
// convert untyped constant to right type to avoid overflow
val[name] = Val{fixConst(pname, o.Val(), imports), false}
} else {
val[name] = Val{pname, false}
}
case *types.Func:
val[name] = Val{pname, false}
case *types.Var:
val[name] = Val{pname, true}
case *types.TypeName:
typ[name] = pname
if t, ok := o.Type().Underlying().(*types.Interface); ok {
var methods []Method
for i := 0; i < t.NumMethods(); i++ {
f := t.Method(i)
if !f.Exported() {
continue
}
sign := f.Type().(*types.Signature)
args := make([]string, sign.Params().Len())
params := make([]string, len(args))
for j := range args {
v := sign.Params().At(j)
if args[j] = v.Name(); args[j] == "" {
args[j] = fmt.Sprintf("a%d", j)
}
params[j] = args[j] + " " + types.TypeString(v.Type(), qualify)
}
arg := "(" + strings.Join(args, ", ") + ")"
param := "(" + strings.Join(params, ", ") + ")"
results := make([]string, sign.Results().Len())
for j := range results {
v := sign.Results().At(j)
results[j] = v.Name() + " " + types.TypeString(v.Type(), qualify)
}
result := "(" + strings.Join(results, ", ") + ")"
ret := ""
if sign.Results().Len() > 0 {
ret = "return"
}
methods = append(methods, Method{f.Name(), param, result, arg, ret})
}
wrap[name] = Wrap{prefix + name, methods}
}
}
}
var buildTags string
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
minorRaw := getMinor(parts[1])
currentGoVersion := parts[0] + "." + minorRaw
minor, errParse := strconv.Atoi(minorRaw)
if errParse != nil {
return nil, fmt.Errorf("failed to parse version: %v", errParse)
}
nextGoVersion := parts[0] + "." + strconv.Itoa(minor+1)
buildTags = currentGoVersion + ",!" + nextGoVersion
}
base := template.New("goexports")
parse, err := base.Parse(model)
if err != nil {
return nil, fmt.Errorf("template parsing error: %v", err)
}
if pkgName == "log/syslog" {
buildTags += ",!windows,!nacl,!plan9"
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": dest,
"Imports": imports,
"PkgName": pkgName,
"Val": val,
"Typ": typ,
"Wrap": wrap,
"BuildTags": buildTags,
"License": license,
}
err = parse.Execute(b, data)
if err != nil {
return nil, fmt.Errorf("template error: %v", err)
}
// gofmt
source, err := format.Source(b.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to format source: %v: %s", err, b.Bytes())
}
return source, nil
}
// fixConst checks untyped constant value, converting it if necessary to avoid overflow.
func fixConst(name string, val constant.Value, imports map[string]bool) string {
var (
tok string
str string
)
switch val.Kind() {
case constant.Int:
tok = "INT"
str = val.ExactString()
case constant.Float:
v := constant.Val(val) // v is *big.Rat or *big.Float
f, ok := v.(*big.Float)
if !ok {
f = new(big.Float).SetRat(v.(*big.Rat))
}
tok = "FLOAT"
str = f.Text('g', int(f.Prec()))
case constant.Complex:
// TODO: not sure how to parse this case
fallthrough
default:
return name
}
imports["go/constant"] = true
imports["go/token"] = true
return fmt.Sprintf("constant.MakeFromLiteral(\"%s\", token.%s, 0)", str, tok)
}
// genLicense generates the correct LICENSE header text from the provided
// path to a LICENSE file.
func genLicense(fname string) (string, error) {
if fname == "" {
return "", nil
}
f, err := os.Open(fname)
if err != nil {
return "", fmt.Errorf("could not open LICENSE file: %v", err)
}
defer func() { _ = f.Close() }()
license := new(strings.Builder)
sc := bufio.NewScanner(f)
for sc.Scan() {
txt := sc.Text()
if txt != "" {
txt = " " + txt
}
license.WriteString("//" + txt + "\n")
}
if sc.Err() != nil {
return "", fmt.Errorf("could not scan LICENSE file: %v", err)
}
return license.String(), nil
}
func main() {
licenseFlag := flag.String("license", "", "path to a LICENSE file")
flag.Parse()
if flag.NArg() == 0 {
flag.Usage()
log.Fatalf("missing package path")
}
license, err := genLicense(*licenseFlag)
if err != nil {
log.Fatal(err)
}
dir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
dest := path.Base(dir)
for _, pkg := range flag.Args() {
var oFile string
skip := map[string]bool{}
if pkg == "syscall" {
goos, arch := os.Getenv("GOOS"), os.Getenv("GOARCH")
oFile = strings.Replace(pkg, "/", "_", -1) + "_" + goos + "_" + arch + ".go"
if goos == "solaris" {
skip["syscall.RawSyscall6"] = true
skip["syscall.Syscall6"] = true
}
} else {
oFile = strings.Replace(pkg, "/", "_", -1) + ".go"
}
content, err := genContent(dest, pkg, license, skip)
if err != nil {
log.Println(err)
continue
}
prefix := runtime.Version()
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
prefix = parts[0] + "_" + getMinor(parts[1])
}
err = ioutil.WriteFile(prefix+"_"+oFile, content, 0666)
if err != nil {
log.Fatal(err)
}
}
}
func getMinor(part string) string {
minor := part
index := strings.Index(minor, "beta")
if index < 0 {
index = strings.Index(minor, "rc")
}
if index > 0 {
minor = minor[:index]
}
return minor
}

123
cmd/yaegi/extract.go Normal file
View File

@@ -0,0 +1,123 @@
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"os"
"path"
"strings"
"github.com/traefik/yaegi/extract"
)
func extractCmd(arg []string) error {
var licensePath string
var name string
var exclude string
var include string
eflag := flag.NewFlagSet("run", flag.ContinueOnError)
eflag.StringVar(&licensePath, "license", "", "path to a LICENSE file")
eflag.StringVar(&name, "name", "", "the namespace for the extracted symbols")
eflag.StringVar(&exclude, "exclude", "", "comma separated list of regexp matching symbols to exclude")
eflag.StringVar(&include, "include", "", "comma separated list of regexp matching symbols to include")
eflag.Usage = func() {
fmt.Println("Usage: yaegi extract [options] packages...")
fmt.Println("Options:")
eflag.PrintDefaults()
}
if err := eflag.Parse(arg); err != nil {
return err
}
args := eflag.Args()
if len(args) == 0 {
return fmt.Errorf("missing package")
}
license, err := genLicense(licensePath)
if err != nil {
return err
}
wd, err := os.Getwd()
if err != nil {
return err
}
if name == "" {
name = path.Base(wd)
}
ext := extract.Extractor{
Dest: name,
License: license,
}
if exclude != "" {
ext.Exclude = strings.Split(exclude, ",")
}
if include != "" {
ext.Include = strings.Split(include, ",")
}
r := strings.NewReplacer("/", "-", ".", "_")
for _, pkgIdent := range args {
var buf bytes.Buffer
importPath, err := ext.Extract(pkgIdent, name, &buf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
oFile := r.Replace(importPath) + ".go"
f, err := os.Create(oFile)
if err != nil {
return err
}
if _, err := io.Copy(f, &buf); err != nil {
_ = f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
}
return nil
}
// genLicense generates the correct LICENSE header text from the provided
// path to a LICENSE file.
func genLicense(fname string) (string, error) {
if fname == "" {
return "", nil
}
f, err := os.Open(fname)
if err != nil {
return "", fmt.Errorf("could not open LICENSE file: %v", err)
}
defer func() { _ = f.Close() }()
license := new(strings.Builder)
sc := bufio.NewScanner(f)
for sc.Scan() {
txt := sc.Text()
if txt != "" {
txt = " " + txt
}
license.WriteString("//" + txt + "\n")
}
if sc.Err() != nil {
return "", fmt.Errorf("could not scan LICENSE file: %v", err)
}
return license.String(), nil
}

47
cmd/yaegi/help.go Normal file
View File

@@ -0,0 +1,47 @@
package main
import "fmt"
const usage = `Yaegi is a Go interpreter.
Usage:
yaegi [command] [arguments]
The commands are:
extract generate a wrapper file from a source package
help print usage information
run execute a Go program from source
test execute test functions in a Go package
version print version
Use "yaegi help <command>" for more information about a command.
If no command is given or if the first argument is not a command, then
the run command is assumed.
`
func help(arg []string) error {
var cmd string
if len(arg) > 0 {
cmd = arg[0]
}
switch cmd {
case Extract:
return extractCmd([]string{"-h"})
case Help, "", "-h", "--help":
fmt.Print(usage)
return nil
case Run:
return run([]string{"-h"})
case Test:
return test([]string{"-h"})
case Version:
fmt.Println("Usage: yaegi version")
return nil
default:
return fmt.Errorf("help: invalid yaegi command: %v", cmd)
}
}

135
cmd/yaegi/run.go Normal file
View File

@@ -0,0 +1,135 @@
package main
import (
"flag"
"fmt"
"go/build"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
"github.com/traefik/yaegi/stdlib/syscall"
"github.com/traefik/yaegi/stdlib/unrestricted"
"github.com/traefik/yaegi/stdlib/unsafe"
)
func run(arg []string) error {
var interactive bool
var tags string
var cmd string
var err error
// The following flags are initialized from environment.
useSyscall, _ := strconv.ParseBool(os.Getenv("YAEGI_SYSCALL"))
useUnrestricted, _ := strconv.ParseBool(os.Getenv("YAEGI_UNRESTRICTED"))
useUnsafe, _ := strconv.ParseBool(os.Getenv("YAEGI_UNSAFE"))
rflag := flag.NewFlagSet("run", flag.ContinueOnError)
rflag.BoolVar(&interactive, "i", false, "start an interactive REPL")
rflag.BoolVar(&useSyscall, "syscall", useSyscall, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "include unrestricted symbols")
rflag.StringVar(&tags, "tags", "", "set a list of build tags")
rflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "include unsafe symbols")
rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
rflag.Usage = func() {
fmt.Println("Usage: yaegi run [options] [path] [args]")
fmt.Println("Options:")
rflag.PrintDefaults()
}
if err = rflag.Parse(arg); err != nil {
return err
}
args := rflag.Args()
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
// Using a environment var allows a nested interpreter to import the syscall package.
if err := os.Setenv("YAEGI_SYSCALL", "1"); err != nil {
return err
}
}
if useUnsafe {
i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
}
if useUnrestricted {
// Use of unrestricted symbols should always follow stdlib and syscall symbols, to update them.
i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
}
if cmd != "" {
_, err = i.Eval(cmd)
}
if len(args) == 0 {
if interactive || cmd == "" {
showError(err)
_, err = i.REPL()
}
return err
}
// Skip first os arg to set command line as expected by interpreted main
path := args[0]
os.Args = arg
flag.CommandLine = flag.NewFlagSet(path, flag.ExitOnError)
if isFile(path) {
err = runFile(i, path)
} else {
_, err = i.EvalPath(path)
}
if err != nil {
return err
}
if interactive {
_, err = i.REPL()
}
return err
}
func isFile(path string) bool {
fi, err := os.Stat(path)
return err == nil && fi.Mode().IsRegular()
}
func runFile(i *interp.Interpreter, path string) error {
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
if s := string(b); strings.HasPrefix(s, "#!") {
// Allow executable go scripts, Have the same behavior as in interactive mode.
s = strings.Replace(s, "#!", "//", 1)
_, err = i.Eval(s)
return err
}
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
_, err = i.EvalPath(path)
return err
}
func showError(err error) {
if err == nil {
return
}
fmt.Fprintln(os.Stderr, err)
if p, ok := err.(interp.Panic); ok {
fmt.Fprintln(os.Stderr, string(p.Stack))
}
}

149
cmd/yaegi/test.go Normal file
View File

@@ -0,0 +1,149 @@
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"os"
"regexp"
"strconv"
"strings"
"testing"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
"github.com/traefik/yaegi/stdlib/syscall"
"github.com/traefik/yaegi/stdlib/unrestricted"
"github.com/traefik/yaegi/stdlib/unsafe"
)
func test(arg []string) (err error) {
var (
bench string
benchmem bool
benchtime string
count string
cpu string
failfast bool
run string
short bool
tags string
timeout string
verbose bool
)
// The following flags are initialized from environment.
useSyscall, _ := strconv.ParseBool(os.Getenv("YAEGI_SYSCALL"))
useUnrestricted, _ := strconv.ParseBool(os.Getenv("YAEGI_UNRESTRICTED"))
useUnsafe, _ := strconv.ParseBool(os.Getenv("YAEGI_UNSAFE"))
tflag := flag.NewFlagSet("test", flag.ContinueOnError)
tflag.StringVar(&bench, "bench", "", "Run only those benchmarks matching a regular expression.")
tflag.BoolVar(&benchmem, "benchmem", false, "Print memory allocation statistics for benchmarks.")
tflag.StringVar(&benchtime, "benchtime", "", "Run enough iterations of each benchmark to take t.")
tflag.StringVar(&count, "count", "", "Run each test and benchmark n times (default 1).")
tflag.StringVar(&cpu, "cpu", "", "Specify a list of GOMAXPROCS values for which the tests or benchmarks should be executed.")
tflag.BoolVar(&failfast, "failfast", false, "Do not start new tests after the first test failure.")
tflag.StringVar(&run, "run", "", "Run only those tests matching a regular expression.")
tflag.BoolVar(&short, "short", false, "Tell long-running tests to shorten their run time.")
tflag.StringVar(&tags, "tags", "", "Set a list of build tags.")
tflag.StringVar(&timeout, "timeout", "", "If a test binary runs longer than duration d, panic.")
tflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "Include unrestricted symbols.")
tflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "Include usafe symbols.")
tflag.BoolVar(&useSyscall, "syscall", useSyscall, "Include syscall symbols.")
tflag.BoolVar(&verbose, "v", false, "Verbose output: log all tests as they are run.")
tflag.Usage = func() {
fmt.Println("Usage: yaegi test [options] [path]")
fmt.Println("Options:")
tflag.PrintDefaults()
}
if err = tflag.Parse(arg); err != nil {
return err
}
args := tflag.Args()
path := "."
if len(args) > 0 {
path = args[0]
}
// Overwrite os.Args with correct flags to setup testing.Init.
tf := []string{""}
if bench != "" {
tf = append(tf, "-test.bench", bench)
}
if benchmem {
tf = append(tf, "-test.benchmem")
}
if benchtime != "" {
tf = append(tf, "-test.benchtime", benchtime)
}
if count != "" {
tf = append(tf, "-test.count", count)
}
if cpu != "" {
tf = append(tf, "-test.cpu", cpu)
}
if failfast {
tf = append(tf, "-test.failfast")
}
if run != "" {
tf = append(tf, "-test.run", run)
}
if short {
tf = append(tf, "-test.short")
}
if timeout != "" {
tf = append(tf, "-test.timeout", timeout)
}
if verbose {
tf = append(tf, "-test.v")
}
testing.Init()
os.Args = tf
flag.Parse()
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
// Using a environment var allows a nested interpreter to import the syscall package.
if err := os.Setenv("YAEGI_SYSCALL", "1"); err != nil {
return err
}
}
if useUnrestricted {
i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
}
if useUnsafe {
i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
}
if err = i.EvalTest(path); err != nil {
return err
}
benchmarks := []testing.InternalBenchmark{}
tests := []testing.InternalTest{}
syms, ok := i.Symbols(path)[path]
if !ok {
return errors.New("No tests found")
}
for name, sym := range syms {
switch fun := sym.Interface().(type) {
case func(*testing.B):
benchmarks = append(benchmarks, testing.InternalBenchmark{name, fun})
case func(*testing.T):
tests = append(tests, testing.InternalTest{name, fun})
}
}
testing.Main(regexp.MatchString, tests, benchmarks, nil)
return nil
}

View File

@@ -71,7 +71,16 @@ Options:
-unsafe
include unsafe symbols.
Debugging support (may be removed at any time):
Environment variables:
YAEGI_SYSCALL=1
Include syscall symbols (same as -syscall flag).
YAEGI_UNRESTRICTED=1
Include unrestricted symbols (same as -unrestricted flag).
YAEGI_UNSAFE=1
Include unsafe symbols (same as -unsafe flag).
YAEGI_PROMPT=1
Force enable the printing of the REPL prompt and the result of last instruction,
even if stdin is not a terminal.
YAEGI_AST_DOT=1
Generate and display graphviz dot of AST with dotty(1)
YAEGI_CFG_DOT=1
@@ -85,94 +94,62 @@ Debugging support (may be removed at any time):
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"io/ioutil"
"log"
"os"
"strings"
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/syscall"
"github.com/containous/yaegi/stdlib/unrestricted"
"github.com/containous/yaegi/stdlib/unsafe"
"github.com/traefik/yaegi/interp"
)
const (
Extract = "extract"
Help = "help"
Run = "run"
Test = "test"
Version = "version"
)
var version = "devel" // This may be overwritten at build time.
func main() {
var interactive bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
flag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
flag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
flag.StringVar(&tags, "tags", "", "set a list of build tags")
flag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
flag.Usage = func() {
fmt.Println("Usage:", os.Args[0], "[options] [script] [args]")
fmt.Println("Options:")
flag.PrintDefaults()
}
flag.Parse()
args := flag.Args()
log.SetFlags(log.Lshortfile)
var err error
var exitCode int
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
}
if useUnsafe {
i.Use(unsafe.Symbols)
}
if useUnrestricted {
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
i.Use(unrestricted.Symbols)
log.SetFlags(log.Lshortfile) // Ease debugging.
if len(os.Args) > 1 {
cmd = os.Args[1]
}
if cmd != `` {
i.REPL(strings.NewReader(cmd), os.Stderr)
switch cmd {
case Extract:
err = extractCmd(os.Args[2:])
case Help, "-h", "--help":
err = help(os.Args[2:])
case Run:
err = run(os.Args[2:])
case Test:
err = test(os.Args[2:])
case Version:
fmt.Println(version)
default:
// If no command is given, fallback to default "run" command.
// This allows scripts starting with "#!/usr/bin/env yaegi",
// as passing more than 1 argument to #! executable may be not supported
// on all platforms.
cmd = Run
err = run(os.Args[1:])
}
if len(args) == 0 {
if interactive || cmd == `` {
i.REPL(os.Stdin, os.Stdout)
}
return
}
// Skip first os arg to set command line as expected by interpreted main
os.Args = os.Args[1:]
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
b, err := ioutil.ReadFile(args[0])
if err != nil {
log.Fatal("Could not read file: ", args[0])
}
if s := string(b); strings.HasPrefix(s, "#!") {
// Allow executable go scripts, Have the same behavior as in interactive mode.
s = strings.Replace(s, "#!", "//", 1)
i.REPL(strings.NewReader(s), os.Stdout)
} else {
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
i.Name = args[0]
_, err := i.Eval(s)
if err != nil {
fmt.Println(err)
if err != nil && !errors.Is(err, flag.ErrHelp) {
fmt.Fprintln(os.Stderr, fmt.Errorf("%s: %w", cmd, err))
if p, ok := err.(interp.Panic); ok {
fmt.Println(string(p.Stack))
fmt.Fprintln(os.Stderr, string(p.Stack))
}
exitCode = 1
}
}
if interactive {
i.REPL(os.Stdin, os.Stdout)
}
os.Exit(exitCode)
}

Some files were not shown because too many files have changed in this diff Show More