29
vendor/go.starlark.net/LICENSE
generated
vendored
Normal file
29
vendor/go.starlark.net/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
Copyright (c) 2017 The Bazel Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1903
vendor/go.starlark.net/internal/compile/compile.go
generated
vendored
Normal file
1903
vendor/go.starlark.net/internal/compile/compile.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
389
vendor/go.starlark.net/internal/compile/serial.go
generated
vendored
Normal file
389
vendor/go.starlark.net/internal/compile/serial.go
generated
vendored
Normal file
@@ -0,0 +1,389 @@
|
||||
package compile
|
||||
|
||||
// This file defines functions to read and write a compile.Program to a file.
|
||||
//
|
||||
// It is the client's responsibility to avoid version skew between the
|
||||
// compiler used to produce a file and the interpreter that consumes it.
|
||||
// The version number is provided as a constant.
|
||||
// Incompatible protocol changes should also increment the version number.
|
||||
//
|
||||
// Encoding
|
||||
//
|
||||
// Program:
|
||||
// "sky!" [4]byte # magic number
|
||||
// str uint32le # offset of <strings> section
|
||||
// version varint # must match Version
|
||||
// filename string
|
||||
// numloads varint
|
||||
// loads []Ident
|
||||
// numnames varint
|
||||
// names []string
|
||||
// numconsts varint
|
||||
// consts []Constant
|
||||
// numglobals varint
|
||||
// globals []Ident
|
||||
// toplevel Funcode
|
||||
// numfuncs varint
|
||||
// funcs []Funcode
|
||||
// <strings> []byte # concatenation of all referenced strings
|
||||
// EOF
|
||||
//
|
||||
// Funcode:
|
||||
// id Ident
|
||||
// code []byte
|
||||
// pclinetablen varint
|
||||
// pclinetab []varint
|
||||
// numlocals varint
|
||||
// locals []Ident
|
||||
// numcells varint
|
||||
// cells []int
|
||||
// numfreevars varint
|
||||
// freevar []Ident
|
||||
// maxstack varint
|
||||
// numparams varint
|
||||
// numkwonlyparams varint
|
||||
// hasvarargs varint (0 or 1)
|
||||
// haskwargs varint (0 or 1)
|
||||
//
|
||||
// Ident:
|
||||
// filename string
|
||||
// line, col varint
|
||||
//
|
||||
// Constant: # type data
|
||||
// type varint # 0=string string
|
||||
// data ... # 1=int varint
|
||||
// # 2=float varint (bits as uint64)
|
||||
// # 3=bigint string (decimal ASCII text)
|
||||
//
|
||||
// The encoding starts with a four-byte magic number.
|
||||
// The next four bytes are a little-endian uint32
|
||||
// that provides the offset of the string section
|
||||
// at the end of the file, which contains the ordered
|
||||
// concatenation of all strings referenced by the
|
||||
// program. This design permits the decoder to read
|
||||
// the first and second parts of the file into different
|
||||
// memory allocations: the first (the encoded program)
|
||||
// is transient, but the second (the strings) persists
|
||||
// for the life of the Program.
|
||||
//
|
||||
// Within the encoded program, all strings are referred
|
||||
// to by their length. As the encoder and decoder process
|
||||
// the entire file sequentially, they are in lock step,
|
||||
// so the start offset of each string is implicit.
|
||||
//
|
||||
// Program.Code is represented as a []byte slice to permit
|
||||
// modification when breakpoints are set. All other strings
|
||||
// are represented as strings. They all (unsafely) share the
|
||||
// same backing byte slice.
|
||||
//
|
||||
// Aside from the str field, all integers are encoded as varints.
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
debugpkg "runtime/debug"
|
||||
"unsafe"
|
||||
|
||||
"go.starlark.net/syntax"
|
||||
)
|
||||
|
||||
const magic = "!sky"
|
||||
|
||||
// Encode encodes a compiled Starlark program.
|
||||
func (prog *Program) Encode() []byte {
|
||||
var e encoder
|
||||
e.p = append(e.p, magic...)
|
||||
e.p = append(e.p, "????"...) // string data offset; filled in later
|
||||
e.int(Version)
|
||||
e.string(prog.Toplevel.Pos.Filename())
|
||||
e.bindings(prog.Loads)
|
||||
e.int(len(prog.Names))
|
||||
for _, name := range prog.Names {
|
||||
e.string(name)
|
||||
}
|
||||
e.int(len(prog.Constants))
|
||||
for _, c := range prog.Constants {
|
||||
switch c := c.(type) {
|
||||
case string:
|
||||
e.int(0)
|
||||
e.string(c)
|
||||
case int64:
|
||||
e.int(1)
|
||||
e.int64(c)
|
||||
case float64:
|
||||
e.int(2)
|
||||
e.uint64(math.Float64bits(c))
|
||||
case *big.Int:
|
||||
e.int(3)
|
||||
e.string(c.Text(10))
|
||||
}
|
||||
}
|
||||
e.bindings(prog.Globals)
|
||||
e.function(prog.Toplevel)
|
||||
e.int(len(prog.Functions))
|
||||
for _, fn := range prog.Functions {
|
||||
e.function(fn)
|
||||
}
|
||||
|
||||
// Patch in the offset of the string data section.
|
||||
binary.LittleEndian.PutUint32(e.p[4:8], uint32(len(e.p)))
|
||||
|
||||
return append(e.p, e.s...)
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
p []byte // encoded program
|
||||
s []byte // strings
|
||||
tmp [binary.MaxVarintLen64]byte
|
||||
}
|
||||
|
||||
func (e *encoder) int(x int) {
|
||||
e.int64(int64(x))
|
||||
}
|
||||
|
||||
func (e *encoder) int64(x int64) {
|
||||
n := binary.PutVarint(e.tmp[:], x)
|
||||
e.p = append(e.p, e.tmp[:n]...)
|
||||
}
|
||||
|
||||
func (e *encoder) uint64(x uint64) {
|
||||
n := binary.PutUvarint(e.tmp[:], x)
|
||||
e.p = append(e.p, e.tmp[:n]...)
|
||||
}
|
||||
|
||||
func (e *encoder) string(s string) {
|
||||
e.int(len(s))
|
||||
e.s = append(e.s, s...)
|
||||
}
|
||||
|
||||
func (e *encoder) bytes(b []byte) {
|
||||
e.int(len(b))
|
||||
e.s = append(e.s, b...)
|
||||
}
|
||||
|
||||
func (e *encoder) binding(bind Binding) {
|
||||
e.string(bind.Name)
|
||||
e.int(int(bind.Pos.Line))
|
||||
e.int(int(bind.Pos.Col))
|
||||
}
|
||||
|
||||
func (e *encoder) bindings(binds []Binding) {
|
||||
e.int(len(binds))
|
||||
for _, bind := range binds {
|
||||
e.binding(bind)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) function(fn *Funcode) {
|
||||
e.binding(Binding{fn.Name, fn.Pos})
|
||||
e.string(fn.Doc)
|
||||
e.bytes(fn.Code)
|
||||
e.int(len(fn.pclinetab))
|
||||
for _, x := range fn.pclinetab {
|
||||
e.int64(int64(x))
|
||||
}
|
||||
e.bindings(fn.Locals)
|
||||
e.int(len(fn.Cells))
|
||||
for _, index := range fn.Cells {
|
||||
e.int(index)
|
||||
}
|
||||
e.bindings(fn.Freevars)
|
||||
e.int(fn.MaxStack)
|
||||
e.int(fn.NumParams)
|
||||
e.int(fn.NumKwonlyParams)
|
||||
e.int(b2i(fn.HasVarargs))
|
||||
e.int(b2i(fn.HasKwargs))
|
||||
}
|
||||
|
||||
func b2i(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeProgram decodes a compiled Starlark program from data.
|
||||
func DecodeProgram(data []byte) (_ *Program, err error) {
|
||||
if len(data) < len(magic) {
|
||||
return nil, fmt.Errorf("not a compiled module: no magic number")
|
||||
}
|
||||
if got := string(data[:4]); got != magic {
|
||||
return nil, fmt.Errorf("not a compiled module: got magic number %q, want %q",
|
||||
got, magic)
|
||||
}
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
debugpkg.PrintStack()
|
||||
err = fmt.Errorf("internal error while decoding program: %v", x)
|
||||
}
|
||||
}()
|
||||
|
||||
offset := binary.LittleEndian.Uint32(data[4:8])
|
||||
d := decoder{
|
||||
p: data[8:offset],
|
||||
s: append([]byte(nil), data[offset:]...), // allocate a copy, which will persist
|
||||
}
|
||||
|
||||
if v := d.int(); v != Version {
|
||||
return nil, fmt.Errorf("version mismatch: read %d, want %d", v, Version)
|
||||
}
|
||||
|
||||
filename := d.string()
|
||||
d.filename = &filename
|
||||
|
||||
loads := d.bindings()
|
||||
|
||||
names := make([]string, d.int())
|
||||
for i := range names {
|
||||
names[i] = d.string()
|
||||
}
|
||||
|
||||
// constants
|
||||
constants := make([]interface{}, d.int())
|
||||
for i := range constants {
|
||||
var c interface{}
|
||||
switch d.int() {
|
||||
case 0:
|
||||
c = d.string()
|
||||
case 1:
|
||||
c = d.int64()
|
||||
case 2:
|
||||
c = math.Float64frombits(d.uint64())
|
||||
case 3:
|
||||
c, _ = new(big.Int).SetString(d.string(), 10)
|
||||
}
|
||||
constants[i] = c
|
||||
}
|
||||
|
||||
globals := d.bindings()
|
||||
toplevel := d.function()
|
||||
funcs := make([]*Funcode, d.int())
|
||||
for i := range funcs {
|
||||
funcs[i] = d.function()
|
||||
}
|
||||
|
||||
prog := &Program{
|
||||
Loads: loads,
|
||||
Names: names,
|
||||
Constants: constants,
|
||||
Globals: globals,
|
||||
Functions: funcs,
|
||||
Toplevel: toplevel,
|
||||
}
|
||||
toplevel.Prog = prog
|
||||
for _, f := range funcs {
|
||||
f.Prog = prog
|
||||
}
|
||||
|
||||
if len(d.p)+len(d.s) > 0 {
|
||||
return nil, fmt.Errorf("internal error: unconsumed data during decoding")
|
||||
}
|
||||
|
||||
return prog, nil
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
p []byte // encoded program
|
||||
s []byte // strings
|
||||
filename *string // (indirect to avoid keeping decoder live)
|
||||
}
|
||||
|
||||
func (d *decoder) int() int {
|
||||
return int(d.int64())
|
||||
}
|
||||
|
||||
func (d *decoder) int64() int64 {
|
||||
x, len := binary.Varint(d.p[:])
|
||||
d.p = d.p[len:]
|
||||
return x
|
||||
}
|
||||
|
||||
func (d *decoder) uint64() uint64 {
|
||||
x, len := binary.Uvarint(d.p[:])
|
||||
d.p = d.p[len:]
|
||||
return x
|
||||
}
|
||||
|
||||
func (d *decoder) string() (s string) {
|
||||
if slice := d.bytes(); len(slice) > 0 {
|
||||
// Avoid a memory allocation for each string
|
||||
// by unsafely aliasing slice.
|
||||
type string struct {
|
||||
data *byte
|
||||
len int
|
||||
}
|
||||
ptr := (*string)(unsafe.Pointer(&s))
|
||||
ptr.data = &slice[0]
|
||||
ptr.len = len(slice)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (d *decoder) bytes() []byte {
|
||||
len := d.int()
|
||||
r := d.s[:len:len]
|
||||
d.s = d.s[len:]
|
||||
return r
|
||||
}
|
||||
|
||||
func (d *decoder) binding() Binding {
|
||||
name := d.string()
|
||||
line := int32(d.int())
|
||||
col := int32(d.int())
|
||||
return Binding{Name: name, Pos: syntax.MakePosition(d.filename, line, col)}
|
||||
}
|
||||
|
||||
func (d *decoder) bindings() []Binding {
|
||||
bindings := make([]Binding, d.int())
|
||||
for i := range bindings {
|
||||
bindings[i] = d.binding()
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (d *decoder) ints() []int {
|
||||
ints := make([]int, d.int())
|
||||
for i := range ints {
|
||||
ints[i] = d.int()
|
||||
}
|
||||
return ints
|
||||
}
|
||||
|
||||
func (d *decoder) bool() bool { return d.int() != 0 }
|
||||
|
||||
func (d *decoder) function() *Funcode {
|
||||
id := d.binding()
|
||||
doc := d.string()
|
||||
code := d.bytes()
|
||||
pclinetab := make([]uint16, d.int())
|
||||
for i := range pclinetab {
|
||||
pclinetab[i] = uint16(d.int())
|
||||
}
|
||||
locals := d.bindings()
|
||||
cells := d.ints()
|
||||
freevars := d.bindings()
|
||||
maxStack := d.int()
|
||||
numParams := d.int()
|
||||
numKwonlyParams := d.int()
|
||||
hasVarargs := d.int() != 0
|
||||
hasKwargs := d.int() != 0
|
||||
return &Funcode{
|
||||
// Prog is filled in later.
|
||||
Pos: id.Pos,
|
||||
Name: id.Name,
|
||||
Doc: doc,
|
||||
Code: code,
|
||||
pclinetab: pclinetab,
|
||||
Locals: locals,
|
||||
Cells: cells,
|
||||
Freevars: freevars,
|
||||
MaxStack: maxStack,
|
||||
NumParams: numParams,
|
||||
NumKwonlyParams: numKwonlyParams,
|
||||
HasVarargs: hasVarargs,
|
||||
HasKwargs: hasKwargs,
|
||||
}
|
||||
}
|
||||
115
vendor/go.starlark.net/internal/spell/spell.go
generated
vendored
Normal file
115
vendor/go.starlark.net/internal/spell/spell.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
// Package spell file defines a simple spelling checker for use in attribute errors
|
||||
// such as "no such field .foo; did you mean .food?".
|
||||
package spell
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Nearest returns the element of candidates
|
||||
// nearest to x using the Levenshtein metric,
|
||||
// or "" if none were promising.
|
||||
func Nearest(x string, candidates []string) string {
|
||||
// Ignore underscores and case when matching.
|
||||
fold := func(s string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if r == '_' {
|
||||
return -1
|
||||
}
|
||||
return unicode.ToLower(r)
|
||||
}, s)
|
||||
}
|
||||
|
||||
x = fold(x)
|
||||
|
||||
var best string
|
||||
bestD := (len(x) + 1) / 2 // allow up to 50% typos
|
||||
for _, c := range candidates {
|
||||
d := levenshtein(x, fold(c), bestD)
|
||||
if d < bestD {
|
||||
bestD = d
|
||||
best = c
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
// levenshtein returns the non-negative Levenshtein edit distance
|
||||
// between the byte strings x and y.
|
||||
//
|
||||
// If the computed distance exceeds max,
|
||||
// the function may return early with an approximate value > max.
|
||||
func levenshtein(x, y string, max int) int {
|
||||
// This implementation is derived from one by Laurent Le Brun in
|
||||
// Bazel that uses the single-row space efficiency trick
|
||||
// described at bitbucket.org/clearer/iosifovich.
|
||||
|
||||
// Let x be the shorter string.
|
||||
if len(x) > len(y) {
|
||||
x, y = y, x
|
||||
}
|
||||
|
||||
// Remove common prefix.
|
||||
for i := 0; i < len(x); i++ {
|
||||
if x[i] != y[i] {
|
||||
x = x[i:]
|
||||
y = y[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
if x == "" {
|
||||
return len(y)
|
||||
}
|
||||
|
||||
if d := abs(len(x) - len(y)); d > max {
|
||||
return d // excessive length divergence
|
||||
}
|
||||
|
||||
row := make([]int, len(y)+1)
|
||||
for i := range row {
|
||||
row[i] = i
|
||||
}
|
||||
|
||||
for i := 1; i <= len(x); i++ {
|
||||
row[0] = i
|
||||
best := i
|
||||
prev := i - 1
|
||||
for j := 1; j <= len(y); j++ {
|
||||
a := prev + b2i(x[i-1] != y[j-1]) // substitution
|
||||
b := 1 + row[j-1] // deletion
|
||||
c := 1 + row[j] // insertion
|
||||
k := min(a, min(b, c))
|
||||
prev, row[j] = row[j], k
|
||||
best = min(best, k)
|
||||
}
|
||||
if best > max {
|
||||
return best
|
||||
}
|
||||
}
|
||||
return row[len(y)]
|
||||
}
|
||||
|
||||
func b2i(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func min(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
} else {
|
||||
return y
|
||||
}
|
||||
}
|
||||
|
||||
func abs(x int) int {
|
||||
if x >= 0 {
|
||||
return x
|
||||
} else {
|
||||
return -x
|
||||
}
|
||||
}
|
||||
74
vendor/go.starlark.net/resolve/binding.go
generated
vendored
Normal file
74
vendor/go.starlark.net/resolve/binding.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2019 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package resolve
|
||||
|
||||
import "go.starlark.net/syntax"
|
||||
|
||||
// This file defines resolver data types saved in the syntax tree.
|
||||
// We cannot guarantee API stability for these types
|
||||
// as they are closely tied to the implementation.
|
||||
|
||||
// A Binding contains resolver information about an identifer.
|
||||
// The resolver populates the Binding field of each syntax.Identifier.
|
||||
// The Binding ties together all identifiers that denote the same variable.
|
||||
type Binding struct {
|
||||
Scope Scope
|
||||
|
||||
// Index records the index into the enclosing
|
||||
// - {DefStmt,File}.Locals, if Scope==Local
|
||||
// - DefStmt.FreeVars, if Scope==Free
|
||||
// - File.Globals, if Scope==Global.
|
||||
// It is zero if Scope is Predeclared, Universal, or Undefined.
|
||||
Index int
|
||||
|
||||
First *syntax.Ident // first binding use (iff Scope==Local/Free/Global)
|
||||
}
|
||||
|
||||
// The Scope of Binding indicates what kind of scope it has.
|
||||
type Scope uint8
|
||||
|
||||
const (
|
||||
Undefined Scope = iota // name is not defined
|
||||
Local // name is local to its function or file
|
||||
Cell // name is function-local but shared with a nested function
|
||||
Free // name is cell of some enclosing function
|
||||
Global // name is global to module
|
||||
Predeclared // name is predeclared for this module (e.g. glob)
|
||||
Universal // name is universal (e.g. len)
|
||||
)
|
||||
|
||||
var scopeNames = [...]string{
|
||||
Undefined: "undefined",
|
||||
Local: "local",
|
||||
Cell: "cell",
|
||||
Free: "free",
|
||||
Global: "global",
|
||||
Predeclared: "predeclared",
|
||||
Universal: "universal",
|
||||
}
|
||||
|
||||
func (scope Scope) String() string { return scopeNames[scope] }
|
||||
|
||||
// A Module contains resolver information about a file.
|
||||
// The resolver populates the Module field of each syntax.File.
|
||||
type Module struct {
|
||||
Locals []*Binding // the file's (comprehension-)local variables
|
||||
Globals []*Binding // the file's global variables
|
||||
}
|
||||
|
||||
// A Function contains resolver information about a named or anonymous function.
|
||||
// The resolver populates the Function field of each syntax.DefStmt and syntax.LambdaExpr.
|
||||
type Function struct {
|
||||
Pos syntax.Position // of DEF or LAMBDA
|
||||
Name string // name of def, or "lambda"
|
||||
Params []syntax.Expr // param = ident | ident=expr | * | *ident | **ident
|
||||
Body []syntax.Stmt // contains synthetic 'return expr' for lambda
|
||||
|
||||
HasVarargs bool // whether params includes *args (convenience)
|
||||
HasKwargs bool // whether params includes **kwargs (convenience)
|
||||
NumKwonlyParams int // number of keyword-only optional parameters
|
||||
Locals []*Binding // this function's local/cell variables, parameters first
|
||||
FreeVars []*Binding // enclosing cells to capture in closure
|
||||
}
|
||||
978
vendor/go.starlark.net/resolve/resolve.go
generated
vendored
Normal file
978
vendor/go.starlark.net/resolve/resolve.go
generated
vendored
Normal file
@@ -0,0 +1,978 @@
|
||||
// Copyright 2017 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package resolve defines a name-resolution pass for Starlark abstract
|
||||
// syntax trees.
|
||||
//
|
||||
// The resolver sets the Locals and FreeVars arrays of each DefStmt and
|
||||
// the LocalIndex field of each syntax.Ident that refers to a local or
|
||||
// free variable. It also sets the Locals array of a File for locals
|
||||
// bound by top-level comprehensions and load statements.
|
||||
// Identifiers for global variables do not get an index.
|
||||
package resolve // import "go.starlark.net/resolve"
|
||||
|
||||
// All references to names are statically resolved. Names may be
|
||||
// predeclared, global, or local to a function or file.
|
||||
// File-local variables include those bound by top-level comprehensions
|
||||
// and by load statements. ("Top-level" means "outside of any function".)
|
||||
// The resolver maps each global name to a small integer and each local
|
||||
// name to a small integer; these integers enable a fast and compact
|
||||
// representation of globals and locals in the evaluator.
|
||||
//
|
||||
// As an optimization, the resolver classifies each predeclared name as
|
||||
// either universal (e.g. None, len) or per-module (e.g. glob in Bazel's
|
||||
// build language), enabling the evaluator to share the representation
|
||||
// of the universal environment across all modules.
|
||||
//
|
||||
// The lexical environment is a tree of blocks with the file block at
|
||||
// its root. The file's child blocks may be of two kinds: functions
|
||||
// and comprehensions, and these may have further children of either
|
||||
// kind.
|
||||
//
|
||||
// Python-style resolution requires multiple passes because a name is
|
||||
// determined to be local to a function only if the function contains a
|
||||
// "binding" use of it; similarly, a name is determined to be global (as
|
||||
// opposed to predeclared) if the module contains a top-level binding use.
|
||||
// Unlike ordinary top-level assignments, the bindings created by load
|
||||
// statements are local to the file block.
|
||||
// A non-binding use may lexically precede the binding to which it is resolved.
|
||||
// In the first pass, we inspect each function, recording in
|
||||
// 'uses' each identifier and the environment block in which it occurs.
|
||||
// If a use of a name is binding, such as a function parameter or
|
||||
// assignment, we add the name to the block's bindings mapping and add a
|
||||
// local variable to the enclosing function.
|
||||
//
|
||||
// As we finish resolving each function, we inspect all the uses within
|
||||
// that function and discard ones that were found to be function-local. The
|
||||
// remaining ones must be either free (local to some lexically enclosing
|
||||
// function), or top-level (global, predeclared, or file-local), but we cannot tell
|
||||
// which until we have finished inspecting the outermost enclosing
|
||||
// function. At that point, we can distinguish local from top-level names
|
||||
// (and this is when Python would compute free variables).
|
||||
//
|
||||
// However, Starlark additionally requires that all references to global
|
||||
// names are satisfied by some declaration in the current module;
|
||||
// Starlark permits a function to forward-reference a global or file-local
|
||||
// that has not
|
||||
// been declared yet so long as it is declared before the end of the
|
||||
// module. So, instead of re-resolving the unresolved references after
|
||||
// each top-level function, we defer this until the end of the module
|
||||
// and ensure that all such references are satisfied by some definition.
|
||||
//
|
||||
// At the end of the module, we visit each of the nested function blocks
|
||||
// in bottom-up order, doing a recursive lexical lookup for each
|
||||
// unresolved name. If the name is found to be local to some enclosing
|
||||
// function, we must create a DefStmt.FreeVar (capture) parameter for
|
||||
// each intervening function. We enter these synthetic bindings into
|
||||
// the bindings map so that we create at most one freevar per name. If
|
||||
// the name was not local, we check that it was defined at module level.
|
||||
//
|
||||
// We resolve all uses of locals in the module (due to load statements
|
||||
// and comprehensions) in a similar way and compute the file's set of
|
||||
// local variables.
|
||||
//
|
||||
// Starlark enforces that all global names are assigned at most once on
|
||||
// all control flow paths by forbidding if/else statements and loops at
|
||||
// top level. A global may be used before it is defined, leading to a
|
||||
// dynamic error. However, the AllowGlobalReassign flag (really: allow
|
||||
// top-level reassign) makes the resolver allow multiple to a variable
|
||||
// at top-level. It also allows if-, for-, and while-loops at top-level,
|
||||
// which in turn may make the evaluator dynamically assign multiple
|
||||
// values to a variable at top-level. (These two roles should be separated.)
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"go.starlark.net/internal/spell"
|
||||
"go.starlark.net/syntax"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
const doesnt = "this Starlark dialect does not "
|
||||
|
||||
// global options
|
||||
// These features are either not standard Starlark (yet), or deprecated
|
||||
// features of the BUILD language, so we put them behind flags.
|
||||
var (
|
||||
AllowNestedDef = false // allow def statements within function bodies
|
||||
AllowLambda = false // allow lambda expressions
|
||||
AllowFloat = false // allow floating point literals, the 'float' built-in, and x / y
|
||||
AllowSet = false // allow the 'set' built-in
|
||||
AllowGlobalReassign = false // allow reassignment to top-level names; also, allow if/for/while at top-level
|
||||
AllowRecursion = false // allow while statements and recursive functions
|
||||
AllowBitwise = true // obsolete; bitwise operations (&, |, ^, ~, <<, and >>) are always enabled
|
||||
LoadBindsGlobally = false // load creates global not file-local bindings (deprecated)
|
||||
)
|
||||
|
||||
// File resolves the specified file and records information about the
|
||||
// module in file.Module.
|
||||
//
|
||||
// The isPredeclared and isUniversal predicates report whether a name is
|
||||
// a pre-declared identifier (visible in the current module) or a
|
||||
// universal identifier (visible in every module).
|
||||
// Clients should typically pass predeclared.Has for the first and
|
||||
// starlark.Universe.Has for the second, where predeclared is the
|
||||
// module's StringDict of predeclared names and starlark.Universe is the
|
||||
// standard set of built-ins.
|
||||
// The isUniverse predicate is supplied a parameter to avoid a cyclic
|
||||
// dependency upon starlark.Universe, not because users should ever need
|
||||
// to redefine it.
|
||||
func File(file *syntax.File, isPredeclared, isUniversal func(name string) bool) error {
|
||||
return REPLChunk(file, nil, isPredeclared, isUniversal)
|
||||
}
|
||||
|
||||
// REPLChunk is a generalization of the File function that supports a
|
||||
// non-empty initial global block, as occurs in a REPL.
|
||||
func REPLChunk(file *syntax.File, isGlobal, isPredeclared, isUniversal func(name string) bool) error {
|
||||
r := newResolver(isGlobal, isPredeclared, isUniversal)
|
||||
r.stmts(file.Stmts)
|
||||
|
||||
r.env.resolveLocalUses()
|
||||
|
||||
// At the end of the module, resolve all non-local variable references,
|
||||
// computing closures.
|
||||
// Function bodies may contain forward references to later global declarations.
|
||||
r.resolveNonLocalUses(r.env)
|
||||
|
||||
file.Module = &Module{
|
||||
Locals: r.moduleLocals,
|
||||
Globals: r.moduleGlobals,
|
||||
}
|
||||
|
||||
if len(r.errors) > 0 {
|
||||
return r.errors
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Expr resolves the specified expression.
|
||||
// It returns the local variables bound within the expression.
|
||||
//
|
||||
// The isPredeclared and isUniversal predicates behave as for the File function.
|
||||
func Expr(expr syntax.Expr, isPredeclared, isUniversal func(name string) bool) ([]*Binding, error) {
|
||||
r := newResolver(nil, isPredeclared, isUniversal)
|
||||
r.expr(expr)
|
||||
r.env.resolveLocalUses()
|
||||
r.resolveNonLocalUses(r.env) // globals & universals
|
||||
if len(r.errors) > 0 {
|
||||
return nil, r.errors
|
||||
}
|
||||
return r.moduleLocals, nil
|
||||
}
|
||||
|
||||
// An ErrorList is a non-empty list of resolver error messages.
|
||||
type ErrorList []Error // len > 0
|
||||
|
||||
func (e ErrorList) Error() string { return e[0].Error() }
|
||||
|
||||
// An Error describes the nature and position of a resolver error.
|
||||
type Error struct {
|
||||
Pos syntax.Position
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e Error) Error() string { return e.Pos.String() + ": " + e.Msg }
|
||||
|
||||
func newResolver(isGlobal, isPredeclared, isUniversal func(name string) bool) *resolver {
|
||||
file := new(block)
|
||||
return &resolver{
|
||||
file: file,
|
||||
env: file,
|
||||
isGlobal: isGlobal,
|
||||
isPredeclared: isPredeclared,
|
||||
isUniversal: isUniversal,
|
||||
globals: make(map[string]*Binding),
|
||||
predeclared: make(map[string]*Binding),
|
||||
}
|
||||
}
|
||||
|
||||
type resolver struct {
|
||||
// env is the current local environment:
|
||||
// a linked list of blocks, innermost first.
|
||||
// The tail of the list is the file block.
|
||||
env *block
|
||||
file *block // file block (contains load bindings)
|
||||
|
||||
// moduleLocals contains the local variables of the module
|
||||
// (due to load statements and comprehensions outside any function).
|
||||
// moduleGlobals contains the global variables of the module.
|
||||
moduleLocals []*Binding
|
||||
moduleGlobals []*Binding
|
||||
|
||||
// globals maps each global name in the module to its binding.
|
||||
// predeclared does the same for predeclared and universal names.
|
||||
globals map[string]*Binding
|
||||
predeclared map[string]*Binding
|
||||
|
||||
// These predicates report whether a name is
|
||||
// pre-declared, either in this module or universally,
|
||||
// or already declared in the module globals (as in a REPL).
|
||||
// isGlobal may be nil.
|
||||
isGlobal, isPredeclared, isUniversal func(name string) bool
|
||||
|
||||
loops int // number of enclosing for loops
|
||||
|
||||
errors ErrorList
|
||||
}
|
||||
|
||||
// container returns the innermost enclosing "container" block:
|
||||
// a function (function != nil) or file (function == nil).
|
||||
// Container blocks accumulate local variable bindings.
|
||||
func (r *resolver) container() *block {
|
||||
for b := r.env; ; b = b.parent {
|
||||
if b.function != nil || b == r.file {
|
||||
return b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resolver) push(b *block) {
|
||||
r.env.children = append(r.env.children, b)
|
||||
b.parent = r.env
|
||||
r.env = b
|
||||
}
|
||||
|
||||
func (r *resolver) pop() { r.env = r.env.parent }
|
||||
|
||||
type block struct {
|
||||
parent *block // nil for file block
|
||||
|
||||
// In the file (root) block, both these fields are nil.
|
||||
function *Function // only for function blocks
|
||||
comp *syntax.Comprehension // only for comprehension blocks
|
||||
|
||||
// bindings maps a name to its binding.
|
||||
// A local binding has an index into its innermost enclosing container's locals array.
|
||||
// A free binding has an index into its innermost enclosing function's freevars array.
|
||||
bindings map[string]*Binding
|
||||
|
||||
// children records the child blocks of the current one.
|
||||
children []*block
|
||||
|
||||
// uses records all identifiers seen in this container (function or file),
|
||||
// and a reference to the environment in which they appear.
|
||||
// As we leave each container block, we resolve them,
|
||||
// so that only free and global ones remain.
|
||||
// At the end of each top-level function we compute closures.
|
||||
uses []use
|
||||
}
|
||||
|
||||
func (b *block) bind(name string, bind *Binding) {
|
||||
if b.bindings == nil {
|
||||
b.bindings = make(map[string]*Binding)
|
||||
}
|
||||
b.bindings[name] = bind
|
||||
}
|
||||
|
||||
func (b *block) String() string {
|
||||
if b.function != nil {
|
||||
return "function block at " + fmt.Sprint(b.function.Pos)
|
||||
}
|
||||
if b.comp != nil {
|
||||
return "comprehension block at " + fmt.Sprint(b.comp.Span())
|
||||
}
|
||||
return "file block"
|
||||
}
|
||||
|
||||
func (r *resolver) errorf(posn syntax.Position, format string, args ...interface{}) {
|
||||
r.errors = append(r.errors, Error{posn, fmt.Sprintf(format, args...)})
|
||||
}
|
||||
|
||||
// A use records an identifier and the environment in which it appears.
|
||||
type use struct {
|
||||
id *syntax.Ident
|
||||
env *block
|
||||
}
|
||||
|
||||
// bind creates a binding for id: a global (not file-local)
|
||||
// binding at top-level, a local binding otherwise.
|
||||
// At top-level, it reports an error if a global or file-local
|
||||
// binding already exists, unless AllowGlobalReassign.
|
||||
// It sets id.Binding to the binding (whether old or new),
|
||||
// and returns whether a binding already existed.
|
||||
func (r *resolver) bind(id *syntax.Ident) bool {
|
||||
// Binding outside any local (comprehension/function) block?
|
||||
if r.env == r.file {
|
||||
bind, ok := r.file.bindings[id.Name]
|
||||
if !ok {
|
||||
bind, ok = r.globals[id.Name]
|
||||
if !ok {
|
||||
// first global binding of this name
|
||||
bind = &Binding{
|
||||
First: id,
|
||||
Scope: Global,
|
||||
Index: len(r.moduleGlobals),
|
||||
}
|
||||
r.globals[id.Name] = bind
|
||||
r.moduleGlobals = append(r.moduleGlobals, bind)
|
||||
}
|
||||
}
|
||||
if ok && !AllowGlobalReassign {
|
||||
r.errorf(id.NamePos, "cannot reassign %s %s declared at %s",
|
||||
bind.Scope, id.Name, bind.First.NamePos)
|
||||
}
|
||||
id.Binding = bind
|
||||
return ok
|
||||
}
|
||||
|
||||
return r.bindLocal(id)
|
||||
}
|
||||
|
||||
func (r *resolver) bindLocal(id *syntax.Ident) bool {
|
||||
// Mark this name as local to current block.
|
||||
// Assign it a new local (positive) index in the current container.
|
||||
_, ok := r.env.bindings[id.Name]
|
||||
if !ok {
|
||||
var locals *[]*Binding
|
||||
if fn := r.container().function; fn != nil {
|
||||
locals = &fn.Locals
|
||||
} else {
|
||||
locals = &r.moduleLocals
|
||||
}
|
||||
bind := &Binding{
|
||||
First: id,
|
||||
Scope: Local,
|
||||
Index: len(*locals),
|
||||
}
|
||||
r.env.bind(id.Name, bind)
|
||||
*locals = append(*locals, bind)
|
||||
}
|
||||
|
||||
r.use(id)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *resolver) use(id *syntax.Ident) {
|
||||
use := use{id, r.env}
|
||||
|
||||
// The spec says that if there is a global binding of a name
|
||||
// then all references to that name in that block refer to the
|
||||
// global, even if the use precedes the def---just as for locals.
|
||||
// For example, in this code,
|
||||
//
|
||||
// print(len); len=1; print(len)
|
||||
//
|
||||
// both occurrences of len refer to the len=1 binding, which
|
||||
// completely shadows the predeclared len function.
|
||||
//
|
||||
// The rationale for these semantics, which differ from Python,
|
||||
// is that the static meaning of len (a reference to a global)
|
||||
// does not change depending on where it appears in the file.
|
||||
// Of course, its dynamic meaning does change, from an error
|
||||
// into a valid reference, so it's not clear these semantics
|
||||
// have any practical advantage.
|
||||
//
|
||||
// In any case, the Bazel implementation lags behind the spec
|
||||
// and follows Python behavior, so the first use of len refers
|
||||
// to the predeclared function. This typically used in a BUILD
|
||||
// file that redefines a predeclared name half way through,
|
||||
// for example:
|
||||
//
|
||||
// proto_library(...) # built-in rule
|
||||
// load("myproto.bzl", "proto_library")
|
||||
// proto_library(...) # user-defined rule
|
||||
//
|
||||
// We will piggyback support for the legacy semantics on the
|
||||
// AllowGlobalReassign flag, which is loosely related and also
|
||||
// required for Bazel.
|
||||
if AllowGlobalReassign && r.env == r.file {
|
||||
r.useToplevel(use)
|
||||
return
|
||||
}
|
||||
|
||||
b := r.container()
|
||||
b.uses = append(b.uses, use)
|
||||
}
|
||||
|
||||
// useToplevel resolves use.id as a reference to a name visible at top-level.
|
||||
// The use.env field captures the original environment for error reporting.
|
||||
func (r *resolver) useToplevel(use use) (bind *Binding) {
|
||||
id := use.id
|
||||
|
||||
if prev, ok := r.file.bindings[id.Name]; ok {
|
||||
// use of load-defined name in file block
|
||||
bind = prev
|
||||
} else if prev, ok := r.globals[id.Name]; ok {
|
||||
// use of global declared by module
|
||||
bind = prev
|
||||
} else if r.isGlobal != nil && r.isGlobal(id.Name) {
|
||||
// use of global defined in a previous REPL chunk
|
||||
bind = &Binding{
|
||||
First: id, // wrong: this is not even a binding use
|
||||
Scope: Global,
|
||||
Index: len(r.moduleGlobals),
|
||||
}
|
||||
r.globals[id.Name] = bind
|
||||
r.moduleGlobals = append(r.moduleGlobals, bind)
|
||||
} else if prev, ok := r.predeclared[id.Name]; ok {
|
||||
// repeated use of predeclared or universal
|
||||
bind = prev
|
||||
} else if r.isPredeclared(id.Name) {
|
||||
// use of pre-declared name
|
||||
bind = &Binding{Scope: Predeclared}
|
||||
r.predeclared[id.Name] = bind // save it
|
||||
} else if r.isUniversal(id.Name) {
|
||||
// use of universal name
|
||||
if !AllowFloat && id.Name == "float" {
|
||||
r.errorf(id.NamePos, doesnt+"support floating point")
|
||||
}
|
||||
if !AllowSet && id.Name == "set" {
|
||||
r.errorf(id.NamePos, doesnt+"support sets")
|
||||
}
|
||||
bind = &Binding{Scope: Universal}
|
||||
r.predeclared[id.Name] = bind // save it
|
||||
} else {
|
||||
bind = &Binding{Scope: Undefined}
|
||||
var hint string
|
||||
if n := r.spellcheck(use); n != "" {
|
||||
hint = fmt.Sprintf(" (did you mean %s?)", n)
|
||||
}
|
||||
r.errorf(id.NamePos, "undefined: %s%s", id.Name, hint)
|
||||
}
|
||||
id.Binding = bind
|
||||
return bind
|
||||
}
|
||||
|
||||
// spellcheck returns the most likely misspelling of
|
||||
// the name use.id in the environment use.env.
|
||||
func (r *resolver) spellcheck(use use) string {
|
||||
var names []string
|
||||
|
||||
// locals
|
||||
for b := use.env; b != nil; b = b.parent {
|
||||
for name := range b.bindings {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
|
||||
// globals
|
||||
//
|
||||
// We have no way to enumerate the sets whose membership
|
||||
// tests are isPredeclared, isUniverse, and isGlobal,
|
||||
// which includes prior names in the REPL session.
|
||||
for _, bind := range r.moduleGlobals {
|
||||
names = append(names, bind.First.Name)
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
return spell.Nearest(use.id.Name, names)
|
||||
}
|
||||
|
||||
// resolveLocalUses is called when leaving a container (function/module)
|
||||
// block. It resolves all uses of locals/cells within that block.
|
||||
func (b *block) resolveLocalUses() {
|
||||
unresolved := b.uses[:0]
|
||||
for _, use := range b.uses {
|
||||
if bind := lookupLocal(use); bind != nil && (bind.Scope == Local || bind.Scope == Cell) {
|
||||
use.id.Binding = bind
|
||||
} else {
|
||||
unresolved = append(unresolved, use)
|
||||
}
|
||||
}
|
||||
b.uses = unresolved
|
||||
}
|
||||
|
||||
func (r *resolver) stmts(stmts []syntax.Stmt) {
|
||||
for _, stmt := range stmts {
|
||||
r.stmt(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resolver) stmt(stmt syntax.Stmt) {
|
||||
switch stmt := stmt.(type) {
|
||||
case *syntax.ExprStmt:
|
||||
r.expr(stmt.X)
|
||||
|
||||
case *syntax.BranchStmt:
|
||||
if r.loops == 0 && (stmt.Token == syntax.BREAK || stmt.Token == syntax.CONTINUE) {
|
||||
r.errorf(stmt.TokenPos, "%s not in a loop", stmt.Token)
|
||||
}
|
||||
|
||||
case *syntax.IfStmt:
|
||||
if !AllowGlobalReassign && r.container().function == nil {
|
||||
r.errorf(stmt.If, "if statement not within a function")
|
||||
}
|
||||
r.expr(stmt.Cond)
|
||||
r.stmts(stmt.True)
|
||||
r.stmts(stmt.False)
|
||||
|
||||
case *syntax.AssignStmt:
|
||||
r.expr(stmt.RHS)
|
||||
isAugmented := stmt.Op != syntax.EQ
|
||||
r.assign(stmt.LHS, isAugmented)
|
||||
|
||||
case *syntax.DefStmt:
|
||||
if !AllowNestedDef && r.container().function != nil {
|
||||
r.errorf(stmt.Def, doesnt+"support nested def")
|
||||
}
|
||||
r.bind(stmt.Name)
|
||||
fn := &Function{
|
||||
Name: stmt.Name.Name,
|
||||
Pos: stmt.Def,
|
||||
Params: stmt.Params,
|
||||
Body: stmt.Body,
|
||||
}
|
||||
stmt.Function = fn
|
||||
r.function(fn, stmt.Def)
|
||||
|
||||
case *syntax.ForStmt:
|
||||
if !AllowGlobalReassign && r.container().function == nil {
|
||||
r.errorf(stmt.For, "for loop not within a function")
|
||||
}
|
||||
r.expr(stmt.X)
|
||||
const isAugmented = false
|
||||
r.assign(stmt.Vars, isAugmented)
|
||||
r.loops++
|
||||
r.stmts(stmt.Body)
|
||||
r.loops--
|
||||
|
||||
case *syntax.WhileStmt:
|
||||
if !AllowRecursion {
|
||||
r.errorf(stmt.While, doesnt+"support while loops")
|
||||
}
|
||||
if !AllowGlobalReassign && r.container().function == nil {
|
||||
r.errorf(stmt.While, "while loop not within a function")
|
||||
}
|
||||
r.expr(stmt.Cond)
|
||||
r.loops++
|
||||
r.stmts(stmt.Body)
|
||||
r.loops--
|
||||
|
||||
case *syntax.ReturnStmt:
|
||||
if r.container().function == nil {
|
||||
r.errorf(stmt.Return, "return statement not within a function")
|
||||
}
|
||||
if stmt.Result != nil {
|
||||
r.expr(stmt.Result)
|
||||
}
|
||||
|
||||
case *syntax.LoadStmt:
|
||||
if r.container().function != nil {
|
||||
r.errorf(stmt.Load, "load statement within a function")
|
||||
}
|
||||
|
||||
for i, from := range stmt.From {
|
||||
if from.Name == "" {
|
||||
r.errorf(from.NamePos, "load: empty identifier")
|
||||
continue
|
||||
}
|
||||
if from.Name[0] == '_' {
|
||||
r.errorf(from.NamePos, "load: names with leading underscores are not exported: %s", from.Name)
|
||||
}
|
||||
|
||||
id := stmt.To[i]
|
||||
if LoadBindsGlobally {
|
||||
r.bind(id)
|
||||
} else if r.bindLocal(id) && !AllowGlobalReassign {
|
||||
// "Global" in AllowGlobalReassign is a misnomer for "toplevel".
|
||||
// Sadly we can't report the previous declaration
|
||||
// as id.Binding may not be set yet.
|
||||
r.errorf(id.NamePos, "cannot reassign top-level %s", id.Name)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
log.Panicf("unexpected stmt %T", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resolver) assign(lhs syntax.Expr, isAugmented bool) {
|
||||
switch lhs := lhs.(type) {
|
||||
case *syntax.Ident:
|
||||
// x = ...
|
||||
r.bind(lhs)
|
||||
|
||||
case *syntax.IndexExpr:
|
||||
// x[i] = ...
|
||||
r.expr(lhs.X)
|
||||
r.expr(lhs.Y)
|
||||
|
||||
case *syntax.DotExpr:
|
||||
// x.f = ...
|
||||
r.expr(lhs.X)
|
||||
|
||||
case *syntax.TupleExpr:
|
||||
// (x, y) = ...
|
||||
if len(lhs.List) == 0 {
|
||||
r.errorf(syntax.Start(lhs), "can't assign to ()")
|
||||
}
|
||||
if isAugmented {
|
||||
r.errorf(syntax.Start(lhs), "can't use tuple expression in augmented assignment")
|
||||
}
|
||||
for _, elem := range lhs.List {
|
||||
r.assign(elem, isAugmented)
|
||||
}
|
||||
|
||||
case *syntax.ListExpr:
|
||||
// [x, y, z] = ...
|
||||
if len(lhs.List) == 0 {
|
||||
r.errorf(syntax.Start(lhs), "can't assign to []")
|
||||
}
|
||||
if isAugmented {
|
||||
r.errorf(syntax.Start(lhs), "can't use list expression in augmented assignment")
|
||||
}
|
||||
for _, elem := range lhs.List {
|
||||
r.assign(elem, isAugmented)
|
||||
}
|
||||
|
||||
case *syntax.ParenExpr:
|
||||
r.assign(lhs.X, isAugmented)
|
||||
|
||||
default:
|
||||
name := strings.ToLower(strings.TrimPrefix(fmt.Sprintf("%T", lhs), "*syntax."))
|
||||
r.errorf(syntax.Start(lhs), "can't assign to %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resolver) expr(e syntax.Expr) {
|
||||
switch e := e.(type) {
|
||||
case *syntax.Ident:
|
||||
r.use(e)
|
||||
|
||||
case *syntax.Literal:
|
||||
if !AllowFloat && e.Token == syntax.FLOAT {
|
||||
r.errorf(e.TokenPos, doesnt+"support floating point")
|
||||
}
|
||||
|
||||
case *syntax.ListExpr:
|
||||
for _, x := range e.List {
|
||||
r.expr(x)
|
||||
}
|
||||
|
||||
case *syntax.CondExpr:
|
||||
r.expr(e.Cond)
|
||||
r.expr(e.True)
|
||||
r.expr(e.False)
|
||||
|
||||
case *syntax.IndexExpr:
|
||||
r.expr(e.X)
|
||||
r.expr(e.Y)
|
||||
|
||||
case *syntax.DictEntry:
|
||||
r.expr(e.Key)
|
||||
r.expr(e.Value)
|
||||
|
||||
case *syntax.SliceExpr:
|
||||
r.expr(e.X)
|
||||
if e.Lo != nil {
|
||||
r.expr(e.Lo)
|
||||
}
|
||||
if e.Hi != nil {
|
||||
r.expr(e.Hi)
|
||||
}
|
||||
if e.Step != nil {
|
||||
r.expr(e.Step)
|
||||
}
|
||||
|
||||
case *syntax.Comprehension:
|
||||
// The 'in' operand of the first clause (always a ForClause)
|
||||
// is resolved in the outer block; consider: [x for x in x].
|
||||
clause := e.Clauses[0].(*syntax.ForClause)
|
||||
r.expr(clause.X)
|
||||
|
||||
// A list/dict comprehension defines a new lexical block.
|
||||
// Locals defined within the block will be allotted
|
||||
// distinct slots in the locals array of the innermost
|
||||
// enclosing container (function/module) block.
|
||||
r.push(&block{comp: e})
|
||||
|
||||
const isAugmented = false
|
||||
r.assign(clause.Vars, isAugmented)
|
||||
|
||||
for _, clause := range e.Clauses[1:] {
|
||||
switch clause := clause.(type) {
|
||||
case *syntax.IfClause:
|
||||
r.expr(clause.Cond)
|
||||
case *syntax.ForClause:
|
||||
r.assign(clause.Vars, isAugmented)
|
||||
r.expr(clause.X)
|
||||
}
|
||||
}
|
||||
r.expr(e.Body) // body may be *DictEntry
|
||||
r.pop()
|
||||
|
||||
case *syntax.TupleExpr:
|
||||
for _, x := range e.List {
|
||||
r.expr(x)
|
||||
}
|
||||
|
||||
case *syntax.DictExpr:
|
||||
for _, entry := range e.List {
|
||||
entry := entry.(*syntax.DictEntry)
|
||||
r.expr(entry.Key)
|
||||
r.expr(entry.Value)
|
||||
}
|
||||
|
||||
case *syntax.UnaryExpr:
|
||||
r.expr(e.X)
|
||||
|
||||
case *syntax.BinaryExpr:
|
||||
if !AllowFloat && e.Op == syntax.SLASH {
|
||||
r.errorf(e.OpPos, doesnt+"support floating point (use //)")
|
||||
}
|
||||
r.expr(e.X)
|
||||
r.expr(e.Y)
|
||||
|
||||
case *syntax.DotExpr:
|
||||
r.expr(e.X)
|
||||
// ignore e.Name
|
||||
|
||||
case *syntax.CallExpr:
|
||||
r.expr(e.Fn)
|
||||
var seenVarargs, seenKwargs bool
|
||||
var seenName map[string]bool
|
||||
var n, p int
|
||||
for _, arg := range e.Args {
|
||||
pos, _ := arg.Span()
|
||||
if unop, ok := arg.(*syntax.UnaryExpr); ok && unop.Op == syntax.STARSTAR {
|
||||
// **kwargs
|
||||
if seenKwargs {
|
||||
r.errorf(pos, "multiple **kwargs not allowed")
|
||||
}
|
||||
seenKwargs = true
|
||||
r.expr(arg)
|
||||
} else if ok && unop.Op == syntax.STAR {
|
||||
// *args
|
||||
if seenKwargs {
|
||||
r.errorf(pos, "*args may not follow **kwargs")
|
||||
} else if seenVarargs {
|
||||
r.errorf(pos, "multiple *args not allowed")
|
||||
}
|
||||
seenVarargs = true
|
||||
r.expr(arg)
|
||||
} else if binop, ok := arg.(*syntax.BinaryExpr); ok && binop.Op == syntax.EQ {
|
||||
// k=v
|
||||
n++
|
||||
if seenKwargs {
|
||||
r.errorf(pos, "argument may not follow **kwargs")
|
||||
}
|
||||
x := binop.X.(*syntax.Ident)
|
||||
if seenName[x.Name] {
|
||||
r.errorf(x.NamePos, "keyword argument %s repeated", x.Name)
|
||||
} else {
|
||||
if seenName == nil {
|
||||
seenName = make(map[string]bool)
|
||||
}
|
||||
seenName[x.Name] = true
|
||||
}
|
||||
r.expr(binop.Y)
|
||||
} else {
|
||||
// positional argument
|
||||
p++
|
||||
if seenVarargs {
|
||||
r.errorf(pos, "argument may not follow *args")
|
||||
} else if seenKwargs {
|
||||
r.errorf(pos, "argument may not follow **kwargs")
|
||||
} else if len(seenName) > 0 {
|
||||
r.errorf(pos, "positional argument may not follow named")
|
||||
}
|
||||
r.expr(arg)
|
||||
}
|
||||
}
|
||||
|
||||
// Fail gracefully if compiler-imposed limit is exceeded.
|
||||
if p >= 256 {
|
||||
pos, _ := e.Span()
|
||||
r.errorf(pos, "%v positional arguments in call, limit is 255", p)
|
||||
}
|
||||
if n >= 256 {
|
||||
pos, _ := e.Span()
|
||||
r.errorf(pos, "%v keyword arguments in call, limit is 255", n)
|
||||
}
|
||||
|
||||
case *syntax.LambdaExpr:
|
||||
if !AllowLambda {
|
||||
r.errorf(e.Lambda, doesnt+"support lambda")
|
||||
}
|
||||
fn := &Function{
|
||||
Name: "lambda",
|
||||
Pos: e.Lambda,
|
||||
Params: e.Params,
|
||||
Body: []syntax.Stmt{&syntax.ReturnStmt{Result: e.Body}},
|
||||
}
|
||||
e.Function = fn
|
||||
r.function(fn, e.Lambda)
|
||||
|
||||
case *syntax.ParenExpr:
|
||||
r.expr(e.X)
|
||||
|
||||
default:
|
||||
log.Panicf("unexpected expr %T", e)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resolver) function(function *Function, pos syntax.Position) {
|
||||
// Resolve defaults in enclosing environment.
|
||||
for _, param := range function.Params {
|
||||
if binary, ok := param.(*syntax.BinaryExpr); ok {
|
||||
r.expr(binary.Y)
|
||||
}
|
||||
}
|
||||
|
||||
// Enter function block.
|
||||
b := &block{function: function}
|
||||
r.push(b)
|
||||
|
||||
var seenOptional bool
|
||||
var star *syntax.UnaryExpr // * or *args param
|
||||
var starStar *syntax.Ident // **kwargs ident
|
||||
var numKwonlyParams int
|
||||
for _, param := range function.Params {
|
||||
switch param := param.(type) {
|
||||
case *syntax.Ident:
|
||||
// e.g. x
|
||||
if starStar != nil {
|
||||
r.errorf(param.NamePos, "required parameter may not follow **%s", starStar.Name)
|
||||
} else if star != nil {
|
||||
numKwonlyParams++
|
||||
} else if seenOptional {
|
||||
r.errorf(param.NamePos, "required parameter may not follow optional")
|
||||
}
|
||||
if r.bind(param) {
|
||||
r.errorf(param.NamePos, "duplicate parameter: %s", param.Name)
|
||||
}
|
||||
|
||||
case *syntax.BinaryExpr:
|
||||
// e.g. y=dflt
|
||||
if starStar != nil {
|
||||
r.errorf(param.OpPos, "optional parameter may not follow **%s", starStar.Name)
|
||||
} else if star != nil {
|
||||
numKwonlyParams++
|
||||
}
|
||||
if id := param.X.(*syntax.Ident); r.bind(id) {
|
||||
r.errorf(param.OpPos, "duplicate parameter: %s", id.Name)
|
||||
}
|
||||
seenOptional = true
|
||||
|
||||
case *syntax.UnaryExpr:
|
||||
// * or *args or **kwargs
|
||||
if param.Op == syntax.STAR {
|
||||
if starStar != nil {
|
||||
r.errorf(param.OpPos, "* parameter may not follow **%s", starStar.Name)
|
||||
} else if star != nil {
|
||||
r.errorf(param.OpPos, "multiple * parameters not allowed")
|
||||
} else {
|
||||
star = param
|
||||
}
|
||||
} else {
|
||||
if starStar != nil {
|
||||
r.errorf(param.OpPos, "multiple ** parameters not allowed")
|
||||
}
|
||||
starStar = param.X.(*syntax.Ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the *args and **kwargs parameters at the end,
|
||||
// so that regular parameters a/b/c are contiguous and
|
||||
// there is no hole for the "*":
|
||||
// def f(a, b, *args, c=0, **kwargs)
|
||||
// def f(a, b, *, c=0, **kwargs)
|
||||
if star != nil {
|
||||
if id, _ := star.X.(*syntax.Ident); id != nil {
|
||||
// *args
|
||||
if r.bind(id) {
|
||||
r.errorf(id.NamePos, "duplicate parameter: %s", id.Name)
|
||||
}
|
||||
function.HasVarargs = true
|
||||
} else if numKwonlyParams == 0 {
|
||||
r.errorf(star.OpPos, "bare * must be followed by keyword-only parameters")
|
||||
}
|
||||
}
|
||||
if starStar != nil {
|
||||
if r.bind(starStar) {
|
||||
r.errorf(starStar.NamePos, "duplicate parameter: %s", starStar.Name)
|
||||
}
|
||||
function.HasKwargs = true
|
||||
}
|
||||
|
||||
function.NumKwonlyParams = numKwonlyParams
|
||||
r.stmts(function.Body)
|
||||
|
||||
// Resolve all uses of this function's local vars,
|
||||
// and keep just the remaining uses of free/global vars.
|
||||
b.resolveLocalUses()
|
||||
|
||||
// Leave function block.
|
||||
r.pop()
|
||||
|
||||
// References within the function body to globals are not
|
||||
// resolved until the end of the module.
|
||||
}
|
||||
|
||||
func (r *resolver) resolveNonLocalUses(b *block) {
|
||||
// First resolve inner blocks.
|
||||
for _, child := range b.children {
|
||||
r.resolveNonLocalUses(child)
|
||||
}
|
||||
for _, use := range b.uses {
|
||||
use.id.Binding = r.lookupLexical(use, use.env)
|
||||
}
|
||||
}
|
||||
|
||||
// lookupLocal looks up an identifier within its immediately enclosing function.
|
||||
func lookupLocal(use use) *Binding {
|
||||
for env := use.env; env != nil; env = env.parent {
|
||||
if bind, ok := env.bindings[use.id.Name]; ok {
|
||||
if bind.Scope == Free {
|
||||
// shouldn't exist till later
|
||||
log.Panicf("%s: internal error: %s, %v", use.id.NamePos, use.id.Name, bind)
|
||||
}
|
||||
return bind // found
|
||||
}
|
||||
if env.function != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil // not found in this function
|
||||
}
|
||||
|
||||
// lookupLexical looks up an identifier use.id within its lexically enclosing environment.
|
||||
// The use.env field captures the original environment for error reporting.
|
||||
func (r *resolver) lookupLexical(use use, env *block) (bind *Binding) {
|
||||
if debug {
|
||||
fmt.Printf("lookupLexical %s in %s = ...\n", use.id.Name, env)
|
||||
defer func() { fmt.Printf("= %v\n", bind) }()
|
||||
}
|
||||
|
||||
// Is this the file block?
|
||||
if env == r.file {
|
||||
return r.useToplevel(use) // file-local, global, predeclared, or not found
|
||||
}
|
||||
|
||||
// Defined in this block?
|
||||
bind, ok := env.bindings[use.id.Name]
|
||||
if !ok {
|
||||
// Defined in parent block?
|
||||
bind = r.lookupLexical(use, env.parent)
|
||||
if env.function != nil && (bind.Scope == Local || bind.Scope == Free || bind.Scope == Cell) {
|
||||
// Found in parent block, which belongs to enclosing function.
|
||||
// Add the parent's binding to the function's freevars,
|
||||
// and add a new 'free' binding to the inner function's block,
|
||||
// and turn the parent's local into cell.
|
||||
if bind.Scope == Local {
|
||||
bind.Scope = Cell
|
||||
}
|
||||
index := len(env.function.FreeVars)
|
||||
env.function.FreeVars = append(env.function.FreeVars, bind)
|
||||
bind = &Binding{
|
||||
First: bind.First,
|
||||
Scope: Free,
|
||||
Index: index,
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("creating freevar %v in function at %s: %s\n",
|
||||
len(env.function.FreeVars), env.function.Pos, use.id.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Memoize, to avoid duplicate free vars
|
||||
// and redundant global (failing) lookups.
|
||||
env.bind(use.id.Name, bind)
|
||||
}
|
||||
return bind
|
||||
}
|
||||
42
vendor/go.starlark.net/starlark/debug.go
generated
vendored
Normal file
42
vendor/go.starlark.net/starlark/debug.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package starlark
|
||||
|
||||
import "go.starlark.net/syntax"
|
||||
|
||||
// This file defines an experimental API for the debugging tools.
|
||||
// Some of these declarations expose details of internal packages.
|
||||
// (The debugger makes liberal use of exported fields of unexported types.)
|
||||
// Breaking changes may occur without notice.
|
||||
|
||||
// Local returns the value of the i'th local variable.
|
||||
// It may be nil if not yet assigned.
|
||||
//
|
||||
// Local may be called only for frames whose Callable is a *Function (a
|
||||
// function defined by Starlark source code), and only while the frame
|
||||
// is active; it will panic otherwise.
|
||||
//
|
||||
// This function is provided only for debugging tools.
|
||||
//
|
||||
// THIS API IS EXPERIMENTAL AND MAY CHANGE WITHOUT NOTICE.
|
||||
func (fr *frame) Local(i int) Value { return fr.locals[i] }
|
||||
|
||||
// DebugFrame is the debugger API for a frame of the interpreter's call stack.
|
||||
//
|
||||
// Most applications have no need for this API; use CallFrame instead.
|
||||
//
|
||||
// Clients must not retain a DebugFrame nor call any of its methods once
|
||||
// the current built-in call has returned or execution has resumed
|
||||
// after a breakpoint as this may have unpredictable effects, including
|
||||
// but not limited to retention of object that would otherwise be garbage.
|
||||
type DebugFrame interface {
|
||||
Callable() Callable // returns the frame's function
|
||||
Local(i int) Value // returns the value of the (Starlark) frame's ith local variable
|
||||
Position() syntax.Position // returns the current position of execution in this frame
|
||||
}
|
||||
|
||||
// DebugFrame returns the debugger interface for
|
||||
// the specified frame of the interpreter's call stack.
|
||||
// Frame numbering is as for Thread.CallFrame.
|
||||
//
|
||||
// This function is intended for use in debugging tools.
|
||||
// Most applications should have no need for it; use CallFrame instead.
|
||||
func (thread *Thread) DebugFrame(depth int) DebugFrame { return thread.frameAt(depth) }
|
||||
3
vendor/go.starlark.net/starlark/empty.s
generated
vendored
Normal file
3
vendor/go.starlark.net/starlark/empty.s
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// The presence of this file allows the package to use the
|
||||
// "go:linkname" hack to call non-exported functions in the
|
||||
// Go runtime, such as hardware-accelerated string hashing.
|
||||
1497
vendor/go.starlark.net/starlark/eval.go
generated
vendored
Normal file
1497
vendor/go.starlark.net/starlark/eval.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
373
vendor/go.starlark.net/starlark/hashtable.go
generated
vendored
Normal file
373
vendor/go.starlark.net/starlark/hashtable.go
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
// Copyright 2017 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package starlark
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "unsafe" // for go:linkname hack
|
||||
)
|
||||
|
||||
// hashtable is used to represent Starlark dict and set values.
|
||||
// It is a hash table whose key/value entries form a doubly-linked list
|
||||
// in the order the entries were inserted.
|
||||
type hashtable struct {
|
||||
table []bucket // len is zero or a power of two
|
||||
bucket0 [1]bucket // inline allocation for small maps.
|
||||
len uint32
|
||||
itercount uint32 // number of active iterators (ignored if frozen)
|
||||
head *entry // insertion order doubly-linked list; may be nil
|
||||
tailLink **entry // address of nil link at end of list (perhaps &head)
|
||||
frozen bool
|
||||
}
|
||||
|
||||
const bucketSize = 8
|
||||
|
||||
type bucket struct {
|
||||
entries [bucketSize]entry
|
||||
next *bucket // linked list of buckets
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
hash uint32 // nonzero => in use
|
||||
key, value Value
|
||||
next *entry // insertion order doubly-linked list; may be nil
|
||||
prevLink **entry // address of link to this entry (perhaps &head)
|
||||
}
|
||||
|
||||
func (ht *hashtable) init(size int) {
|
||||
if size < 0 {
|
||||
panic("size < 0")
|
||||
}
|
||||
nb := 1
|
||||
for overloaded(size, nb) {
|
||||
nb = nb << 1
|
||||
}
|
||||
if nb < 2 {
|
||||
ht.table = ht.bucket0[:1]
|
||||
} else {
|
||||
ht.table = make([]bucket, nb)
|
||||
}
|
||||
ht.tailLink = &ht.head
|
||||
}
|
||||
|
||||
func (ht *hashtable) freeze() {
|
||||
if !ht.frozen {
|
||||
ht.frozen = true
|
||||
for i := range ht.table {
|
||||
for p := &ht.table[i]; p != nil; p = p.next {
|
||||
for i := range p.entries {
|
||||
e := &p.entries[i]
|
||||
if e.hash != 0 {
|
||||
e.key.Freeze()
|
||||
e.value.Freeze()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ht *hashtable) insert(k, v Value) error {
|
||||
if ht.frozen {
|
||||
return fmt.Errorf("cannot insert into frozen hash table")
|
||||
}
|
||||
if ht.itercount > 0 {
|
||||
return fmt.Errorf("cannot insert into hash table during iteration")
|
||||
}
|
||||
if ht.table == nil {
|
||||
ht.init(1)
|
||||
}
|
||||
h, err := k.Hash()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h == 0 {
|
||||
h = 1 // zero is reserved
|
||||
}
|
||||
|
||||
retry:
|
||||
var insert *entry
|
||||
|
||||
// Inspect each bucket in the bucket list.
|
||||
p := &ht.table[h&(uint32(len(ht.table)-1))]
|
||||
for {
|
||||
for i := range p.entries {
|
||||
e := &p.entries[i]
|
||||
if e.hash != h {
|
||||
if e.hash == 0 {
|
||||
// Found empty entry; make a note.
|
||||
insert = e
|
||||
}
|
||||
continue
|
||||
}
|
||||
if eq, err := Equal(k, e.key); err != nil {
|
||||
return err // e.g. excessively recursive tuple
|
||||
} else if !eq {
|
||||
continue
|
||||
}
|
||||
// Key already present; update value.
|
||||
e.value = v
|
||||
return nil
|
||||
}
|
||||
if p.next == nil {
|
||||
break
|
||||
}
|
||||
p = p.next
|
||||
}
|
||||
|
||||
// Key not found. p points to the last bucket.
|
||||
|
||||
// Does the number of elements exceed the buckets' load factor?
|
||||
if overloaded(int(ht.len), len(ht.table)) {
|
||||
ht.grow()
|
||||
goto retry
|
||||
}
|
||||
|
||||
if insert == nil {
|
||||
// No space in existing buckets. Add a new one to the bucket list.
|
||||
b := new(bucket)
|
||||
p.next = b
|
||||
insert = &b.entries[0]
|
||||
}
|
||||
|
||||
// Insert key/value pair.
|
||||
insert.hash = h
|
||||
insert.key = k
|
||||
insert.value = v
|
||||
|
||||
// Append entry to doubly-linked list.
|
||||
insert.prevLink = ht.tailLink
|
||||
*ht.tailLink = insert
|
||||
ht.tailLink = &insert.next
|
||||
|
||||
ht.len++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func overloaded(elems, buckets int) bool {
|
||||
const loadFactor = 6.5 // just a guess
|
||||
return elems >= bucketSize && float64(elems) >= loadFactor*float64(buckets)
|
||||
}
|
||||
|
||||
func (ht *hashtable) grow() {
|
||||
// Double the number of buckets and rehash.
|
||||
// TODO(adonovan): opt:
|
||||
// - avoid reentrant calls to ht.insert, and specialize it.
|
||||
// e.g. we know the calls to Equals will return false since
|
||||
// there are no duplicates among the old keys.
|
||||
// - saving the entire hash in the bucket would avoid the need to
|
||||
// recompute the hash.
|
||||
// - save the old buckets on a free list.
|
||||
ht.table = make([]bucket, len(ht.table)<<1)
|
||||
oldhead := ht.head
|
||||
ht.head = nil
|
||||
ht.tailLink = &ht.head
|
||||
ht.len = 0
|
||||
for e := oldhead; e != nil; e = e.next {
|
||||
ht.insert(e.key, e.value)
|
||||
}
|
||||
ht.bucket0[0] = bucket{} // clear out unused initial bucket
|
||||
}
|
||||
|
||||
func (ht *hashtable) lookup(k Value) (v Value, found bool, err error) {
|
||||
h, err := k.Hash()
|
||||
if err != nil {
|
||||
return nil, false, err // unhashable
|
||||
}
|
||||
if h == 0 {
|
||||
h = 1 // zero is reserved
|
||||
}
|
||||
if ht.table == nil {
|
||||
return None, false, nil // empty
|
||||
}
|
||||
|
||||
// Inspect each bucket in the bucket list.
|
||||
for p := &ht.table[h&(uint32(len(ht.table)-1))]; p != nil; p = p.next {
|
||||
for i := range p.entries {
|
||||
e := &p.entries[i]
|
||||
if e.hash == h {
|
||||
if eq, err := Equal(k, e.key); err != nil {
|
||||
return nil, false, err // e.g. excessively recursive tuple
|
||||
} else if eq {
|
||||
return e.value, true, nil // found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return None, false, nil // not found
|
||||
}
|
||||
|
||||
// Items returns all the items in the map (as key/value pairs) in insertion order.
|
||||
func (ht *hashtable) items() []Tuple {
|
||||
items := make([]Tuple, 0, ht.len)
|
||||
array := make([]Value, ht.len*2) // allocate a single backing array
|
||||
for e := ht.head; e != nil; e = e.next {
|
||||
pair := Tuple(array[:2:2])
|
||||
array = array[2:]
|
||||
pair[0] = e.key
|
||||
pair[1] = e.value
|
||||
items = append(items, pair)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func (ht *hashtable) first() (Value, bool) {
|
||||
if ht.head != nil {
|
||||
return ht.head.key, true
|
||||
}
|
||||
return None, false
|
||||
}
|
||||
|
||||
func (ht *hashtable) keys() []Value {
|
||||
keys := make([]Value, 0, ht.len)
|
||||
for e := ht.head; e != nil; e = e.next {
|
||||
keys = append(keys, e.key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (ht *hashtable) delete(k Value) (v Value, found bool, err error) {
|
||||
if ht.frozen {
|
||||
return nil, false, fmt.Errorf("cannot delete from frozen hash table")
|
||||
}
|
||||
if ht.itercount > 0 {
|
||||
return nil, false, fmt.Errorf("cannot delete from hash table during iteration")
|
||||
}
|
||||
if ht.table == nil {
|
||||
return None, false, nil // empty
|
||||
}
|
||||
h, err := k.Hash()
|
||||
if err != nil {
|
||||
return nil, false, err // unhashable
|
||||
}
|
||||
if h == 0 {
|
||||
h = 1 // zero is reserved
|
||||
}
|
||||
|
||||
// Inspect each bucket in the bucket list.
|
||||
for p := &ht.table[h&(uint32(len(ht.table)-1))]; p != nil; p = p.next {
|
||||
for i := range p.entries {
|
||||
e := &p.entries[i]
|
||||
if e.hash == h {
|
||||
if eq, err := Equal(k, e.key); err != nil {
|
||||
return nil, false, err
|
||||
} else if eq {
|
||||
// Remove e from doubly-linked list.
|
||||
*e.prevLink = e.next
|
||||
if e.next == nil {
|
||||
ht.tailLink = e.prevLink // deletion of last entry
|
||||
} else {
|
||||
e.next.prevLink = e.prevLink
|
||||
}
|
||||
|
||||
v := e.value
|
||||
*e = entry{}
|
||||
ht.len--
|
||||
return v, true, nil // found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: remove completely empty bucket from bucket list.
|
||||
|
||||
return None, false, nil // not found
|
||||
}
|
||||
|
||||
func (ht *hashtable) clear() error {
|
||||
if ht.frozen {
|
||||
return fmt.Errorf("cannot clear frozen hash table")
|
||||
}
|
||||
if ht.itercount > 0 {
|
||||
return fmt.Errorf("cannot clear hash table during iteration")
|
||||
}
|
||||
if ht.table != nil {
|
||||
for i := range ht.table {
|
||||
ht.table[i] = bucket{}
|
||||
}
|
||||
}
|
||||
ht.head = nil
|
||||
ht.tailLink = &ht.head
|
||||
ht.len = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// dump is provided as an aid to debugging.
|
||||
func (ht *hashtable) dump() {
|
||||
fmt.Printf("hashtable %p len=%d head=%p tailLink=%p",
|
||||
ht, ht.len, ht.head, ht.tailLink)
|
||||
if ht.tailLink != nil {
|
||||
fmt.Printf(" *tailLink=%p", *ht.tailLink)
|
||||
}
|
||||
fmt.Println()
|
||||
for j := range ht.table {
|
||||
fmt.Printf("bucket chain %d\n", j)
|
||||
for p := &ht.table[j]; p != nil; p = p.next {
|
||||
fmt.Printf("bucket %p\n", p)
|
||||
for i := range p.entries {
|
||||
e := &p.entries[i]
|
||||
fmt.Printf("\tentry %d @ %p hash=%d key=%v value=%v\n",
|
||||
i, e, e.hash, e.key, e.value)
|
||||
fmt.Printf("\t\tnext=%p &next=%p prev=%p",
|
||||
e.next, &e.next, e.prevLink)
|
||||
if e.prevLink != nil {
|
||||
fmt.Printf(" *prev=%p", *e.prevLink)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ht *hashtable) iterate() *keyIterator {
|
||||
if !ht.frozen {
|
||||
ht.itercount++
|
||||
}
|
||||
return &keyIterator{ht: ht, e: ht.head}
|
||||
}
|
||||
|
||||
type keyIterator struct {
|
||||
ht *hashtable
|
||||
e *entry
|
||||
}
|
||||
|
||||
func (it *keyIterator) Next(k *Value) bool {
|
||||
if it.e != nil {
|
||||
*k = it.e.key
|
||||
it.e = it.e.next
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (it *keyIterator) Done() {
|
||||
if !it.ht.frozen {
|
||||
it.ht.itercount--
|
||||
}
|
||||
}
|
||||
|
||||
// hashString computes the hash of s.
|
||||
func hashString(s string) uint32 {
|
||||
if len(s) >= 12 {
|
||||
// Call the Go runtime's optimized hash implementation,
|
||||
// which uses the AESENC instruction on amd64 machines.
|
||||
return uint32(goStringHash(s, 0))
|
||||
}
|
||||
return softHashString(s)
|
||||
}
|
||||
|
||||
//go:linkname goStringHash runtime.stringHash
|
||||
func goStringHash(s string, seed uintptr) uintptr
|
||||
|
||||
// softHashString computes the FNV hash of s in software.
|
||||
func softHashString(s string) uint32 {
|
||||
var h uint32
|
||||
for i := 0; i < len(s); i++ {
|
||||
h ^= uint32(s[i])
|
||||
h *= 16777619
|
||||
}
|
||||
return h
|
||||
}
|
||||
350
vendor/go.starlark.net/starlark/int.go
generated
vendored
Normal file
350
vendor/go.starlark.net/starlark/int.go
generated
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
// Copyright 2017 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package starlark
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"go.starlark.net/syntax"
|
||||
)
|
||||
|
||||
// Int is the type of a Starlark int.
|
||||
type Int struct {
|
||||
// We use only the signed 32 bit range of small to ensure
|
||||
// that small+small and small*small do not overflow.
|
||||
|
||||
small int64 // minint32 <= small <= maxint32
|
||||
big *big.Int // big != nil <=> value is not representable as int32
|
||||
}
|
||||
|
||||
// newBig allocates a new big.Int.
|
||||
func newBig(x int64) *big.Int {
|
||||
if 0 <= x && int64(big.Word(x)) == x {
|
||||
// x is guaranteed to fit into a single big.Word.
|
||||
// Most starlark ints are small,
|
||||
// but math/big assumes that since you've chosen to use math/big,
|
||||
// your big.Ints will probably grow, so it over-allocates.
|
||||
// Avoid that over-allocation by manually constructing a single-word slice.
|
||||
// See https://golang.org/cl/150999, which will hopefully land in Go 1.13.
|
||||
return new(big.Int).SetBits([]big.Word{big.Word(x)})
|
||||
}
|
||||
return big.NewInt(x)
|
||||
}
|
||||
|
||||
// MakeInt returns a Starlark int for the specified signed integer.
|
||||
func MakeInt(x int) Int { return MakeInt64(int64(x)) }
|
||||
|
||||
// MakeInt64 returns a Starlark int for the specified int64.
|
||||
func MakeInt64(x int64) Int {
|
||||
if math.MinInt32 <= x && x <= math.MaxInt32 {
|
||||
return Int{small: x}
|
||||
}
|
||||
return Int{big: newBig(x)}
|
||||
}
|
||||
|
||||
// MakeUint returns a Starlark int for the specified unsigned integer.
|
||||
func MakeUint(x uint) Int { return MakeUint64(uint64(x)) }
|
||||
|
||||
// MakeUint64 returns a Starlark int for the specified uint64.
|
||||
func MakeUint64(x uint64) Int {
|
||||
if x <= math.MaxInt32 {
|
||||
return Int{small: int64(x)}
|
||||
}
|
||||
if uint64(big.Word(x)) == x {
|
||||
// See comment in newBig for an explanation of this optimization.
|
||||
return Int{big: new(big.Int).SetBits([]big.Word{big.Word(x)})}
|
||||
}
|
||||
return Int{big: new(big.Int).SetUint64(x)}
|
||||
}
|
||||
|
||||
// MakeBigInt returns a Starlark int for the specified big.Int.
|
||||
// The caller must not subsequently modify x.
|
||||
func MakeBigInt(x *big.Int) Int {
|
||||
if n := x.BitLen(); n < 32 || n == 32 && x.Int64() == math.MinInt32 {
|
||||
return Int{small: x.Int64()}
|
||||
}
|
||||
return Int{big: x}
|
||||
}
|
||||
|
||||
var (
|
||||
zero, one = Int{small: 0}, Int{small: 1}
|
||||
oneBig = newBig(1)
|
||||
|
||||
_ HasUnary = Int{}
|
||||
)
|
||||
|
||||
// Unary implements the operations +int, -int, and ~int.
|
||||
func (i Int) Unary(op syntax.Token) (Value, error) {
|
||||
switch op {
|
||||
case syntax.MINUS:
|
||||
return zero.Sub(i), nil
|
||||
case syntax.PLUS:
|
||||
return i, nil
|
||||
case syntax.TILDE:
|
||||
return i.Not(), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Int64 returns the value as an int64.
|
||||
// If it is not exactly representable the result is undefined and ok is false.
|
||||
func (i Int) Int64() (_ int64, ok bool) {
|
||||
if i.big != nil {
|
||||
x, acc := bigintToInt64(i.big)
|
||||
if acc != big.Exact {
|
||||
return // inexact
|
||||
}
|
||||
return x, true
|
||||
}
|
||||
return i.small, true
|
||||
}
|
||||
|
||||
// BigInt returns the value as a big.Int.
|
||||
// The returned variable must not be modified by the client.
|
||||
func (i Int) BigInt() *big.Int {
|
||||
if i.big != nil {
|
||||
return i.big
|
||||
}
|
||||
return newBig(i.small)
|
||||
}
|
||||
|
||||
// Uint64 returns the value as a uint64.
|
||||
// If it is not exactly representable the result is undefined and ok is false.
|
||||
func (i Int) Uint64() (_ uint64, ok bool) {
|
||||
if i.big != nil {
|
||||
x, acc := bigintToUint64(i.big)
|
||||
if acc != big.Exact {
|
||||
return // inexact
|
||||
}
|
||||
return x, true
|
||||
}
|
||||
if i.small < 0 {
|
||||
return // inexact
|
||||
}
|
||||
return uint64(i.small), true
|
||||
}
|
||||
|
||||
// The math/big API should provide this function.
|
||||
func bigintToInt64(i *big.Int) (int64, big.Accuracy) {
|
||||
sign := i.Sign()
|
||||
if sign > 0 {
|
||||
if i.Cmp(maxint64) > 0 {
|
||||
return math.MaxInt64, big.Below
|
||||
}
|
||||
} else if sign < 0 {
|
||||
if i.Cmp(minint64) < 0 {
|
||||
return math.MinInt64, big.Above
|
||||
}
|
||||
}
|
||||
return i.Int64(), big.Exact
|
||||
}
|
||||
|
||||
// The math/big API should provide this function.
|
||||
func bigintToUint64(i *big.Int) (uint64, big.Accuracy) {
|
||||
sign := i.Sign()
|
||||
if sign > 0 {
|
||||
if i.BitLen() > 64 {
|
||||
return math.MaxUint64, big.Below
|
||||
}
|
||||
} else if sign < 0 {
|
||||
return 0, big.Above
|
||||
}
|
||||
return i.Uint64(), big.Exact
|
||||
}
|
||||
|
||||
var (
|
||||
minint64 = new(big.Int).SetInt64(math.MinInt64)
|
||||
maxint64 = new(big.Int).SetInt64(math.MaxInt64)
|
||||
)
|
||||
|
||||
func (i Int) Format(s fmt.State, ch rune) {
|
||||
if i.big != nil {
|
||||
i.big.Format(s, ch)
|
||||
return
|
||||
}
|
||||
newBig(i.small).Format(s, ch)
|
||||
}
|
||||
func (i Int) String() string {
|
||||
if i.big != nil {
|
||||
return i.big.Text(10)
|
||||
}
|
||||
return strconv.FormatInt(i.small, 10)
|
||||
}
|
||||
func (i Int) Type() string { return "int" }
|
||||
func (i Int) Freeze() {} // immutable
|
||||
func (i Int) Truth() Bool { return i.Sign() != 0 }
|
||||
func (i Int) Hash() (uint32, error) {
|
||||
var lo big.Word
|
||||
if i.big != nil {
|
||||
lo = i.big.Bits()[0]
|
||||
} else {
|
||||
lo = big.Word(i.small)
|
||||
}
|
||||
return 12582917 * uint32(lo+3), nil
|
||||
}
|
||||
func (x Int) CompareSameType(op syntax.Token, v Value, depth int) (bool, error) {
|
||||
y := v.(Int)
|
||||
if x.big != nil || y.big != nil {
|
||||
return threeway(op, x.BigInt().Cmp(y.BigInt())), nil
|
||||
}
|
||||
return threeway(op, signum64(x.small-y.small)), nil
|
||||
}
|
||||
|
||||
// Float returns the float value nearest i.
|
||||
func (i Int) Float() Float {
|
||||
if i.big != nil {
|
||||
f, _ := new(big.Float).SetInt(i.big).Float64()
|
||||
return Float(f)
|
||||
}
|
||||
return Float(i.small)
|
||||
}
|
||||
|
||||
func (x Int) Sign() int {
|
||||
if x.big != nil {
|
||||
return x.big.Sign()
|
||||
}
|
||||
return signum64(x.small)
|
||||
}
|
||||
|
||||
func (x Int) Add(y Int) Int {
|
||||
if x.big != nil || y.big != nil {
|
||||
return MakeBigInt(new(big.Int).Add(x.BigInt(), y.BigInt()))
|
||||
}
|
||||
return MakeInt64(x.small + y.small)
|
||||
}
|
||||
func (x Int) Sub(y Int) Int {
|
||||
if x.big != nil || y.big != nil {
|
||||
return MakeBigInt(new(big.Int).Sub(x.BigInt(), y.BigInt()))
|
||||
}
|
||||
return MakeInt64(x.small - y.small)
|
||||
}
|
||||
func (x Int) Mul(y Int) Int {
|
||||
if x.big != nil || y.big != nil {
|
||||
return MakeBigInt(new(big.Int).Mul(x.BigInt(), y.BigInt()))
|
||||
}
|
||||
return MakeInt64(x.small * y.small)
|
||||
}
|
||||
func (x Int) Or(y Int) Int {
|
||||
if x.big != nil || y.big != nil {
|
||||
return Int{big: new(big.Int).Or(x.BigInt(), y.BigInt())}
|
||||
}
|
||||
return Int{small: x.small | y.small}
|
||||
}
|
||||
func (x Int) And(y Int) Int {
|
||||
if x.big != nil || y.big != nil {
|
||||
return MakeBigInt(new(big.Int).And(x.BigInt(), y.BigInt()))
|
||||
}
|
||||
return Int{small: x.small & y.small}
|
||||
}
|
||||
func (x Int) Xor(y Int) Int {
|
||||
if x.big != nil || y.big != nil {
|
||||
return MakeBigInt(new(big.Int).Xor(x.BigInt(), y.BigInt()))
|
||||
}
|
||||
return Int{small: x.small ^ y.small}
|
||||
}
|
||||
func (x Int) Not() Int {
|
||||
if x.big != nil {
|
||||
return MakeBigInt(new(big.Int).Not(x.big))
|
||||
}
|
||||
return Int{small: ^x.small}
|
||||
}
|
||||
func (x Int) Lsh(y uint) Int { return MakeBigInt(new(big.Int).Lsh(x.BigInt(), y)) }
|
||||
func (x Int) Rsh(y uint) Int { return MakeBigInt(new(big.Int).Rsh(x.BigInt(), y)) }
|
||||
|
||||
// Precondition: y is nonzero.
|
||||
func (x Int) Div(y Int) Int {
|
||||
// http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html
|
||||
if x.big != nil || y.big != nil {
|
||||
xb, yb := x.BigInt(), y.BigInt()
|
||||
|
||||
var quo, rem big.Int
|
||||
quo.QuoRem(xb, yb, &rem)
|
||||
if (xb.Sign() < 0) != (yb.Sign() < 0) && rem.Sign() != 0 {
|
||||
quo.Sub(&quo, oneBig)
|
||||
}
|
||||
return MakeBigInt(&quo)
|
||||
}
|
||||
quo := x.small / y.small
|
||||
rem := x.small % y.small
|
||||
if (x.small < 0) != (y.small < 0) && rem != 0 {
|
||||
quo -= 1
|
||||
}
|
||||
return MakeInt64(quo)
|
||||
}
|
||||
|
||||
// Precondition: y is nonzero.
|
||||
func (x Int) Mod(y Int) Int {
|
||||
if x.big != nil || y.big != nil {
|
||||
xb, yb := x.BigInt(), y.BigInt()
|
||||
|
||||
var quo, rem big.Int
|
||||
quo.QuoRem(xb, yb, &rem)
|
||||
if (xb.Sign() < 0) != (yb.Sign() < 0) && rem.Sign() != 0 {
|
||||
rem.Add(&rem, yb)
|
||||
}
|
||||
return MakeBigInt(&rem)
|
||||
}
|
||||
rem := x.small % y.small
|
||||
if (x.small < 0) != (y.small < 0) && rem != 0 {
|
||||
rem += y.small
|
||||
}
|
||||
return Int{small: rem}
|
||||
}
|
||||
|
||||
func (i Int) rational() *big.Rat {
|
||||
if i.big != nil {
|
||||
return new(big.Rat).SetInt(i.big)
|
||||
}
|
||||
return new(big.Rat).SetInt64(i.small)
|
||||
}
|
||||
|
||||
// AsInt32 returns the value of x if is representable as an int32.
|
||||
func AsInt32(x Value) (int, error) {
|
||||
i, ok := x.(Int)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("got %s, want int", x.Type())
|
||||
}
|
||||
if i.big != nil {
|
||||
return 0, fmt.Errorf("%s out of range", i)
|
||||
}
|
||||
return int(i.small), nil
|
||||
}
|
||||
|
||||
// NumberToInt converts a number x to an integer value.
|
||||
// An int is returned unchanged, a float is truncated towards zero.
|
||||
// NumberToInt reports an error for all other values.
|
||||
func NumberToInt(x Value) (Int, error) {
|
||||
switch x := x.(type) {
|
||||
case Int:
|
||||
return x, nil
|
||||
case Float:
|
||||
f := float64(x)
|
||||
if math.IsInf(f, 0) {
|
||||
return zero, fmt.Errorf("cannot convert float infinity to integer")
|
||||
} else if math.IsNaN(f) {
|
||||
return zero, fmt.Errorf("cannot convert float NaN to integer")
|
||||
}
|
||||
return finiteFloatToInt(x), nil
|
||||
|
||||
}
|
||||
return zero, fmt.Errorf("cannot convert %s to int", x.Type())
|
||||
}
|
||||
|
||||
// finiteFloatToInt converts f to an Int, truncating towards zero.
|
||||
// f must be finite.
|
||||
func finiteFloatToInt(f Float) Int {
|
||||
if math.MinInt64 <= f && f <= math.MaxInt64 {
|
||||
// small values
|
||||
return MakeInt64(int64(f))
|
||||
}
|
||||
rat := f.rational()
|
||||
if rat == nil {
|
||||
panic(f) // non-finite
|
||||
}
|
||||
return MakeBigInt(new(big.Int).Div(rat.Num(), rat.Denom()))
|
||||
}
|
||||
637
vendor/go.starlark.net/starlark/interp.go
generated
vendored
Normal file
637
vendor/go.starlark.net/starlark/interp.go
generated
vendored
Normal file
@@ -0,0 +1,637 @@
|
||||
package starlark
|
||||
|
||||
// This file defines the bytecode interpreter.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.starlark.net/internal/compile"
|
||||
"go.starlark.net/internal/spell"
|
||||
"go.starlark.net/resolve"
|
||||
"go.starlark.net/syntax"
|
||||
)
|
||||
|
||||
const vmdebug = false // TODO(adonovan): use a bitfield of specific kinds of error.
|
||||
|
||||
// TODO(adonovan):
|
||||
// - optimize position table.
|
||||
// - opt: record MaxIterStack during compilation and preallocate the stack.
|
||||
|
||||
func (fn *Function) CallInternal(thread *Thread, args Tuple, kwargs []Tuple) (Value, error) {
|
||||
if !resolve.AllowRecursion {
|
||||
// detect recursion
|
||||
for _, fr := range thread.stack[:len(thread.stack)-1] {
|
||||
// We look for the same function code,
|
||||
// not function value, otherwise the user could
|
||||
// defeat the check by writing the Y combinator.
|
||||
if frfn, ok := fr.Callable().(*Function); ok && frfn.funcode == fn.funcode {
|
||||
return nil, fmt.Errorf("function %s called recursively", fn.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f := fn.funcode
|
||||
fr := thread.frameAt(0)
|
||||
|
||||
// Allocate space for stack and locals.
|
||||
// Logically these do not escape from this frame
|
||||
// (See https://github.com/golang/go/issues/20533.)
|
||||
//
|
||||
// This heap allocation looks expensive, but I was unable to get
|
||||
// more than 1% real time improvement in a large alloc-heavy
|
||||
// benchmark (in which this alloc was 8% of alloc-bytes)
|
||||
// by allocating space for 8 Values in each frame, or
|
||||
// by allocating stack by slicing an array held by the Thread
|
||||
// that is expanded in chunks of min(k, nspace), for k=256 or 1024.
|
||||
nlocals := len(f.Locals)
|
||||
nspace := nlocals + f.MaxStack
|
||||
space := make([]Value, nspace)
|
||||
locals := space[:nlocals:nlocals] // local variables, starting with parameters
|
||||
stack := space[nlocals:] // operand stack
|
||||
|
||||
// Digest arguments and set parameters.
|
||||
err := setArgs(locals, fn, args, kwargs)
|
||||
if err != nil {
|
||||
return nil, thread.evalError(err)
|
||||
}
|
||||
|
||||
fr.locals = locals
|
||||
|
||||
if vmdebug {
|
||||
fmt.Printf("Entering %s @ %s\n", f.Name, f.Position(0))
|
||||
fmt.Printf("%d stack, %d locals\n", len(stack), len(locals))
|
||||
defer fmt.Println("Leaving ", f.Name)
|
||||
}
|
||||
|
||||
// Spill indicated locals to cells.
|
||||
// Each cell is a separate alloc to avoid spurious liveness.
|
||||
for _, index := range f.Cells {
|
||||
locals[index] = &cell{locals[index]}
|
||||
}
|
||||
|
||||
// TODO(adonovan): add static check that beneath this point
|
||||
// - there is exactly one return statement
|
||||
// - there is no redefinition of 'err'.
|
||||
|
||||
var iterstack []Iterator // stack of active iterators
|
||||
|
||||
sp := 0
|
||||
var pc uint32
|
||||
var result Value
|
||||
code := f.Code
|
||||
loop:
|
||||
for {
|
||||
fr.pc = pc
|
||||
|
||||
op := compile.Opcode(code[pc])
|
||||
pc++
|
||||
var arg uint32
|
||||
if op >= compile.OpcodeArgMin {
|
||||
// TODO(adonovan): opt: profile this.
|
||||
// Perhaps compiling big endian would be less work to decode?
|
||||
for s := uint(0); ; s += 7 {
|
||||
b := code[pc]
|
||||
pc++
|
||||
arg |= uint32(b&0x7f) << s
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if vmdebug {
|
||||
fmt.Fprintln(os.Stderr, stack[:sp]) // very verbose!
|
||||
compile.PrintOp(f, fr.pc, op, arg)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case compile.NOP:
|
||||
// nop
|
||||
|
||||
case compile.DUP:
|
||||
stack[sp] = stack[sp-1]
|
||||
sp++
|
||||
|
||||
case compile.DUP2:
|
||||
stack[sp] = stack[sp-2]
|
||||
stack[sp+1] = stack[sp-1]
|
||||
sp += 2
|
||||
|
||||
case compile.POP:
|
||||
sp--
|
||||
|
||||
case compile.EXCH:
|
||||
stack[sp-2], stack[sp-1] = stack[sp-1], stack[sp-2]
|
||||
|
||||
case compile.EQL, compile.NEQ, compile.GT, compile.LT, compile.LE, compile.GE:
|
||||
op := syntax.Token(op-compile.EQL) + syntax.EQL
|
||||
y := stack[sp-1]
|
||||
x := stack[sp-2]
|
||||
sp -= 2
|
||||
ok, err2 := Compare(op, x, y)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
stack[sp] = Bool(ok)
|
||||
sp++
|
||||
|
||||
case compile.PLUS,
|
||||
compile.MINUS,
|
||||
compile.STAR,
|
||||
compile.SLASH,
|
||||
compile.SLASHSLASH,
|
||||
compile.PERCENT,
|
||||
compile.AMP,
|
||||
compile.PIPE,
|
||||
compile.CIRCUMFLEX,
|
||||
compile.LTLT,
|
||||
compile.GTGT,
|
||||
compile.IN:
|
||||
binop := syntax.Token(op-compile.PLUS) + syntax.PLUS
|
||||
if op == compile.IN {
|
||||
binop = syntax.IN // IN token is out of order
|
||||
}
|
||||
y := stack[sp-1]
|
||||
x := stack[sp-2]
|
||||
sp -= 2
|
||||
z, err2 := Binary(binop, x, y)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
stack[sp] = z
|
||||
sp++
|
||||
|
||||
case compile.UPLUS, compile.UMINUS, compile.TILDE:
|
||||
var unop syntax.Token
|
||||
if op == compile.TILDE {
|
||||
unop = syntax.TILDE
|
||||
} else {
|
||||
unop = syntax.Token(op-compile.UPLUS) + syntax.PLUS
|
||||
}
|
||||
x := stack[sp-1]
|
||||
y, err2 := Unary(unop, x)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
stack[sp-1] = y
|
||||
|
||||
case compile.INPLACE_ADD:
|
||||
y := stack[sp-1]
|
||||
x := stack[sp-2]
|
||||
sp -= 2
|
||||
|
||||
// It's possible that y is not Iterable but
|
||||
// nonetheless defines x+y, in which case we
|
||||
// should fall back to the general case.
|
||||
var z Value
|
||||
if xlist, ok := x.(*List); ok {
|
||||
if yiter, ok := y.(Iterable); ok {
|
||||
if err = xlist.checkMutable("apply += to"); err != nil {
|
||||
break loop
|
||||
}
|
||||
listExtend(xlist, yiter)
|
||||
z = xlist
|
||||
}
|
||||
}
|
||||
if z == nil {
|
||||
z, err = Binary(syntax.PLUS, x, y)
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
stack[sp] = z
|
||||
sp++
|
||||
|
||||
case compile.NONE:
|
||||
stack[sp] = None
|
||||
sp++
|
||||
|
||||
case compile.TRUE:
|
||||
stack[sp] = True
|
||||
sp++
|
||||
|
||||
case compile.FALSE:
|
||||
stack[sp] = False
|
||||
sp++
|
||||
|
||||
case compile.MANDATORY:
|
||||
stack[sp] = mandatory{}
|
||||
sp++
|
||||
|
||||
case compile.JMP:
|
||||
pc = arg
|
||||
|
||||
case compile.CALL, compile.CALL_VAR, compile.CALL_KW, compile.CALL_VAR_KW:
|
||||
var kwargs Value
|
||||
if op == compile.CALL_KW || op == compile.CALL_VAR_KW {
|
||||
kwargs = stack[sp-1]
|
||||
sp--
|
||||
}
|
||||
|
||||
var args Value
|
||||
if op == compile.CALL_VAR || op == compile.CALL_VAR_KW {
|
||||
args = stack[sp-1]
|
||||
sp--
|
||||
}
|
||||
|
||||
// named args (pairs)
|
||||
var kvpairs []Tuple
|
||||
if nkvpairs := int(arg & 0xff); nkvpairs > 0 {
|
||||
kvpairs = make([]Tuple, 0, nkvpairs)
|
||||
kvpairsAlloc := make(Tuple, 2*nkvpairs) // allocate a single backing array
|
||||
sp -= 2 * nkvpairs
|
||||
for i := 0; i < nkvpairs; i++ {
|
||||
pair := kvpairsAlloc[:2:2]
|
||||
kvpairsAlloc = kvpairsAlloc[2:]
|
||||
pair[0] = stack[sp+2*i] // name
|
||||
pair[1] = stack[sp+2*i+1] // value
|
||||
kvpairs = append(kvpairs, pair)
|
||||
}
|
||||
}
|
||||
if kwargs != nil {
|
||||
// Add key/value items from **kwargs dictionary.
|
||||
dict, ok := kwargs.(IterableMapping)
|
||||
if !ok {
|
||||
err = fmt.Errorf("argument after ** must be a mapping, not %s", kwargs.Type())
|
||||
break loop
|
||||
}
|
||||
items := dict.Items()
|
||||
for _, item := range items {
|
||||
if _, ok := item[0].(String); !ok {
|
||||
err = fmt.Errorf("keywords must be strings, not %s", item[0].Type())
|
||||
break loop
|
||||
}
|
||||
}
|
||||
if len(kvpairs) == 0 {
|
||||
kvpairs = items
|
||||
} else {
|
||||
kvpairs = append(kvpairs, items...)
|
||||
}
|
||||
}
|
||||
|
||||
// positional args
|
||||
var positional Tuple
|
||||
if npos := int(arg >> 8); npos > 0 {
|
||||
positional = make(Tuple, npos)
|
||||
sp -= npos
|
||||
copy(positional, stack[sp:])
|
||||
}
|
||||
if args != nil {
|
||||
// Add elements from *args sequence.
|
||||
iter := Iterate(args)
|
||||
if iter == nil {
|
||||
err = fmt.Errorf("argument after * must be iterable, not %s", args.Type())
|
||||
break loop
|
||||
}
|
||||
var elem Value
|
||||
for iter.Next(&elem) {
|
||||
positional = append(positional, elem)
|
||||
}
|
||||
iter.Done()
|
||||
}
|
||||
|
||||
function := stack[sp-1]
|
||||
|
||||
if vmdebug {
|
||||
fmt.Printf("VM call %s args=%s kwargs=%s @%s\n",
|
||||
function, positional, kvpairs, f.Position(fr.pc))
|
||||
}
|
||||
|
||||
thread.endProfSpan()
|
||||
z, err2 := Call(thread, function, positional, kvpairs)
|
||||
thread.beginProfSpan()
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
if vmdebug {
|
||||
fmt.Printf("Resuming %s @ %s\n", f.Name, f.Position(0))
|
||||
}
|
||||
stack[sp-1] = z
|
||||
|
||||
case compile.ITERPUSH:
|
||||
x := stack[sp-1]
|
||||
sp--
|
||||
iter := Iterate(x)
|
||||
if iter == nil {
|
||||
err = fmt.Errorf("%s value is not iterable", x.Type())
|
||||
break loop
|
||||
}
|
||||
iterstack = append(iterstack, iter)
|
||||
|
||||
case compile.ITERJMP:
|
||||
iter := iterstack[len(iterstack)-1]
|
||||
if iter.Next(&stack[sp]) {
|
||||
sp++
|
||||
} else {
|
||||
pc = arg
|
||||
}
|
||||
|
||||
case compile.ITERPOP:
|
||||
n := len(iterstack) - 1
|
||||
iterstack[n].Done()
|
||||
iterstack = iterstack[:n]
|
||||
|
||||
case compile.NOT:
|
||||
stack[sp-1] = !stack[sp-1].Truth()
|
||||
|
||||
case compile.RETURN:
|
||||
result = stack[sp-1]
|
||||
break loop
|
||||
|
||||
case compile.SETINDEX:
|
||||
z := stack[sp-1]
|
||||
y := stack[sp-2]
|
||||
x := stack[sp-3]
|
||||
sp -= 3
|
||||
err = setIndex(x, y, z)
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
|
||||
case compile.INDEX:
|
||||
y := stack[sp-1]
|
||||
x := stack[sp-2]
|
||||
sp -= 2
|
||||
z, err2 := getIndex(x, y)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
stack[sp] = z
|
||||
sp++
|
||||
|
||||
case compile.ATTR:
|
||||
x := stack[sp-1]
|
||||
name := f.Prog.Names[arg]
|
||||
y, err2 := getAttr(x, name)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
stack[sp-1] = y
|
||||
|
||||
case compile.SETFIELD:
|
||||
y := stack[sp-1]
|
||||
x := stack[sp-2]
|
||||
sp -= 2
|
||||
name := f.Prog.Names[arg]
|
||||
if err2 := setField(x, name, y); err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
|
||||
case compile.MAKEDICT:
|
||||
stack[sp] = new(Dict)
|
||||
sp++
|
||||
|
||||
case compile.SETDICT, compile.SETDICTUNIQ:
|
||||
dict := stack[sp-3].(*Dict)
|
||||
k := stack[sp-2]
|
||||
v := stack[sp-1]
|
||||
sp -= 3
|
||||
oldlen := dict.Len()
|
||||
if err2 := dict.SetKey(k, v); err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
if op == compile.SETDICTUNIQ && dict.Len() == oldlen {
|
||||
err = fmt.Errorf("duplicate key: %v", k)
|
||||
break loop
|
||||
}
|
||||
|
||||
case compile.APPEND:
|
||||
elem := stack[sp-1]
|
||||
list := stack[sp-2].(*List)
|
||||
sp -= 2
|
||||
list.elems = append(list.elems, elem)
|
||||
|
||||
case compile.SLICE:
|
||||
x := stack[sp-4]
|
||||
lo := stack[sp-3]
|
||||
hi := stack[sp-2]
|
||||
step := stack[sp-1]
|
||||
sp -= 4
|
||||
res, err2 := slice(x, lo, hi, step)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
break loop
|
||||
}
|
||||
stack[sp] = res
|
||||
sp++
|
||||
|
||||
case compile.UNPACK:
|
||||
n := int(arg)
|
||||
iterable := stack[sp-1]
|
||||
sp--
|
||||
iter := Iterate(iterable)
|
||||
if iter == nil {
|
||||
err = fmt.Errorf("got %s in sequence assignment", iterable.Type())
|
||||
break loop
|
||||
}
|
||||
i := 0
|
||||
sp += n
|
||||
for i < n && iter.Next(&stack[sp-1-i]) {
|
||||
i++
|
||||
}
|
||||
var dummy Value
|
||||
if iter.Next(&dummy) {
|
||||
// NB: Len may return -1 here in obscure cases.
|
||||
err = fmt.Errorf("too many values to unpack (got %d, want %d)", Len(iterable), n)
|
||||
break loop
|
||||
}
|
||||
iter.Done()
|
||||
if i < n {
|
||||
err = fmt.Errorf("too few values to unpack (got %d, want %d)", i, n)
|
||||
break loop
|
||||
}
|
||||
|
||||
case compile.CJMP:
|
||||
if stack[sp-1].Truth() {
|
||||
pc = arg
|
||||
}
|
||||
sp--
|
||||
|
||||
case compile.CONSTANT:
|
||||
stack[sp] = fn.module.constants[arg]
|
||||
sp++
|
||||
|
||||
case compile.MAKETUPLE:
|
||||
n := int(arg)
|
||||
tuple := make(Tuple, n)
|
||||
sp -= n
|
||||
copy(tuple, stack[sp:])
|
||||
stack[sp] = tuple
|
||||
sp++
|
||||
|
||||
case compile.MAKELIST:
|
||||
n := int(arg)
|
||||
elems := make([]Value, n)
|
||||
sp -= n
|
||||
copy(elems, stack[sp:])
|
||||
stack[sp] = NewList(elems)
|
||||
sp++
|
||||
|
||||
case compile.MAKEFUNC:
|
||||
funcode := f.Prog.Functions[arg]
|
||||
tuple := stack[sp-1].(Tuple)
|
||||
n := len(tuple) - len(funcode.Freevars)
|
||||
defaults := tuple[:n:n]
|
||||
freevars := tuple[n:]
|
||||
stack[sp-1] = &Function{
|
||||
funcode: funcode,
|
||||
module: fn.module,
|
||||
defaults: defaults,
|
||||
freevars: freevars,
|
||||
}
|
||||
|
||||
case compile.LOAD:
|
||||
n := int(arg)
|
||||
module := string(stack[sp-1].(String))
|
||||
sp--
|
||||
|
||||
if thread.Load == nil {
|
||||
err = fmt.Errorf("load not implemented by this application")
|
||||
break loop
|
||||
}
|
||||
|
||||
thread.endProfSpan()
|
||||
dict, err2 := thread.Load(thread, module)
|
||||
thread.beginProfSpan()
|
||||
if err2 != nil {
|
||||
err = wrappedError{
|
||||
msg: fmt.Sprintf("cannot load %s: %v", module, err2),
|
||||
cause: err2,
|
||||
}
|
||||
break loop
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
from := string(stack[sp-1-i].(String))
|
||||
v, ok := dict[from]
|
||||
if !ok {
|
||||
err = fmt.Errorf("load: name %s not found in module %s", from, module)
|
||||
if n := spell.Nearest(from, dict.Keys()); n != "" {
|
||||
err = fmt.Errorf("%s (did you mean %s?)", err, n)
|
||||
}
|
||||
break loop
|
||||
}
|
||||
stack[sp-1-i] = v
|
||||
}
|
||||
|
||||
case compile.SETLOCAL:
|
||||
locals[arg] = stack[sp-1]
|
||||
sp--
|
||||
|
||||
case compile.SETCELL:
|
||||
x := stack[sp-2]
|
||||
y := stack[sp-1]
|
||||
sp -= 2
|
||||
y.(*cell).v = x
|
||||
|
||||
case compile.SETGLOBAL:
|
||||
fn.module.globals[arg] = stack[sp-1]
|
||||
sp--
|
||||
|
||||
case compile.LOCAL:
|
||||
x := locals[arg]
|
||||
if x == nil {
|
||||
err = fmt.Errorf("local variable %s referenced before assignment", f.Locals[arg].Name)
|
||||
break loop
|
||||
}
|
||||
stack[sp] = x
|
||||
sp++
|
||||
|
||||
case compile.FREE:
|
||||
stack[sp] = fn.freevars[arg]
|
||||
sp++
|
||||
|
||||
case compile.CELL:
|
||||
x := stack[sp-1]
|
||||
stack[sp-1] = x.(*cell).v
|
||||
|
||||
case compile.GLOBAL:
|
||||
x := fn.module.globals[arg]
|
||||
if x == nil {
|
||||
err = fmt.Errorf("global variable %s referenced before assignment", f.Prog.Globals[arg].Name)
|
||||
break loop
|
||||
}
|
||||
stack[sp] = x
|
||||
sp++
|
||||
|
||||
case compile.PREDECLARED:
|
||||
name := f.Prog.Names[arg]
|
||||
x := fn.module.predeclared[name]
|
||||
if x == nil {
|
||||
err = fmt.Errorf("internal error: predeclared variable %s is uninitialized", name)
|
||||
break loop
|
||||
}
|
||||
stack[sp] = x
|
||||
sp++
|
||||
|
||||
case compile.UNIVERSAL:
|
||||
stack[sp] = Universe[f.Prog.Names[arg]]
|
||||
sp++
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unimplemented: %s", op)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// ITERPOP the rest of the iterator stack.
|
||||
for _, iter := range iterstack {
|
||||
iter.Done()
|
||||
}
|
||||
|
||||
fr.locals = nil
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
type wrappedError struct {
|
||||
msg string
|
||||
cause error
|
||||
}
|
||||
|
||||
func (e wrappedError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
// Implements the xerrors.Wrapper interface
|
||||
// https://godoc.org/golang.org/x/xerrors#Wrapper
|
||||
func (e wrappedError) Unwrap() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// mandatory is a sentinel value used in a function's defaults tuple
|
||||
// to indicate that a (keyword-only) parameter is mandatory.
|
||||
type mandatory struct{}
|
||||
|
||||
func (mandatory) String() string { return "mandatory" }
|
||||
func (mandatory) Type() string { return "mandatory" }
|
||||
func (mandatory) Freeze() {} // immutable
|
||||
func (mandatory) Truth() Bool { return False }
|
||||
func (mandatory) Hash() (uint32, error) { return 0, nil }
|
||||
|
||||
// A cell is a box containing a Value.
|
||||
// Local variables marked as cells hold their value indirectly
|
||||
// so that they may be shared by outer and inner nested functions.
|
||||
// Cells are always accessed using indirect CELL/SETCELL instructions.
|
||||
// The FreeVars tuple contains only cells.
|
||||
// The FREE instruction always yields a cell.
|
||||
type cell struct{ v Value }
|
||||
|
||||
func (c *cell) String() string { return "cell" }
|
||||
func (c *cell) Type() string { return "cell" }
|
||||
func (c *cell) Freeze() {
|
||||
if c.v != nil {
|
||||
c.v.Freeze()
|
||||
}
|
||||
}
|
||||
func (c *cell) Truth() Bool { panic("unreachable") }
|
||||
func (c *cell) Hash() (uint32, error) { panic("unreachable") }
|
||||
2104
vendor/go.starlark.net/starlark/library.go
generated
vendored
Normal file
2104
vendor/go.starlark.net/starlark/library.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
449
vendor/go.starlark.net/starlark/profile.go
generated
vendored
Normal file
449
vendor/go.starlark.net/starlark/profile.go
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
// Copyright 2019 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package starlark
|
||||
|
||||
// This file defines a simple execution-time profiler for Starlark.
|
||||
// It measures the wall time spent executing Starlark code, and emits a
|
||||
// gzipped protocol message in pprof format (github.com/google/pprof).
|
||||
//
|
||||
// When profiling is enabled, the interpreter calls the profiler to
|
||||
// indicate the start and end of each "span" or time interval. A leaf
|
||||
// function (whether Go or Starlark) has a single span. A function that
|
||||
// calls another function has spans for each interval in which it is the
|
||||
// top of the stack. (A LOAD instruction also ends a span.)
|
||||
//
|
||||
// At the start of a span, the interpreter records the current time in
|
||||
// the thread's topmost frame. At the end of the span, it obtains the
|
||||
// time again and subtracts the span start time. The difference is added
|
||||
// to an accumulator variable in the thread. If the accumulator exceeds
|
||||
// some fixed quantum (10ms, say), the profiler records the current call
|
||||
// stack and sends it to the profiler goroutine, along with the number
|
||||
// of quanta, which are subtracted. For example, if the accumulator
|
||||
// holds 3ms and then a completed span adds 25ms to it, its value is 28ms,
|
||||
// which exceeeds 10ms. The profiler records a stack with the value 20ms
|
||||
// (2 quanta), and the accumulator is left with 8ms.
|
||||
//
|
||||
// The profiler goroutine converts the stacks into the pprof format and
|
||||
// emits a gzip-compressed protocol message to the designated output
|
||||
// file. We use a hand-written streaming proto encoder to avoid
|
||||
// dependencies on pprof and proto, and to avoid the need to
|
||||
// materialize the profile data structure in memory.
|
||||
//
|
||||
// A limitation of this profiler is that it measures wall time, which
|
||||
// does not necessarily correspond to CPU time. A CPU profiler requires
|
||||
// that only running (not runnable) threads are sampled; this is
|
||||
// commonly achieved by having the kernel deliver a (PROF) signal to an
|
||||
// arbitrary running thread, through setitimer(2). The CPU profiler in the
|
||||
// Go runtime uses this mechanism, but it is not possible for a Go
|
||||
// application to register a SIGPROF handler, nor is it possible for a
|
||||
// Go handler for some other signal to read the stack pointer of
|
||||
// the interrupted thread.
|
||||
//
|
||||
// Two caveats:
|
||||
// (1) it is tempting to send the leaf Frame directly to the profiler
|
||||
// goroutine instead of making a copy of the stack, since a Frame is a
|
||||
// spaghetti stack--a linked list. However, as soon as execution
|
||||
// resumes, the stack's Frame.pc values may be mutated, so Frames are
|
||||
// not safe to share with the asynchronous profiler goroutine.
|
||||
// (2) it is tempting to use Callables as keys in a map when tabulating
|
||||
// the pprof protocols's Function entities. However, we cannot assume
|
||||
// that Callables are valid map keys, and furthermore we must not
|
||||
// pin function values in memory indefinitely as this may cause lambda
|
||||
// values to keep their free variables live much longer than necessary.
|
||||
|
||||
// TODO(adonovan):
|
||||
// - make Start/Stop fully thread-safe.
|
||||
// - fix the pc hack.
|
||||
// - experiment with other values of quantum.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"go.starlark.net/syntax"
|
||||
)
|
||||
|
||||
// StartProfile enables time profiling of all Starlark threads,
|
||||
// and writes a profile in pprof format to w.
|
||||
// It must be followed by a call to StopProfiler to stop
|
||||
// the profiler and finalize the profile.
|
||||
//
|
||||
// StartProfile returns an error if profiling was already enabled.
|
||||
//
|
||||
// StartProfile must not be called concurrently with Starlark execution.
|
||||
func StartProfile(w io.Writer) error {
|
||||
if !atomic.CompareAndSwapUint32(&profiler.on, 0, 1) {
|
||||
return fmt.Errorf("profiler already running")
|
||||
}
|
||||
|
||||
// TODO(adonovan): make the API fully concurrency-safe.
|
||||
// The main challenge is racy reads/writes of profiler.events,
|
||||
// and of send/close races on the channel it refers to.
|
||||
// It's easy to solve them with a mutex but harder to do
|
||||
// it efficiently.
|
||||
|
||||
profiler.events = make(chan *profEvent, 1)
|
||||
profiler.done = make(chan error)
|
||||
|
||||
go profile(w)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopProfiler stops the profiler started by a prior call to
|
||||
// StartProfile and finalizes the profile. It returns an error if the
|
||||
// profile could not be completed.
|
||||
//
|
||||
// StopProfiler must not be called concurrently with Starlark execution.
|
||||
func StopProfile() error {
|
||||
// Terminate the profiler goroutine and get its result.
|
||||
close(profiler.events)
|
||||
err := <-profiler.done
|
||||
|
||||
profiler.done = nil
|
||||
profiler.events = nil
|
||||
atomic.StoreUint32(&profiler.on, 0)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// globals
|
||||
var profiler struct {
|
||||
on uint32 // nonzero => profiler running
|
||||
events chan *profEvent // profile events from interpreter threads
|
||||
done chan error // indicates profiler goroutine is ready
|
||||
}
|
||||
|
||||
func (thread *Thread) beginProfSpan() {
|
||||
if profiler.events == nil {
|
||||
return // profiling not enabled
|
||||
}
|
||||
|
||||
thread.frameAt(0).spanStart = nanotime()
|
||||
}
|
||||
|
||||
// TODO(adonovan): experiment with smaller values,
|
||||
// which trade space and time for greater precision.
|
||||
const quantum = 10 * time.Millisecond
|
||||
|
||||
func (thread *Thread) endProfSpan() {
|
||||
if profiler.events == nil {
|
||||
return // profiling not enabled
|
||||
}
|
||||
|
||||
// Add the span to the thread's accumulator.
|
||||
thread.proftime += time.Duration(nanotime() - thread.frameAt(0).spanStart)
|
||||
if thread.proftime < quantum {
|
||||
return
|
||||
}
|
||||
|
||||
// Only record complete quanta.
|
||||
n := thread.proftime / quantum
|
||||
thread.proftime -= n * quantum
|
||||
|
||||
// Copy the stack.
|
||||
// (We can't save thread.frame because its pc will change.)
|
||||
ev := &profEvent{
|
||||
thread: thread,
|
||||
time: n * quantum,
|
||||
}
|
||||
ev.stack = ev.stackSpace[:0]
|
||||
for i := range thread.stack {
|
||||
fr := thread.frameAt(i)
|
||||
ev.stack = append(ev.stack, profFrame{
|
||||
pos: fr.Position(),
|
||||
fn: fr.Callable(),
|
||||
pc: fr.pc,
|
||||
})
|
||||
}
|
||||
|
||||
profiler.events <- ev
|
||||
}
|
||||
|
||||
type profEvent struct {
|
||||
thread *Thread // currently unused
|
||||
time time.Duration
|
||||
stack []profFrame
|
||||
stackSpace [8]profFrame // initial space for stack
|
||||
}
|
||||
|
||||
type profFrame struct {
|
||||
fn Callable // don't hold this live for too long (prevents GC of lambdas)
|
||||
pc uint32 // program counter (Starlark frames only)
|
||||
pos syntax.Position // position of pc within this frame
|
||||
}
|
||||
|
||||
// profile is the profiler goroutine.
|
||||
// It runs until StopProfiler is called.
|
||||
func profile(w io.Writer) {
|
||||
// Field numbers from pprof protocol.
|
||||
// See https://github.com/google/pprof/blob/master/proto/profile.proto
|
||||
const (
|
||||
Profile_sample_type = 1 // repeated ValueType
|
||||
Profile_sample = 2 // repeated Sample
|
||||
Profile_mapping = 3 // repeated Mapping
|
||||
Profile_location = 4 // repeated Location
|
||||
Profile_function = 5 // repeated Function
|
||||
Profile_string_table = 6 // repeated string
|
||||
Profile_time_nanos = 9 // int64
|
||||
Profile_duration_nanos = 10 // int64
|
||||
Profile_period_type = 11 // ValueType
|
||||
Profile_period = 12 // int64
|
||||
|
||||
ValueType_type = 1 // int64
|
||||
ValueType_unit = 2 // int64
|
||||
|
||||
Sample_location_id = 1 // repeated uint64
|
||||
Sample_value = 2 // repeated int64
|
||||
Sample_label = 3 // repeated Label
|
||||
|
||||
Label_key = 1 // int64
|
||||
Label_str = 2 // int64
|
||||
Label_num = 3 // int64
|
||||
Label_num_unit = 4 // int64
|
||||
|
||||
Location_id = 1 // uint64
|
||||
Location_mapping_id = 2 // uint64
|
||||
Location_address = 3 // uint64
|
||||
Location_line = 4 // repeated Line
|
||||
|
||||
Line_function_id = 1 // uint64
|
||||
Line_line = 2 // int64
|
||||
|
||||
Function_id = 1 // uint64
|
||||
Function_name = 2 // int64
|
||||
Function_system_name = 3 // int64
|
||||
Function_filename = 4 // int64
|
||||
Function_start_line = 5 // int64
|
||||
)
|
||||
|
||||
bufw := bufio.NewWriter(w) // write file in 4KB (not 240B flate-sized) chunks
|
||||
gz := gzip.NewWriter(bufw)
|
||||
enc := protoEncoder{w: gz}
|
||||
|
||||
// strings
|
||||
stringIndex := make(map[string]int64)
|
||||
str := func(s string) int64 {
|
||||
i, ok := stringIndex[s]
|
||||
if !ok {
|
||||
i = int64(len(stringIndex))
|
||||
enc.string(Profile_string_table, s)
|
||||
stringIndex[s] = i
|
||||
}
|
||||
return i
|
||||
}
|
||||
str("") // entry 0
|
||||
|
||||
// functions
|
||||
//
|
||||
// function returns the ID of a Callable for use in Line.FunctionId.
|
||||
// The ID is the same as the function's logical address,
|
||||
// which is supplied by the caller to avoid the need to recompute it.
|
||||
functionId := make(map[uintptr]uint64)
|
||||
function := func(fn Callable, addr uintptr) uint64 {
|
||||
id, ok := functionId[addr]
|
||||
if !ok {
|
||||
id = uint64(addr)
|
||||
|
||||
var pos syntax.Position
|
||||
if fn, ok := fn.(callableWithPosition); ok {
|
||||
pos = fn.Position()
|
||||
}
|
||||
|
||||
name := fn.Name()
|
||||
if name == "<toplevel>" {
|
||||
name = pos.Filename()
|
||||
}
|
||||
|
||||
nameIndex := str(name)
|
||||
|
||||
fun := new(bytes.Buffer)
|
||||
funenc := protoEncoder{w: fun}
|
||||
funenc.uint(Function_id, id)
|
||||
funenc.int(Function_name, nameIndex)
|
||||
funenc.int(Function_system_name, nameIndex)
|
||||
funenc.int(Function_filename, str(pos.Filename()))
|
||||
funenc.int(Function_start_line, int64(pos.Line))
|
||||
enc.bytes(Profile_function, fun.Bytes())
|
||||
|
||||
functionId[addr] = id
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// locations
|
||||
//
|
||||
// location returns the ID of the location denoted by fr.
|
||||
// For Starlark frames, this is the Frame pc.
|
||||
locationId := make(map[uintptr]uint64)
|
||||
location := func(fr profFrame) uint64 {
|
||||
fnAddr := profFuncAddr(fr.fn)
|
||||
|
||||
// For Starlark functions, the frame position
|
||||
// represents the current PC value.
|
||||
// Mix it into the low bits of the address.
|
||||
// This is super hacky and may result in collisions
|
||||
// in large functions or if functions are numerous.
|
||||
// TODO(adonovan): fix: try making this cleaner by treating
|
||||
// each bytecode segment as a Profile.Mapping.
|
||||
pcAddr := fnAddr
|
||||
if _, ok := fr.fn.(*Function); ok {
|
||||
pcAddr = (pcAddr << 16) ^ uintptr(fr.pc)
|
||||
}
|
||||
|
||||
id, ok := locationId[pcAddr]
|
||||
if !ok {
|
||||
id = uint64(pcAddr)
|
||||
|
||||
line := new(bytes.Buffer)
|
||||
lineenc := protoEncoder{w: line}
|
||||
lineenc.uint(Line_function_id, function(fr.fn, fnAddr))
|
||||
lineenc.int(Line_line, int64(fr.pos.Line))
|
||||
loc := new(bytes.Buffer)
|
||||
locenc := protoEncoder{w: loc}
|
||||
locenc.uint(Location_id, id)
|
||||
locenc.uint(Location_address, uint64(pcAddr))
|
||||
locenc.bytes(Location_line, line.Bytes())
|
||||
enc.bytes(Profile_location, loc.Bytes())
|
||||
|
||||
locationId[pcAddr] = id
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
wallNanos := new(bytes.Buffer)
|
||||
wnenc := protoEncoder{w: wallNanos}
|
||||
wnenc.int(ValueType_type, str("wall"))
|
||||
wnenc.int(ValueType_unit, str("nanoseconds"))
|
||||
|
||||
// informational fields of Profile
|
||||
enc.bytes(Profile_sample_type, wallNanos.Bytes())
|
||||
enc.int(Profile_period, quantum.Nanoseconds()) // magnitude of sampling period
|
||||
enc.bytes(Profile_period_type, wallNanos.Bytes()) // dimension and unit of period
|
||||
enc.int(Profile_time_nanos, time.Now().UnixNano()) // start (real) time of profile
|
||||
|
||||
startNano := nanotime()
|
||||
|
||||
// Read profile events from the channel
|
||||
// until it is closed by StopProfiler.
|
||||
for e := range profiler.events {
|
||||
sample := new(bytes.Buffer)
|
||||
sampleenc := protoEncoder{w: sample}
|
||||
sampleenc.int(Sample_value, e.time.Nanoseconds()) // wall nanoseconds
|
||||
for _, fr := range e.stack {
|
||||
sampleenc.uint(Sample_location_id, location(fr))
|
||||
}
|
||||
enc.bytes(Profile_sample, sample.Bytes())
|
||||
}
|
||||
|
||||
endNano := nanotime()
|
||||
enc.int(Profile_duration_nanos, endNano-startNano)
|
||||
|
||||
err := gz.Close() // Close reports any prior write error
|
||||
if flushErr := bufw.Flush(); err == nil {
|
||||
err = flushErr
|
||||
}
|
||||
profiler.done <- err
|
||||
}
|
||||
|
||||
// nanotime returns the time in nanoseconds since epoch.
|
||||
// It is implemented by runtime.nanotime using the linkname hack;
|
||||
// runtime.nanotime is defined for all OSs/ARCHS and uses the
|
||||
// monotonic system clock, which there is no portable way to access.
|
||||
// Should that function ever go away, these alternatives exist:
|
||||
//
|
||||
// // POSIX only. REALTIME not MONOTONIC. 17ns.
|
||||
// var tv syscall.Timeval
|
||||
// syscall.Gettimeofday(&tv) // can't fail
|
||||
// return tv.Nano()
|
||||
//
|
||||
// // Portable. REALTIME not MONOTONIC. 46ns.
|
||||
// return time.Now().Nanoseconds()
|
||||
//
|
||||
// // POSIX only. Adds a dependency.
|
||||
// import "golang.org/x/sys/unix"
|
||||
// var ts unix.Timespec
|
||||
// unix.ClockGettime(CLOCK_MONOTONIC, &ts) // can't fail
|
||||
// return unix.TimespecToNsec(ts)
|
||||
//
|
||||
//go:linkname nanotime runtime.nanotime
|
||||
func nanotime() int64
|
||||
|
||||
// profFuncAddr returns the canonical "address"
|
||||
// of a Callable for use by the profiler.
|
||||
func profFuncAddr(fn Callable) uintptr {
|
||||
switch fn := fn.(type) {
|
||||
case *Builtin:
|
||||
return reflect.ValueOf(fn.fn).Pointer()
|
||||
case *Function:
|
||||
return uintptr(unsafe.Pointer(fn.funcode))
|
||||
}
|
||||
|
||||
// User-defined callable types are typically of
|
||||
// of kind pointer-to-struct. Handle them specially.
|
||||
if v := reflect.ValueOf(fn); v.Type().Kind() == reflect.Ptr {
|
||||
return v.Pointer()
|
||||
}
|
||||
|
||||
// Address zero is reserved by the protocol.
|
||||
// Use 1 for callables we don't recognize.
|
||||
log.Printf("Starlark profiler: no address for Callable %T", fn)
|
||||
return 1
|
||||
}
|
||||
|
||||
// We encode the protocol message by hand to avoid making
|
||||
// the interpreter depend on both github.com/google/pprof
|
||||
// and github.com/golang/protobuf.
|
||||
//
|
||||
// This also avoids the need to materialize a protocol message object
|
||||
// tree of unbounded size and serialize it all at the end.
|
||||
// The pprof format appears to have been designed to
|
||||
// permit streaming implementations such as this one.
|
||||
//
|
||||
// See https://developers.google.com/protocol-buffers/docs/encoding.
|
||||
type protoEncoder struct {
|
||||
w io.Writer // *bytes.Buffer or *gzip.Writer
|
||||
tmp [binary.MaxVarintLen64]byte
|
||||
}
|
||||
|
||||
func (e *protoEncoder) uvarint(x uint64) {
|
||||
n := binary.PutUvarint(e.tmp[:], x)
|
||||
e.w.Write(e.tmp[:n])
|
||||
}
|
||||
|
||||
func (e *protoEncoder) tag(field, wire uint) {
|
||||
e.uvarint(uint64(field<<3 | wire))
|
||||
}
|
||||
|
||||
func (e *protoEncoder) string(field uint, s string) {
|
||||
e.tag(field, 2) // length-delimited
|
||||
e.uvarint(uint64(len(s)))
|
||||
io.WriteString(e.w, s)
|
||||
}
|
||||
|
||||
func (e *protoEncoder) bytes(field uint, b []byte) {
|
||||
e.tag(field, 2) // length-delimited
|
||||
e.uvarint(uint64(len(b)))
|
||||
e.w.Write(b)
|
||||
}
|
||||
|
||||
func (e *protoEncoder) uint(field uint, x uint64) {
|
||||
e.tag(field, 0) // varint
|
||||
e.uvarint(x)
|
||||
}
|
||||
|
||||
func (e *protoEncoder) int(field uint, x int64) {
|
||||
e.tag(field, 0) // varint
|
||||
e.uvarint(uint64(x))
|
||||
}
|
||||
258
vendor/go.starlark.net/starlark/unpack.go
generated
vendored
Normal file
258
vendor/go.starlark.net/starlark/unpack.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
package starlark
|
||||
|
||||
// This file defines the Unpack helper functions used by
|
||||
// built-in functions to interpret their call arguments.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UnpackArgs unpacks the positional and keyword arguments into the
|
||||
// supplied parameter variables. pairs is an alternating list of names
|
||||
// and pointers to variables.
|
||||
//
|
||||
// If the variable is a bool, int, string, *List, *Dict, Callable,
|
||||
// Iterable, or user-defined implementation of Value,
|
||||
// UnpackArgs performs the appropriate type check.
|
||||
// An int uses the AsInt32 check.
|
||||
// If the parameter name ends with "?",
|
||||
// it and all following parameters are optional.
|
||||
//
|
||||
// If the variable implements Value, UnpackArgs may call
|
||||
// its Type() method while constructing the error message.
|
||||
//
|
||||
// Beware: an optional *List, *Dict, Callable, Iterable, or Value variable that is
|
||||
// not assigned is not a valid Starlark Value, so the caller must
|
||||
// explicitly handle such cases by interpreting nil as None or some
|
||||
// computed default.
|
||||
func UnpackArgs(fnname string, args Tuple, kwargs []Tuple, pairs ...interface{}) error {
|
||||
nparams := len(pairs) / 2
|
||||
var defined intset
|
||||
defined.init(nparams)
|
||||
|
||||
paramName := func(x interface{}) string { // (no free variables)
|
||||
name := x.(string)
|
||||
if name[len(name)-1] == '?' {
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// positional arguments
|
||||
if len(args) > nparams {
|
||||
return fmt.Errorf("%s: got %d arguments, want at most %d",
|
||||
fnname, len(args), nparams)
|
||||
}
|
||||
for i, arg := range args {
|
||||
defined.set(i)
|
||||
if err := unpackOneArg(arg, pairs[2*i+1]); err != nil {
|
||||
name := paramName(pairs[2*i])
|
||||
return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// keyword arguments
|
||||
kwloop:
|
||||
for _, item := range kwargs {
|
||||
name, arg := item[0].(String), item[1]
|
||||
for i := 0; i < nparams; i++ {
|
||||
if paramName(pairs[2*i]) == string(name) {
|
||||
// found it
|
||||
if defined.set(i) {
|
||||
return fmt.Errorf("%s: got multiple values for keyword argument %s",
|
||||
fnname, name)
|
||||
}
|
||||
ptr := pairs[2*i+1]
|
||||
if err := unpackOneArg(arg, ptr); err != nil {
|
||||
return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err)
|
||||
}
|
||||
continue kwloop
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s: unexpected keyword argument %s", fnname, name)
|
||||
}
|
||||
|
||||
// Check that all non-optional parameters are defined.
|
||||
// (We needn't check the first len(args).)
|
||||
for i := len(args); i < nparams; i++ {
|
||||
name := pairs[2*i].(string)
|
||||
if strings.HasSuffix(name, "?") {
|
||||
break // optional
|
||||
}
|
||||
if !defined.get(i) {
|
||||
return fmt.Errorf("%s: missing argument for %s", fnname, name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnpackPositionalArgs unpacks the positional arguments into
|
||||
// corresponding variables. Each element of vars is a pointer; see
|
||||
// UnpackArgs for allowed types and conversions.
|
||||
//
|
||||
// UnpackPositionalArgs reports an error if the number of arguments is
|
||||
// less than min or greater than len(vars), if kwargs is nonempty, or if
|
||||
// any conversion fails.
|
||||
func UnpackPositionalArgs(fnname string, args Tuple, kwargs []Tuple, min int, vars ...interface{}) error {
|
||||
if len(kwargs) > 0 {
|
||||
return fmt.Errorf("%s: unexpected keyword arguments", fnname)
|
||||
}
|
||||
max := len(vars)
|
||||
if len(args) < min {
|
||||
var atleast string
|
||||
if min < max {
|
||||
atleast = "at least "
|
||||
}
|
||||
return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atleast, min)
|
||||
}
|
||||
if len(args) > max {
|
||||
var atmost string
|
||||
if max > min {
|
||||
atmost = "at most "
|
||||
}
|
||||
return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atmost, max)
|
||||
}
|
||||
for i, arg := range args {
|
||||
if err := unpackOneArg(arg, vars[i]); err != nil {
|
||||
return fmt.Errorf("%s: for parameter %d: %s", fnname, i+1, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unpackOneArg(v Value, ptr interface{}) error {
|
||||
// On failure, don't clobber *ptr.
|
||||
switch ptr := ptr.(type) {
|
||||
case *Value:
|
||||
*ptr = v
|
||||
case *string:
|
||||
s, ok := AsString(v)
|
||||
if !ok {
|
||||
return fmt.Errorf("got %s, want string", v.Type())
|
||||
}
|
||||
*ptr = s
|
||||
case *bool:
|
||||
b, ok := v.(Bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("got %s, want bool", v.Type())
|
||||
}
|
||||
*ptr = bool(b)
|
||||
case *int:
|
||||
i, err := AsInt32(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*ptr = i
|
||||
case **List:
|
||||
list, ok := v.(*List)
|
||||
if !ok {
|
||||
return fmt.Errorf("got %s, want list", v.Type())
|
||||
}
|
||||
*ptr = list
|
||||
case **Dict:
|
||||
dict, ok := v.(*Dict)
|
||||
if !ok {
|
||||
return fmt.Errorf("got %s, want dict", v.Type())
|
||||
}
|
||||
*ptr = dict
|
||||
case *Callable:
|
||||
f, ok := v.(Callable)
|
||||
if !ok {
|
||||
return fmt.Errorf("got %s, want callable", v.Type())
|
||||
}
|
||||
*ptr = f
|
||||
case *Iterable:
|
||||
it, ok := v.(Iterable)
|
||||
if !ok {
|
||||
return fmt.Errorf("got %s, want iterable", v.Type())
|
||||
}
|
||||
*ptr = it
|
||||
default:
|
||||
// v must have type *V, where V is some subtype of starlark.Value.
|
||||
ptrv := reflect.ValueOf(ptr)
|
||||
if ptrv.Kind() != reflect.Ptr {
|
||||
log.Panicf("internal error: not a pointer: %T", ptr)
|
||||
}
|
||||
paramVar := ptrv.Elem()
|
||||
if !reflect.TypeOf(v).AssignableTo(paramVar.Type()) {
|
||||
// The value is not assignable to the variable.
|
||||
|
||||
// Detect a possible bug in the Go program that called Unpack:
|
||||
// If the variable *ptr is not a subtype of Value,
|
||||
// no value of v can possibly work.
|
||||
if !paramVar.Type().AssignableTo(reflect.TypeOf(new(Value)).Elem()) {
|
||||
log.Panicf("pointer element type does not implement Value: %T", ptr)
|
||||
}
|
||||
|
||||
// Report Starlark dynamic type error.
|
||||
//
|
||||
// We prefer the Starlark Value.Type name over
|
||||
// its Go reflect.Type name, but calling the
|
||||
// Value.Type method on the variable is not safe
|
||||
// in general. If the variable is an interface,
|
||||
// the call will fail. Even if the variable has
|
||||
// a concrete type, it might not be safe to call
|
||||
// Type() on a zero instance. Thus we must use
|
||||
// recover.
|
||||
|
||||
// Default to Go reflect.Type name
|
||||
paramType := paramVar.Type().String()
|
||||
|
||||
// Attempt to call Value.Type method.
|
||||
func() {
|
||||
defer func() { recover() }()
|
||||
paramType = paramVar.MethodByName("Type").Call(nil)[0].String()
|
||||
}()
|
||||
return fmt.Errorf("got %s, want %s", v.Type(), paramType)
|
||||
}
|
||||
paramVar.Set(reflect.ValueOf(v))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type intset struct {
|
||||
small uint64 // bitset, used if n < 64
|
||||
large map[int]bool // set, used if n >= 64
|
||||
}
|
||||
|
||||
func (is *intset) init(n int) {
|
||||
if n >= 64 {
|
||||
is.large = make(map[int]bool)
|
||||
}
|
||||
}
|
||||
|
||||
func (is *intset) set(i int) (prev bool) {
|
||||
if is.large == nil {
|
||||
prev = is.small&(1<<uint(i)) != 0
|
||||
is.small |= 1 << uint(i)
|
||||
} else {
|
||||
prev = is.large[i]
|
||||
is.large[i] = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (is *intset) get(i int) bool {
|
||||
if is.large == nil {
|
||||
return is.small&(1<<uint(i)) != 0
|
||||
}
|
||||
return is.large[i]
|
||||
}
|
||||
|
||||
func (is *intset) len() int {
|
||||
if is.large == nil {
|
||||
// Suboptimal, but used only for error reporting.
|
||||
len := 0
|
||||
for i := 0; i < 64; i++ {
|
||||
if is.small&(1<<uint(i)) != 0 {
|
||||
len++
|
||||
}
|
||||
}
|
||||
return len
|
||||
}
|
||||
return len(is.large)
|
||||
}
|
||||
1293
vendor/go.starlark.net/starlark/value.go
generated
vendored
Normal file
1293
vendor/go.starlark.net/starlark/value.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
43
vendor/go.starlark.net/starlarkstruct/module.go
generated
vendored
Normal file
43
vendor/go.starlark.net/starlarkstruct/module.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package starlarkstruct
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.starlark.net/starlark"
|
||||
)
|
||||
|
||||
// A Module is a named collection of values,
|
||||
// typically a suite of functions imported by a load statement.
|
||||
//
|
||||
// It differs from Struct primarily in that its string representation
|
||||
// does not enumerate its fields.
|
||||
type Module struct {
|
||||
Name string
|
||||
Members starlark.StringDict
|
||||
}
|
||||
|
||||
var _ starlark.HasAttrs = (*Module)(nil)
|
||||
|
||||
func (m *Module) Attr(name string) (starlark.Value, error) { return m.Members[name], nil }
|
||||
func (m *Module) AttrNames() []string { return m.Members.Keys() }
|
||||
func (m *Module) Freeze() { m.Members.Freeze() }
|
||||
func (m *Module) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", m.Type()) }
|
||||
func (m *Module) String() string { return fmt.Sprintf("<module %q>", m.Name) }
|
||||
func (m *Module) Truth() starlark.Bool { return true }
|
||||
func (m *Module) Type() string { return "module" }
|
||||
|
||||
// MakeModule may be used as the implementation of a Starlark built-in
|
||||
// function, module(name, **kwargs). It returns a new module with the
|
||||
// specified name and members.
|
||||
func MakeModule(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var name string
|
||||
if err := starlark.UnpackPositionalArgs(b.Name(), args, nil, 1, &name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
members := make(starlark.StringDict, len(kwargs))
|
||||
for _, kwarg := range kwargs {
|
||||
k := string(kwarg[0].(starlark.String))
|
||||
members[k] = kwarg[1]
|
||||
}
|
||||
return &Module{name, members}, nil
|
||||
}
|
||||
281
vendor/go.starlark.net/starlarkstruct/struct.go
generated
vendored
Normal file
281
vendor/go.starlark.net/starlarkstruct/struct.go
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
// Copyright 2017 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package starlarkstruct defines the Starlark types 'struct' and
|
||||
// 'module', both optional language extensions.
|
||||
//
|
||||
package starlarkstruct // import "go.starlark.net/starlarkstruct"
|
||||
|
||||
// It is tempting to introduce a variant of Struct that is a wrapper
|
||||
// around a Go struct value, for stronger typing guarantees and more
|
||||
// efficient and convenient field lookup. However:
|
||||
// 1) all fields of Starlark structs are optional, so we cannot represent
|
||||
// them using more specific types such as String, Int, *Depset, and
|
||||
// *File, as such types give no way to represent missing fields.
|
||||
// 2) the efficiency gain of direct struct field access is rather
|
||||
// marginal: finding the index of a field by binary searching on the
|
||||
// sorted list of field names is quite fast compared to the other
|
||||
// overheads.
|
||||
// 3) the gains in compactness and spatial locality are also rather
|
||||
// marginal: the array behind the []entry slice is (due to field name
|
||||
// strings) only a factor of 2 larger than the corresponding Go struct
|
||||
// would be, and, like the Go struct, requires only a single allocation.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/syntax"
|
||||
)
|
||||
|
||||
// Make is the implementation of a built-in function that instantiates
|
||||
// an immutable struct from the specified keyword arguments.
|
||||
//
|
||||
// An application can add 'struct' to the Starlark environment like so:
|
||||
//
|
||||
// globals := starlark.StringDict{
|
||||
// "struct": starlark.NewBuiltin("struct", starlarkstruct.Make),
|
||||
// }
|
||||
//
|
||||
func Make(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if len(args) > 0 {
|
||||
return nil, fmt.Errorf("struct: unexpected positional arguments")
|
||||
}
|
||||
return FromKeywords(Default, kwargs), nil
|
||||
}
|
||||
|
||||
// FromKeywords returns a new struct instance whose fields are specified by the
|
||||
// key/value pairs in kwargs. (Each kwargs[i][0] must be a starlark.String.)
|
||||
func FromKeywords(constructor starlark.Value, kwargs []starlark.Tuple) *Struct {
|
||||
if constructor == nil {
|
||||
panic("nil constructor")
|
||||
}
|
||||
s := &Struct{
|
||||
constructor: constructor,
|
||||
entries: make(entries, 0, len(kwargs)),
|
||||
}
|
||||
for _, kwarg := range kwargs {
|
||||
k := string(kwarg[0].(starlark.String))
|
||||
v := kwarg[1]
|
||||
s.entries = append(s.entries, entry{k, v})
|
||||
}
|
||||
sort.Sort(s.entries)
|
||||
return s
|
||||
}
|
||||
|
||||
// FromStringDict returns a whose elements are those of d.
|
||||
// The constructor parameter specifies the constructor; use Default for an ordinary struct.
|
||||
func FromStringDict(constructor starlark.Value, d starlark.StringDict) *Struct {
|
||||
if constructor == nil {
|
||||
panic("nil constructor")
|
||||
}
|
||||
s := &Struct{
|
||||
constructor: constructor,
|
||||
entries: make(entries, 0, len(d)),
|
||||
}
|
||||
for k, v := range d {
|
||||
s.entries = append(s.entries, entry{k, v})
|
||||
}
|
||||
sort.Sort(s.entries)
|
||||
return s
|
||||
}
|
||||
|
||||
// Struct is an immutable Starlark type that maps field names to values.
|
||||
// It is not iterable and does not support len.
|
||||
//
|
||||
// A struct has a constructor, a distinct value that identifies a class
|
||||
// of structs, and which appears in the struct's string representation.
|
||||
//
|
||||
// Operations such as x+y fail if the constructors of the two operands
|
||||
// are not equal.
|
||||
//
|
||||
// The default constructor, Default, is the string "struct", but
|
||||
// clients may wish to 'brand' structs for their own purposes.
|
||||
// The constructor value appears in the printed form of the value,
|
||||
// and is accessible using the Constructor method.
|
||||
//
|
||||
// Use Attr to access its fields and AttrNames to enumerate them.
|
||||
type Struct struct {
|
||||
constructor starlark.Value
|
||||
entries entries // sorted by name
|
||||
}
|
||||
|
||||
// Default is the default constructor for structs.
|
||||
// It is merely the string "struct".
|
||||
const Default = starlark.String("struct")
|
||||
|
||||
type entries []entry
|
||||
|
||||
func (a entries) Len() int { return len(a) }
|
||||
func (a entries) Less(i, j int) bool { return a[i].name < a[j].name }
|
||||
func (a entries) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type entry struct {
|
||||
name string
|
||||
value starlark.Value
|
||||
}
|
||||
|
||||
var (
|
||||
_ starlark.HasAttrs = (*Struct)(nil)
|
||||
_ starlark.HasBinary = (*Struct)(nil)
|
||||
)
|
||||
|
||||
// ToStringDict adds a name/value entry to d for each field of the struct.
|
||||
func (s *Struct) ToStringDict(d starlark.StringDict) {
|
||||
for _, e := range s.entries {
|
||||
d[e.name] = e.value
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Struct) String() string {
|
||||
buf := new(strings.Builder)
|
||||
if s.constructor == Default {
|
||||
// NB: The Java implementation always prints struct
|
||||
// even for Bazel provider instances.
|
||||
buf.WriteString("struct") // avoid String()'s quotation
|
||||
} else {
|
||||
buf.WriteString(s.constructor.String())
|
||||
}
|
||||
buf.WriteByte('(')
|
||||
for i, e := range s.entries {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(e.name)
|
||||
buf.WriteString(" = ")
|
||||
buf.WriteString(e.value.String())
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Constructor returns the constructor used to create this struct.
|
||||
func (s *Struct) Constructor() starlark.Value { return s.constructor }
|
||||
|
||||
func (s *Struct) Type() string { return "struct" }
|
||||
func (s *Struct) Truth() starlark.Bool { return true } // even when empty
|
||||
func (s *Struct) Hash() (uint32, error) {
|
||||
// Same algorithm as Tuple.hash, but with different primes.
|
||||
var x, m uint32 = 8731, 9839
|
||||
for _, e := range s.entries {
|
||||
namehash, _ := starlark.String(e.name).Hash()
|
||||
x = x ^ 3*namehash
|
||||
y, err := e.value.Hash()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
x = x ^ y*m
|
||||
m += 7349
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
func (s *Struct) Freeze() {
|
||||
for _, e := range s.entries {
|
||||
e.value.Freeze()
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Struct) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
|
||||
if y, ok := y.(*Struct); ok && op == syntax.PLUS {
|
||||
if side == starlark.Right {
|
||||
x, y = y, x
|
||||
}
|
||||
|
||||
if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
|
||||
return nil, fmt.Errorf("in %s + %s: error comparing constructors: %v",
|
||||
x.constructor, y.constructor, err)
|
||||
} else if !eq {
|
||||
return nil, fmt.Errorf("cannot add structs of different constructors: %s + %s",
|
||||
x.constructor, y.constructor)
|
||||
}
|
||||
|
||||
z := make(starlark.StringDict, x.len()+y.len())
|
||||
for _, e := range x.entries {
|
||||
z[e.name] = e.value
|
||||
}
|
||||
for _, e := range y.entries {
|
||||
z[e.name] = e.value
|
||||
}
|
||||
|
||||
return FromStringDict(x.constructor, z), nil
|
||||
}
|
||||
return nil, nil // unhandled
|
||||
}
|
||||
|
||||
// Attr returns the value of the specified field.
|
||||
func (s *Struct) Attr(name string) (starlark.Value, error) {
|
||||
// Binary search the entries.
|
||||
// This implementation is a specialization of
|
||||
// sort.Search that avoids dynamic dispatch.
|
||||
n := len(s.entries)
|
||||
i, j := 0, n
|
||||
for i < j {
|
||||
h := int(uint(i+j) >> 1)
|
||||
if s.entries[h].name < name {
|
||||
i = h + 1
|
||||
} else {
|
||||
j = h
|
||||
}
|
||||
}
|
||||
if i < n && s.entries[i].name == name {
|
||||
return s.entries[i].value, nil
|
||||
}
|
||||
|
||||
var ctor string
|
||||
if s.constructor != Default {
|
||||
ctor = s.constructor.String() + " "
|
||||
}
|
||||
return nil, starlark.NoSuchAttrError(
|
||||
fmt.Sprintf("%sstruct has no .%s attribute", ctor, name))
|
||||
}
|
||||
|
||||
func (s *Struct) len() int { return len(s.entries) }
|
||||
|
||||
// AttrNames returns a new sorted list of the struct fields.
|
||||
func (s *Struct) AttrNames() []string {
|
||||
names := make([]string, len(s.entries))
|
||||
for i, e := range s.entries {
|
||||
names[i] = e.name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (x *Struct) CompareSameType(op syntax.Token, y_ starlark.Value, depth int) (bool, error) {
|
||||
y := y_.(*Struct)
|
||||
switch op {
|
||||
case syntax.EQL:
|
||||
return structsEqual(x, y, depth)
|
||||
case syntax.NEQ:
|
||||
eq, err := structsEqual(x, y, depth)
|
||||
return !eq, err
|
||||
default:
|
||||
return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func structsEqual(x, y *Struct, depth int) (bool, error) {
|
||||
if x.len() != y.len() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
|
||||
return false, fmt.Errorf("error comparing struct constructors %v and %v: %v",
|
||||
x.constructor, y.constructor, err)
|
||||
} else if !eq {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for i, n := 0, x.len(); i < n; i++ {
|
||||
if x.entries[i].name != y.entries[i].name {
|
||||
return false, nil
|
||||
} else if eq, err := starlark.EqualDepth(x.entries[i].value, y.entries[i].value, depth-1); err != nil {
|
||||
return false, err
|
||||
} else if !eq {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
129
vendor/go.starlark.net/syntax/grammar.txt
generated
vendored
Normal file
129
vendor/go.starlark.net/syntax/grammar.txt
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
|
||||
Grammar of Starlark
|
||||
==================
|
||||
|
||||
File = {Statement | newline} eof .
|
||||
|
||||
Statement = DefStmt | IfStmt | ForStmt | WhileStmt | SimpleStmt .
|
||||
|
||||
DefStmt = 'def' identifier '(' [Parameters [',']] ')' ':' Suite .
|
||||
|
||||
Parameters = Parameter {',' Parameter}.
|
||||
|
||||
Parameter = identifier | identifier '=' Test | '*' | '*' identifier | '**' identifier .
|
||||
|
||||
IfStmt = 'if' Test ':' Suite {'elif' Test ':' Suite} ['else' ':' Suite] .
|
||||
|
||||
ForStmt = 'for' LoopVariables 'in' Expression ':' Suite .
|
||||
|
||||
WhileStmt = 'while' Test ':' Suite .
|
||||
|
||||
Suite = [newline indent {Statement} outdent] | SimpleStmt .
|
||||
|
||||
SimpleStmt = SmallStmt {';' SmallStmt} [';'] '\n' .
|
||||
# NOTE: '\n' optional at EOF
|
||||
|
||||
SmallStmt = ReturnStmt
|
||||
| BreakStmt | ContinueStmt | PassStmt
|
||||
| AssignStmt
|
||||
| ExprStmt
|
||||
| LoadStmt
|
||||
.
|
||||
|
||||
ReturnStmt = 'return' [Expression] .
|
||||
BreakStmt = 'break' .
|
||||
ContinueStmt = 'continue' .
|
||||
PassStmt = 'pass' .
|
||||
AssignStmt = Expression ('=' | '+=' | '-=' | '*=' | '/=' | '//=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') Expression .
|
||||
ExprStmt = Expression .
|
||||
|
||||
LoadStmt = 'load' '(' string {',' [identifier '='] string} [','] ')' .
|
||||
|
||||
Test = LambdaExpr
|
||||
| IfExpr
|
||||
| PrimaryExpr
|
||||
| UnaryExpr
|
||||
| BinaryExpr
|
||||
.
|
||||
|
||||
LambdaExpr = 'lambda' [Parameters] ':' Test .
|
||||
|
||||
IfExpr = Test 'if' Test 'else' Test .
|
||||
|
||||
PrimaryExpr = Operand
|
||||
| PrimaryExpr DotSuffix
|
||||
| PrimaryExpr CallSuffix
|
||||
| PrimaryExpr SliceSuffix
|
||||
.
|
||||
|
||||
Operand = identifier
|
||||
| int | float | string
|
||||
| ListExpr | ListComp
|
||||
| DictExpr | DictComp
|
||||
| '(' [Expression [',']] ')'
|
||||
| ('-' | '+') PrimaryExpr
|
||||
.
|
||||
|
||||
DotSuffix = '.' identifier .
|
||||
CallSuffix = '(' [Arguments [',']] ')' .
|
||||
SliceSuffix = '[' [Expression] [':' Test [':' Test]] ']' .
|
||||
|
||||
Arguments = Argument {',' Argument} .
|
||||
Argument = Test | identifier '=' Test | '*' Test | '**' Test .
|
||||
|
||||
ListExpr = '[' [Expression [',']] ']' .
|
||||
ListComp = '[' Test {CompClause} ']'.
|
||||
|
||||
DictExpr = '{' [Entries [',']] '}' .
|
||||
DictComp = '{' Entry {CompClause} '}' .
|
||||
Entries = Entry {',' Entry} .
|
||||
Entry = Test ':' Test .
|
||||
|
||||
CompClause = 'for' LoopVariables 'in' Test | 'if' Test .
|
||||
|
||||
UnaryExpr = 'not' Test .
|
||||
|
||||
BinaryExpr = Test {Binop Test} .
|
||||
|
||||
Binop = 'or'
|
||||
| 'and'
|
||||
| '==' | '!=' | '<' | '>' | '<=' | '>=' | 'in' | 'not' 'in'
|
||||
| '|'
|
||||
| '^'
|
||||
| '&'
|
||||
| '-' | '+'
|
||||
| '*' | '%' | '/' | '//'
|
||||
.
|
||||
|
||||
Expression = Test {',' Test} .
|
||||
# NOTE: trailing comma permitted only when within [...] or (...).
|
||||
|
||||
LoopVariables = PrimaryExpr {',' PrimaryExpr} .
|
||||
|
||||
|
||||
# Notation (similar to Go spec):
|
||||
- lowercase and 'quoted' items are lexical tokens.
|
||||
- Capitalized names denote grammar productions.
|
||||
- (...) implies grouping
|
||||
- x | y means either x or y.
|
||||
- [x] means x is optional
|
||||
- {x} means x is repeated zero or more times
|
||||
- The end of each declaration is marked with a period.
|
||||
|
||||
# Tokens
|
||||
- spaces: newline, eof, indent, outdent.
|
||||
- identifier.
|
||||
- literals: string, int, float.
|
||||
- plus all quoted tokens such as '+=', 'return'.
|
||||
|
||||
# Notes:
|
||||
- Ambiguity is resolved using operator precedence.
|
||||
- The grammar does not enforce the legal order of params and args,
|
||||
nor that the first compclause must be a 'for'.
|
||||
|
||||
TODO:
|
||||
- explain how the lexer generates indent, outdent, and newline tokens.
|
||||
- why is unary NOT separated from unary - and +?
|
||||
- the grammar is (mostly) in LL(1) style so, for example,
|
||||
dot expressions are formed suffixes, not complete expressions,
|
||||
which makes the spec harder to read. Reorganize into non-LL(1) form?
|
||||
1029
vendor/go.starlark.net/syntax/parse.go
generated
vendored
Normal file
1029
vendor/go.starlark.net/syntax/parse.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
269
vendor/go.starlark.net/syntax/quote.go
generated
vendored
Normal file
269
vendor/go.starlark.net/syntax/quote.go
generated
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
// Copyright 2017 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package syntax
|
||||
|
||||
// Starlark quoted string utilities.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// unesc maps single-letter chars following \ to their actual values.
|
||||
var unesc = [256]byte{
|
||||
'a': '\a',
|
||||
'b': '\b',
|
||||
'f': '\f',
|
||||
'n': '\n',
|
||||
'r': '\r',
|
||||
't': '\t',
|
||||
'v': '\v',
|
||||
'\\': '\\',
|
||||
'\'': '\'',
|
||||
'"': '"',
|
||||
}
|
||||
|
||||
// esc maps escape-worthy bytes to the char that should follow \.
|
||||
var esc = [256]byte{
|
||||
'\a': 'a',
|
||||
'\b': 'b',
|
||||
'\f': 'f',
|
||||
'\n': 'n',
|
||||
'\r': 'r',
|
||||
'\t': 't',
|
||||
'\v': 'v',
|
||||
'\\': '\\',
|
||||
'\'': '\'',
|
||||
'"': '"',
|
||||
}
|
||||
|
||||
// notEsc is a list of characters that can follow a \ in a string value
|
||||
// without having to escape the \. That is, since ( is in this list, we
|
||||
// quote the Go string "foo\\(bar" as the Python literal "foo\(bar".
|
||||
// This really does happen in BUILD files, especially in strings
|
||||
// being used as shell arguments containing regular expressions.
|
||||
const notEsc = " !#$%&()*+,-./:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~"
|
||||
|
||||
// unquote unquotes the quoted string, returning the actual
|
||||
// string value, whether the original was triple-quoted, and
|
||||
// an error describing invalid input.
|
||||
func unquote(quoted string) (s string, triple bool, err error) {
|
||||
// Check for raw prefix: means don't interpret the inner \.
|
||||
raw := false
|
||||
if strings.HasPrefix(quoted, "r") {
|
||||
raw = true
|
||||
quoted = quoted[1:]
|
||||
}
|
||||
|
||||
if len(quoted) < 2 {
|
||||
err = fmt.Errorf("string literal too short")
|
||||
return
|
||||
}
|
||||
|
||||
if quoted[0] != '"' && quoted[0] != '\'' || quoted[0] != quoted[len(quoted)-1] {
|
||||
err = fmt.Errorf("string literal has invalid quotes")
|
||||
return
|
||||
}
|
||||
|
||||
// Check for triple quoted string.
|
||||
quote := quoted[0]
|
||||
if len(quoted) >= 6 && quoted[1] == quote && quoted[2] == quote && quoted[:3] == quoted[len(quoted)-3:] {
|
||||
triple = true
|
||||
quoted = quoted[3 : len(quoted)-3]
|
||||
} else {
|
||||
quoted = quoted[1 : len(quoted)-1]
|
||||
}
|
||||
|
||||
// Now quoted is the quoted data, but no quotes.
|
||||
// If we're in raw mode or there are no escapes or
|
||||
// carriage returns, we're done.
|
||||
var unquoteChars string
|
||||
if raw {
|
||||
unquoteChars = "\r"
|
||||
} else {
|
||||
unquoteChars = "\\\r"
|
||||
}
|
||||
if !strings.ContainsAny(quoted, unquoteChars) {
|
||||
s = quoted
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise process quoted string.
|
||||
// Each iteration processes one escape sequence along with the
|
||||
// plain text leading up to it.
|
||||
buf := new(strings.Builder)
|
||||
for {
|
||||
// Remove prefix before escape sequence.
|
||||
i := strings.IndexAny(quoted, unquoteChars)
|
||||
if i < 0 {
|
||||
i = len(quoted)
|
||||
}
|
||||
buf.WriteString(quoted[:i])
|
||||
quoted = quoted[i:]
|
||||
|
||||
if len(quoted) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Process carriage return.
|
||||
if quoted[0] == '\r' {
|
||||
buf.WriteByte('\n')
|
||||
if len(quoted) > 1 && quoted[1] == '\n' {
|
||||
quoted = quoted[2:]
|
||||
} else {
|
||||
quoted = quoted[1:]
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Process escape sequence.
|
||||
if len(quoted) == 1 {
|
||||
err = fmt.Errorf(`truncated escape sequence \`)
|
||||
return
|
||||
}
|
||||
|
||||
switch quoted[1] {
|
||||
default:
|
||||
// In Python, if \z (for some byte z) is not a known escape sequence
|
||||
// then it appears as literal text in the string.
|
||||
buf.WriteString(quoted[:2])
|
||||
quoted = quoted[2:]
|
||||
|
||||
case '\n':
|
||||
// Ignore the escape and the line break.
|
||||
quoted = quoted[2:]
|
||||
|
||||
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '\'', '"':
|
||||
// One-char escape
|
||||
buf.WriteByte(unesc[quoted[1]])
|
||||
quoted = quoted[2:]
|
||||
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
// Octal escape, up to 3 digits.
|
||||
n := int(quoted[1] - '0')
|
||||
quoted = quoted[2:]
|
||||
for i := 1; i < 3; i++ {
|
||||
if len(quoted) == 0 || quoted[0] < '0' || '7' < quoted[0] {
|
||||
break
|
||||
}
|
||||
n = n*8 + int(quoted[0]-'0')
|
||||
quoted = quoted[1:]
|
||||
}
|
||||
if n >= 256 {
|
||||
// NOTE: Python silently discards the high bit,
|
||||
// so that '\541' == '\141' == 'a'.
|
||||
// Let's see if we can avoid doing that in BUILD files.
|
||||
err = fmt.Errorf(`invalid escape sequence \%03o`, n)
|
||||
return
|
||||
}
|
||||
buf.WriteByte(byte(n))
|
||||
|
||||
case 'x':
|
||||
// Hexadecimal escape, exactly 2 digits.
|
||||
if len(quoted) < 4 {
|
||||
err = fmt.Errorf(`truncated escape sequence %s`, quoted)
|
||||
return
|
||||
}
|
||||
n, err1 := strconv.ParseUint(quoted[2:4], 16, 0)
|
||||
if err1 != nil {
|
||||
err = fmt.Errorf(`invalid escape sequence %s`, quoted[:4])
|
||||
return
|
||||
}
|
||||
buf.WriteByte(byte(n))
|
||||
quoted = quoted[4:]
|
||||
}
|
||||
}
|
||||
|
||||
s = buf.String()
|
||||
return
|
||||
}
|
||||
|
||||
// indexByte returns the index of the first instance of b in s, or else -1.
|
||||
func indexByte(s string, b byte) int {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == b {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// hex is a list of the hexadecimal digits, for use in quoting.
|
||||
// We always print lower-case hexadecimal.
|
||||
const hex = "0123456789abcdef"
|
||||
|
||||
// quote returns the quoted form of the string value "x".
|
||||
// If triple is true, quote uses the triple-quoted form """x""".
|
||||
func quote(unquoted string, triple bool) string {
|
||||
q := `"`
|
||||
if triple {
|
||||
q = `"""`
|
||||
}
|
||||
|
||||
buf := new(strings.Builder)
|
||||
buf.WriteString(q)
|
||||
|
||||
for i := 0; i < len(unquoted); i++ {
|
||||
c := unquoted[i]
|
||||
if c == '"' && triple && (i+1 < len(unquoted) && unquoted[i+1] != '"' || i+2 < len(unquoted) && unquoted[i+2] != '"') {
|
||||
// Can pass up to two quotes through, because they are followed by a non-quote byte.
|
||||
buf.WriteByte(c)
|
||||
if i+1 < len(unquoted) && unquoted[i+1] == '"' {
|
||||
buf.WriteByte(c)
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if triple && c == '\n' {
|
||||
// Can allow newline in triple-quoted string.
|
||||
buf.WriteByte(c)
|
||||
continue
|
||||
}
|
||||
if c == '\'' {
|
||||
// Can allow ' since we always use ".
|
||||
buf.WriteByte(c)
|
||||
continue
|
||||
}
|
||||
if c == '\\' {
|
||||
if i+1 < len(unquoted) && indexByte(notEsc, unquoted[i+1]) >= 0 {
|
||||
// Can pass \ through when followed by a byte that
|
||||
// known not to be a valid escape sequence and also
|
||||
// that does not trigger an escape sequence of its own.
|
||||
// Use this, because various BUILD files do.
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(unquoted[i+1])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
}
|
||||
if esc[c] != 0 {
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(esc[c])
|
||||
continue
|
||||
}
|
||||
if c < 0x20 || c >= 0x80 {
|
||||
// BUILD files are supposed to be Latin-1, so escape all control and high bytes.
|
||||
// I'd prefer to use \x here, but Blaze does not implement
|
||||
// \x in quoted strings (b/7272572).
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(hex[c>>6]) // actually octal but reusing hex digits 0-7.
|
||||
buf.WriteByte(hex[(c>>3)&7])
|
||||
buf.WriteByte(hex[c&7])
|
||||
/*
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('x')
|
||||
buf.WriteByte(hex[c>>4])
|
||||
buf.WriteByte(hex[c&0xF])
|
||||
*/
|
||||
continue
|
||||
}
|
||||
buf.WriteByte(c)
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteString(q)
|
||||
return buf.String()
|
||||
}
|
||||
1089
vendor/go.starlark.net/syntax/scan.go
generated
vendored
Normal file
1089
vendor/go.starlark.net/syntax/scan.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
529
vendor/go.starlark.net/syntax/syntax.go
generated
vendored
Normal file
529
vendor/go.starlark.net/syntax/syntax.go
generated
vendored
Normal file
@@ -0,0 +1,529 @@
|
||||
// Copyright 2017 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package syntax provides a Starlark parser and abstract syntax tree.
|
||||
package syntax // import "go.starlark.net/syntax"
|
||||
|
||||
// A Node is a node in a Starlark syntax tree.
|
||||
type Node interface {
|
||||
// Span returns the start and end position of the expression.
|
||||
Span() (start, end Position)
|
||||
|
||||
// Comments returns the comments associated with this node.
|
||||
// It returns nil if RetainComments was not specified during parsing,
|
||||
// or if AllocComments was not called.
|
||||
Comments() *Comments
|
||||
|
||||
// AllocComments allocates a new Comments node if there was none.
|
||||
// This makes possible to add new comments using Comments() method.
|
||||
AllocComments()
|
||||
}
|
||||
|
||||
// A Comment represents a single # comment.
|
||||
type Comment struct {
|
||||
Start Position
|
||||
Text string // without trailing newline
|
||||
}
|
||||
|
||||
// Comments collects the comments associated with an expression.
|
||||
type Comments struct {
|
||||
Before []Comment // whole-line comments before this expression
|
||||
Suffix []Comment // end-of-line comments after this expression (up to 1)
|
||||
|
||||
// For top-level expressions only, After lists whole-line
|
||||
// comments following the expression.
|
||||
After []Comment
|
||||
}
|
||||
|
||||
// A commentsRef is a possibly-nil reference to a set of comments.
|
||||
// A commentsRef is embedded in each type of syntax node,
|
||||
// and provides its Comments and AllocComments methods.
|
||||
type commentsRef struct{ ref *Comments }
|
||||
|
||||
// Comments returns the comments associated with a syntax node,
|
||||
// or nil if AllocComments has not yet been called.
|
||||
func (cr commentsRef) Comments() *Comments { return cr.ref }
|
||||
|
||||
// AllocComments enables comments to be associated with a syntax node.
|
||||
func (cr *commentsRef) AllocComments() {
|
||||
if cr.ref == nil {
|
||||
cr.ref = new(Comments)
|
||||
}
|
||||
}
|
||||
|
||||
// Start returns the start position of the expression.
|
||||
func Start(n Node) Position {
|
||||
start, _ := n.Span()
|
||||
return start
|
||||
}
|
||||
|
||||
// End returns the end position of the expression.
|
||||
func End(n Node) Position {
|
||||
_, end := n.Span()
|
||||
return end
|
||||
}
|
||||
|
||||
// A File represents a Starlark file.
|
||||
type File struct {
|
||||
commentsRef
|
||||
Path string
|
||||
Stmts []Stmt
|
||||
|
||||
Module interface{} // a *resolve.Module, set by resolver
|
||||
}
|
||||
|
||||
func (x *File) Span() (start, end Position) {
|
||||
if len(x.Stmts) == 0 {
|
||||
return
|
||||
}
|
||||
start, _ = x.Stmts[0].Span()
|
||||
_, end = x.Stmts[len(x.Stmts)-1].Span()
|
||||
return start, end
|
||||
}
|
||||
|
||||
// A Stmt is a Starlark statement.
|
||||
type Stmt interface {
|
||||
Node
|
||||
stmt()
|
||||
}
|
||||
|
||||
func (*AssignStmt) stmt() {}
|
||||
func (*BranchStmt) stmt() {}
|
||||
func (*DefStmt) stmt() {}
|
||||
func (*ExprStmt) stmt() {}
|
||||
func (*ForStmt) stmt() {}
|
||||
func (*WhileStmt) stmt() {}
|
||||
func (*IfStmt) stmt() {}
|
||||
func (*LoadStmt) stmt() {}
|
||||
func (*ReturnStmt) stmt() {}
|
||||
|
||||
// An AssignStmt represents an assignment:
|
||||
// x = 0
|
||||
// x, y = y, x
|
||||
// x += 1
|
||||
type AssignStmt struct {
|
||||
commentsRef
|
||||
OpPos Position
|
||||
Op Token // = EQ | {PLUS,MINUS,STAR,PERCENT}_EQ
|
||||
LHS Expr
|
||||
RHS Expr
|
||||
}
|
||||
|
||||
func (x *AssignStmt) Span() (start, end Position) {
|
||||
start, _ = x.LHS.Span()
|
||||
_, end = x.RHS.Span()
|
||||
return
|
||||
}
|
||||
|
||||
// A DefStmt represents a function definition.
|
||||
type DefStmt struct {
|
||||
commentsRef
|
||||
Def Position
|
||||
Name *Ident
|
||||
Params []Expr // param = ident | ident=expr | * | *ident | **ident
|
||||
Body []Stmt
|
||||
|
||||
Function interface{} // a *resolve.Function, set by resolver
|
||||
}
|
||||
|
||||
func (x *DefStmt) Span() (start, end Position) {
|
||||
_, end = x.Body[len(x.Body)-1].Span()
|
||||
return x.Def, end
|
||||
}
|
||||
|
||||
// An ExprStmt is an expression evaluated for side effects.
|
||||
type ExprStmt struct {
|
||||
commentsRef
|
||||
X Expr
|
||||
}
|
||||
|
||||
func (x *ExprStmt) Span() (start, end Position) {
|
||||
return x.X.Span()
|
||||
}
|
||||
|
||||
// An IfStmt is a conditional: If Cond: True; else: False.
|
||||
// 'elseif' is desugared into a chain of IfStmts.
|
||||
type IfStmt struct {
|
||||
commentsRef
|
||||
If Position // IF or ELIF
|
||||
Cond Expr
|
||||
True []Stmt
|
||||
ElsePos Position // ELSE or ELIF
|
||||
False []Stmt // optional
|
||||
}
|
||||
|
||||
func (x *IfStmt) Span() (start, end Position) {
|
||||
body := x.False
|
||||
if body == nil {
|
||||
body = x.True
|
||||
}
|
||||
_, end = body[len(body)-1].Span()
|
||||
return x.If, end
|
||||
}
|
||||
|
||||
// A LoadStmt loads another module and binds names from it:
|
||||
// load(Module, "x", y="foo").
|
||||
//
|
||||
// The AST is slightly unfaithful to the concrete syntax here because
|
||||
// Starlark's load statement, so that it can be implemented in Python,
|
||||
// binds some names (like y above) with an identifier and some (like x)
|
||||
// without. For consistency we create fake identifiers for all the
|
||||
// strings.
|
||||
type LoadStmt struct {
|
||||
commentsRef
|
||||
Load Position
|
||||
Module *Literal // a string
|
||||
From []*Ident // name defined in loading module
|
||||
To []*Ident // name in loaded module
|
||||
Rparen Position
|
||||
}
|
||||
|
||||
func (x *LoadStmt) Span() (start, end Position) {
|
||||
return x.Load, x.Rparen
|
||||
}
|
||||
|
||||
// ModuleName returns the name of the module loaded by this statement.
|
||||
func (x *LoadStmt) ModuleName() string { return x.Module.Value.(string) }
|
||||
|
||||
// A BranchStmt changes the flow of control: break, continue, pass.
|
||||
type BranchStmt struct {
|
||||
commentsRef
|
||||
Token Token // = BREAK | CONTINUE | PASS
|
||||
TokenPos Position
|
||||
}
|
||||
|
||||
func (x *BranchStmt) Span() (start, end Position) {
|
||||
return x.TokenPos, x.TokenPos.add(x.Token.String())
|
||||
}
|
||||
|
||||
// A ReturnStmt returns from a function.
|
||||
type ReturnStmt struct {
|
||||
commentsRef
|
||||
Return Position
|
||||
Result Expr // may be nil
|
||||
}
|
||||
|
||||
func (x *ReturnStmt) Span() (start, end Position) {
|
||||
if x.Result == nil {
|
||||
return x.Return, x.Return.add("return")
|
||||
}
|
||||
_, end = x.Result.Span()
|
||||
return x.Return, end
|
||||
}
|
||||
|
||||
// An Expr is a Starlark expression.
|
||||
type Expr interface {
|
||||
Node
|
||||
expr()
|
||||
}
|
||||
|
||||
func (*BinaryExpr) expr() {}
|
||||
func (*CallExpr) expr() {}
|
||||
func (*Comprehension) expr() {}
|
||||
func (*CondExpr) expr() {}
|
||||
func (*DictEntry) expr() {}
|
||||
func (*DictExpr) expr() {}
|
||||
func (*DotExpr) expr() {}
|
||||
func (*Ident) expr() {}
|
||||
func (*IndexExpr) expr() {}
|
||||
func (*LambdaExpr) expr() {}
|
||||
func (*ListExpr) expr() {}
|
||||
func (*Literal) expr() {}
|
||||
func (*ParenExpr) expr() {}
|
||||
func (*SliceExpr) expr() {}
|
||||
func (*TupleExpr) expr() {}
|
||||
func (*UnaryExpr) expr() {}
|
||||
|
||||
// An Ident represents an identifier.
|
||||
type Ident struct {
|
||||
commentsRef
|
||||
NamePos Position
|
||||
Name string
|
||||
|
||||
Binding interface{} // a *resolver.Binding, set by resolver
|
||||
}
|
||||
|
||||
func (x *Ident) Span() (start, end Position) {
|
||||
return x.NamePos, x.NamePos.add(x.Name)
|
||||
}
|
||||
|
||||
// A Literal represents a literal string or number.
|
||||
type Literal struct {
|
||||
commentsRef
|
||||
Token Token // = STRING | INT
|
||||
TokenPos Position
|
||||
Raw string // uninterpreted text
|
||||
Value interface{} // = string | int64 | *big.Int
|
||||
}
|
||||
|
||||
func (x *Literal) Span() (start, end Position) {
|
||||
return x.TokenPos, x.TokenPos.add(x.Raw)
|
||||
}
|
||||
|
||||
// A ParenExpr represents a parenthesized expression: (X).
|
||||
type ParenExpr struct {
|
||||
commentsRef
|
||||
Lparen Position
|
||||
X Expr
|
||||
Rparen Position
|
||||
}
|
||||
|
||||
func (x *ParenExpr) Span() (start, end Position) {
|
||||
return x.Lparen, x.Rparen.add(")")
|
||||
}
|
||||
|
||||
// A CallExpr represents a function call expression: Fn(Args).
|
||||
type CallExpr struct {
|
||||
commentsRef
|
||||
Fn Expr
|
||||
Lparen Position
|
||||
Args []Expr // arg = expr | ident=expr | *expr | **expr
|
||||
Rparen Position
|
||||
}
|
||||
|
||||
func (x *CallExpr) Span() (start, end Position) {
|
||||
start, _ = x.Fn.Span()
|
||||
return start, x.Rparen.add(")")
|
||||
}
|
||||
|
||||
// A DotExpr represents a field or method selector: X.Name.
|
||||
type DotExpr struct {
|
||||
commentsRef
|
||||
X Expr
|
||||
Dot Position
|
||||
NamePos Position
|
||||
Name *Ident
|
||||
}
|
||||
|
||||
func (x *DotExpr) Span() (start, end Position) {
|
||||
start, _ = x.X.Span()
|
||||
_, end = x.Name.Span()
|
||||
return
|
||||
}
|
||||
|
||||
// A Comprehension represents a list or dict comprehension:
|
||||
// [Body for ... if ...] or {Body for ... if ...}
|
||||
type Comprehension struct {
|
||||
commentsRef
|
||||
Curly bool // {x:y for ...} or {x for ...}, not [x for ...]
|
||||
Lbrack Position
|
||||
Body Expr
|
||||
Clauses []Node // = *ForClause | *IfClause
|
||||
Rbrack Position
|
||||
}
|
||||
|
||||
func (x *Comprehension) Span() (start, end Position) {
|
||||
return x.Lbrack, x.Rbrack.add("]")
|
||||
}
|
||||
|
||||
// A ForStmt represents a loop: for Vars in X: Body.
|
||||
type ForStmt struct {
|
||||
commentsRef
|
||||
For Position
|
||||
Vars Expr // name, or tuple of names
|
||||
X Expr
|
||||
Body []Stmt
|
||||
}
|
||||
|
||||
func (x *ForStmt) Span() (start, end Position) {
|
||||
_, end = x.Body[len(x.Body)-1].Span()
|
||||
return x.For, end
|
||||
}
|
||||
|
||||
// A WhileStmt represents a while loop: while X: Body.
|
||||
type WhileStmt struct {
|
||||
commentsRef
|
||||
While Position
|
||||
Cond Expr
|
||||
Body []Stmt
|
||||
}
|
||||
|
||||
func (x *WhileStmt) Span() (start, end Position) {
|
||||
_, end = x.Body[len(x.Body)-1].Span()
|
||||
return x.While, end
|
||||
}
|
||||
|
||||
// A ForClause represents a for clause in a list comprehension: for Vars in X.
|
||||
type ForClause struct {
|
||||
commentsRef
|
||||
For Position
|
||||
Vars Expr // name, or tuple of names
|
||||
In Position
|
||||
X Expr
|
||||
}
|
||||
|
||||
func (x *ForClause) Span() (start, end Position) {
|
||||
_, end = x.X.Span()
|
||||
return x.For, end
|
||||
}
|
||||
|
||||
// An IfClause represents an if clause in a list comprehension: if Cond.
|
||||
type IfClause struct {
|
||||
commentsRef
|
||||
If Position
|
||||
Cond Expr
|
||||
}
|
||||
|
||||
func (x *IfClause) Span() (start, end Position) {
|
||||
_, end = x.Cond.Span()
|
||||
return x.If, end
|
||||
}
|
||||
|
||||
// A DictExpr represents a dictionary literal: { List }.
|
||||
type DictExpr struct {
|
||||
commentsRef
|
||||
Lbrace Position
|
||||
List []Expr // all *DictEntrys
|
||||
Rbrace Position
|
||||
}
|
||||
|
||||
func (x *DictExpr) Span() (start, end Position) {
|
||||
return x.Lbrace, x.Rbrace.add("}")
|
||||
}
|
||||
|
||||
// A DictEntry represents a dictionary entry: Key: Value.
|
||||
// Used only within a DictExpr.
|
||||
type DictEntry struct {
|
||||
commentsRef
|
||||
Key Expr
|
||||
Colon Position
|
||||
Value Expr
|
||||
}
|
||||
|
||||
func (x *DictEntry) Span() (start, end Position) {
|
||||
start, _ = x.Key.Span()
|
||||
_, end = x.Value.Span()
|
||||
return start, end
|
||||
}
|
||||
|
||||
// A LambdaExpr represents an inline function abstraction.
|
||||
//
|
||||
// Although they may be added in future, lambda expressions are not
|
||||
// currently part of the Starlark spec, so their use is controlled by the
|
||||
// resolver.AllowLambda flag.
|
||||
type LambdaExpr struct {
|
||||
commentsRef
|
||||
Lambda Position
|
||||
Params []Expr // param = ident | ident=expr | * | *ident | **ident
|
||||
Body Expr
|
||||
|
||||
Function interface{} // a *resolve.Function, set by resolver
|
||||
}
|
||||
|
||||
func (x *LambdaExpr) Span() (start, end Position) {
|
||||
_, end = x.Body.Span()
|
||||
return x.Lambda, end
|
||||
}
|
||||
|
||||
// A ListExpr represents a list literal: [ List ].
|
||||
type ListExpr struct {
|
||||
commentsRef
|
||||
Lbrack Position
|
||||
List []Expr
|
||||
Rbrack Position
|
||||
}
|
||||
|
||||
func (x *ListExpr) Span() (start, end Position) {
|
||||
return x.Lbrack, x.Rbrack.add("]")
|
||||
}
|
||||
|
||||
// CondExpr represents the conditional: X if COND else ELSE.
|
||||
type CondExpr struct {
|
||||
commentsRef
|
||||
If Position
|
||||
Cond Expr
|
||||
True Expr
|
||||
ElsePos Position
|
||||
False Expr
|
||||
}
|
||||
|
||||
func (x *CondExpr) Span() (start, end Position) {
|
||||
start, _ = x.True.Span()
|
||||
_, end = x.False.Span()
|
||||
return start, end
|
||||
}
|
||||
|
||||
// A TupleExpr represents a tuple literal: (List).
|
||||
type TupleExpr struct {
|
||||
commentsRef
|
||||
Lparen Position // optional (e.g. in x, y = 0, 1), but required if List is empty
|
||||
List []Expr
|
||||
Rparen Position
|
||||
}
|
||||
|
||||
func (x *TupleExpr) Span() (start, end Position) {
|
||||
if x.Lparen.IsValid() {
|
||||
return x.Lparen, x.Rparen
|
||||
} else {
|
||||
return Start(x.List[0]), End(x.List[len(x.List)-1])
|
||||
}
|
||||
}
|
||||
|
||||
// A UnaryExpr represents a unary expression: Op X.
|
||||
//
|
||||
// As a special case, UnaryOp{Op:Star} may also represent
|
||||
// the star parameter in def f(*args) or def f(*, x).
|
||||
type UnaryExpr struct {
|
||||
commentsRef
|
||||
OpPos Position
|
||||
Op Token
|
||||
X Expr // may be nil if Op==STAR
|
||||
}
|
||||
|
||||
func (x *UnaryExpr) Span() (start, end Position) {
|
||||
if x.X != nil {
|
||||
_, end = x.X.Span()
|
||||
} else {
|
||||
end = x.OpPos.add("*")
|
||||
}
|
||||
return x.OpPos, end
|
||||
}
|
||||
|
||||
// A BinaryExpr represents a binary expression: X Op Y.
|
||||
//
|
||||
// As a special case, BinaryExpr{Op:EQ} may also
|
||||
// represent a named argument in a call f(k=v)
|
||||
// or a named parameter in a function declaration
|
||||
// def f(param=default).
|
||||
type BinaryExpr struct {
|
||||
commentsRef
|
||||
X Expr
|
||||
OpPos Position
|
||||
Op Token
|
||||
Y Expr
|
||||
}
|
||||
|
||||
func (x *BinaryExpr) Span() (start, end Position) {
|
||||
start, _ = x.X.Span()
|
||||
_, end = x.Y.Span()
|
||||
return start, end
|
||||
}
|
||||
|
||||
// A SliceExpr represents a slice or substring expression: X[Lo:Hi:Step].
|
||||
type SliceExpr struct {
|
||||
commentsRef
|
||||
X Expr
|
||||
Lbrack Position
|
||||
Lo, Hi, Step Expr // all optional
|
||||
Rbrack Position
|
||||
}
|
||||
|
||||
func (x *SliceExpr) Span() (start, end Position) {
|
||||
start, _ = x.X.Span()
|
||||
return start, x.Rbrack
|
||||
}
|
||||
|
||||
// An IndexExpr represents an index expression: X[Y].
|
||||
type IndexExpr struct {
|
||||
commentsRef
|
||||
X Expr
|
||||
Lbrack Position
|
||||
Y Expr
|
||||
Rbrack Position
|
||||
}
|
||||
|
||||
func (x *IndexExpr) Span() (start, end Position) {
|
||||
start, _ = x.X.Span()
|
||||
return start, x.Rbrack
|
||||
}
|
||||
163
vendor/go.starlark.net/syntax/walk.go
generated
vendored
Normal file
163
vendor/go.starlark.net/syntax/walk.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright 2017 The Bazel Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package syntax
|
||||
|
||||
// Walk traverses a syntax tree in depth-first order.
|
||||
// It starts by calling f(n); n must not be nil.
|
||||
// If f returns true, Walk calls itself
|
||||
// recursively for each non-nil child of n.
|
||||
// Walk then calls f(nil).
|
||||
func Walk(n Node, f func(Node) bool) {
|
||||
if n == nil {
|
||||
panic("nil")
|
||||
}
|
||||
if !f(n) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: order cases using profile data.
|
||||
switch n := n.(type) {
|
||||
case *File:
|
||||
walkStmts(n.Stmts, f)
|
||||
|
||||
case *ExprStmt:
|
||||
Walk(n.X, f)
|
||||
|
||||
case *BranchStmt:
|
||||
// no-op
|
||||
|
||||
case *IfStmt:
|
||||
Walk(n.Cond, f)
|
||||
walkStmts(n.True, f)
|
||||
walkStmts(n.False, f)
|
||||
|
||||
case *AssignStmt:
|
||||
Walk(n.LHS, f)
|
||||
Walk(n.RHS, f)
|
||||
|
||||
case *DefStmt:
|
||||
Walk(n.Name, f)
|
||||
for _, param := range n.Params {
|
||||
Walk(param, f)
|
||||
}
|
||||
walkStmts(n.Body, f)
|
||||
|
||||
case *ForStmt:
|
||||
Walk(n.Vars, f)
|
||||
Walk(n.X, f)
|
||||
walkStmts(n.Body, f)
|
||||
|
||||
case *ReturnStmt:
|
||||
if n.Result != nil {
|
||||
Walk(n.Result, f)
|
||||
}
|
||||
|
||||
case *LoadStmt:
|
||||
Walk(n.Module, f)
|
||||
for _, from := range n.From {
|
||||
Walk(from, f)
|
||||
}
|
||||
for _, to := range n.To {
|
||||
Walk(to, f)
|
||||
}
|
||||
|
||||
case *Ident, *Literal:
|
||||
// no-op
|
||||
|
||||
case *ListExpr:
|
||||
for _, x := range n.List {
|
||||
Walk(x, f)
|
||||
}
|
||||
|
||||
case *ParenExpr:
|
||||
Walk(n.X, f)
|
||||
|
||||
case *CondExpr:
|
||||
Walk(n.Cond, f)
|
||||
Walk(n.True, f)
|
||||
Walk(n.False, f)
|
||||
|
||||
case *IndexExpr:
|
||||
Walk(n.X, f)
|
||||
Walk(n.Y, f)
|
||||
|
||||
case *DictEntry:
|
||||
Walk(n.Key, f)
|
||||
Walk(n.Value, f)
|
||||
|
||||
case *SliceExpr:
|
||||
Walk(n.X, f)
|
||||
if n.Lo != nil {
|
||||
Walk(n.Lo, f)
|
||||
}
|
||||
if n.Hi != nil {
|
||||
Walk(n.Hi, f)
|
||||
}
|
||||
if n.Step != nil {
|
||||
Walk(n.Step, f)
|
||||
}
|
||||
|
||||
case *Comprehension:
|
||||
Walk(n.Body, f)
|
||||
for _, clause := range n.Clauses {
|
||||
Walk(clause, f)
|
||||
}
|
||||
|
||||
case *IfClause:
|
||||
Walk(n.Cond, f)
|
||||
|
||||
case *ForClause:
|
||||
Walk(n.Vars, f)
|
||||
Walk(n.X, f)
|
||||
|
||||
case *TupleExpr:
|
||||
for _, x := range n.List {
|
||||
Walk(x, f)
|
||||
}
|
||||
|
||||
case *DictExpr:
|
||||
for _, entry := range n.List {
|
||||
entry := entry.(*DictEntry)
|
||||
Walk(entry.Key, f)
|
||||
Walk(entry.Value, f)
|
||||
}
|
||||
|
||||
case *UnaryExpr:
|
||||
if n.X != nil {
|
||||
Walk(n.X, f)
|
||||
}
|
||||
|
||||
case *BinaryExpr:
|
||||
Walk(n.X, f)
|
||||
Walk(n.Y, f)
|
||||
|
||||
case *DotExpr:
|
||||
Walk(n.X, f)
|
||||
Walk(n.Name, f)
|
||||
|
||||
case *CallExpr:
|
||||
Walk(n.Fn, f)
|
||||
for _, arg := range n.Args {
|
||||
Walk(arg, f)
|
||||
}
|
||||
|
||||
case *LambdaExpr:
|
||||
for _, param := range n.Params {
|
||||
Walk(param, f)
|
||||
}
|
||||
Walk(n.Body, f)
|
||||
|
||||
default:
|
||||
panic(n)
|
||||
}
|
||||
|
||||
f(nil)
|
||||
}
|
||||
|
||||
func walkStmts(stmts []Stmt, f func(Node) bool) {
|
||||
for _, stmt := range stmts {
|
||||
Walk(stmt, f)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user