feat: kubesphere 4.0 (#6115)

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

View File

@@ -33,6 +33,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"go.starlark.net/resolve"
@@ -46,7 +47,7 @@ var Disassemble = false
const debug = false // make code generation verbose, for debugging the compiler
// Increment this to force recompilation of saved bytecode files.
const Version = 10
const Version = 13
type Opcode uint8
@@ -99,20 +100,19 @@ const (
FALSE // - FALSE False
MANDATORY // - MANDATORY Mandatory [sentinel value for required kwonly args]
ITERPUSH // iterable ITERPUSH - [pushes the iterator stack]
ITERPOP // - ITERPOP - [pops the iterator stack]
NOT // value NOT bool
RETURN // value RETURN -
SETINDEX // a i new SETINDEX -
INDEX // a i INDEX elem
SETDICT // dict key value SETDICT -
SETDICTUNIQ // dict key value SETDICTUNIQ -
APPEND // list elem APPEND -
SLICE // x lo hi step SLICE slice
INPLACE_ADD // x y INPLACE_ADD z where z is x+y or x.extend(y)
MAKEDICT // - MAKEDICT dict
SETCELL // value cell SETCELL -
CELL // cell CELL value
ITERPUSH // iterable ITERPUSH - [pushes the iterator stack]
ITERPOP // - ITERPOP - [pops the iterator stack]
NOT // value NOT bool
RETURN // value RETURN -
SETINDEX // a i new SETINDEX -
INDEX // a i INDEX elem
SETDICT // dict key value SETDICT -
SETDICTUNIQ // dict key value SETDICTUNIQ -
APPEND // list elem APPEND -
SLICE // x lo hi step SLICE slice
INPLACE_ADD // x y INPLACE_ADD z where z is x+y or x.extend(y)
INPLACE_PIPE // x y INPLACE_PIPE z where z is x|y
MAKEDICT // - MAKEDICT dict
// --- opcodes with an argument must go below this line ---
@@ -122,21 +122,24 @@ const (
ITERJMP // - ITERJMP<addr> elem (and fall through) [acts on topmost iterator]
// or: - ITERJMP<addr> - (and jump)
CONSTANT // - CONSTANT<constant> value
MAKETUPLE // x1 ... xn MAKETUPLE<n> tuple
MAKELIST // x1 ... xn MAKELIST<n> list
MAKEFUNC // defaults+freevars MAKEFUNC<func> fn
LOAD // from1 ... fromN module LOAD<n> v1 ... vN
SETLOCAL // value SETLOCAL<local> -
SETGLOBAL // value SETGLOBAL<global> -
LOCAL // - LOCAL<local> value
FREE // - FREE<freevar> cell
GLOBAL // - GLOBAL<global> value
PREDECLARED // - PREDECLARED<name> value
UNIVERSAL // - UNIVERSAL<name> value
ATTR // x ATTR<name> y y = x.name
SETFIELD // x y SETFIELD<name> - x.name = y
UNPACK // iterable UNPACK<n> vn ... v1
CONSTANT // - CONSTANT<constant> value
MAKETUPLE // x1 ... xn MAKETUPLE<n> tuple
MAKELIST // x1 ... xn MAKELIST<n> list
MAKEFUNC // defaults+freevars MAKEFUNC<func> fn
LOAD // from1 ... fromN module LOAD<n> v1 ... vN
SETLOCAL // value SETLOCAL<local> -
SETGLOBAL // value SETGLOBAL<global> -
LOCAL // - LOCAL<local> value
FREE // - FREE<freevar> cell
FREECELL // - FREECELL<freevar> value (content of FREE cell)
LOCALCELL // - LOCALCELL<local> value (content of LOCAL cell)
SETLOCALCELL // value SETLOCALCELL<local> - (set content of LOCAL cell)
GLOBAL // - GLOBAL<global> value
PREDECLARED // - PREDECLARED<name> value
UNIVERSAL // - UNIVERSAL<name> value
ATTR // x ATTR<name> y y = x.name
SETFIELD // x y SETFIELD<name> - x.name = y
UNPACK // iterable UNPACK<n> vn ... v1
// n>>8 is #positional args and n&0xff is #named args (pairs).
CALL // fn positional named CALL<n> result
@@ -151,72 +154,74 @@ const (
// TODO(adonovan): add dynamic checks for missing opcodes in the tables below.
var opcodeNames = [...]string{
AMP: "amp",
APPEND: "append",
ATTR: "attr",
CALL: "call",
CALL_KW: "call_kw ",
CALL_VAR: "call_var",
CALL_VAR_KW: "call_var_kw",
CELL: "cell",
CIRCUMFLEX: "circumflex",
CJMP: "cjmp",
CONSTANT: "constant",
DUP2: "dup2",
DUP: "dup",
EQL: "eql",
EXCH: "exch",
FALSE: "false",
FREE: "free",
GE: "ge",
GLOBAL: "global",
GT: "gt",
GTGT: "gtgt",
IN: "in",
INDEX: "index",
INPLACE_ADD: "inplace_add",
ITERJMP: "iterjmp",
ITERPOP: "iterpop",
ITERPUSH: "iterpush",
JMP: "jmp",
LE: "le",
LOAD: "load",
LOCAL: "local",
LT: "lt",
LTLT: "ltlt",
MAKEDICT: "makedict",
MAKEFUNC: "makefunc",
MAKELIST: "makelist",
MAKETUPLE: "maketuple",
MANDATORY: "mandatory",
MINUS: "minus",
NEQ: "neq",
NONE: "none",
NOP: "nop",
NOT: "not",
PERCENT: "percent",
PIPE: "pipe",
PLUS: "plus",
POP: "pop",
PREDECLARED: "predeclared",
RETURN: "return",
SETCELL: "setcell",
SETDICT: "setdict",
SETDICTUNIQ: "setdictuniq",
SETFIELD: "setfield",
SETGLOBAL: "setglobal",
SETINDEX: "setindex",
SETLOCAL: "setlocal",
SLASH: "slash",
SLASHSLASH: "slashslash",
SLICE: "slice",
STAR: "star",
TILDE: "tilde",
TRUE: "true",
UMINUS: "uminus",
UNIVERSAL: "universal",
UNPACK: "unpack",
UPLUS: "uplus",
AMP: "amp",
APPEND: "append",
ATTR: "attr",
CALL: "call",
CALL_KW: "call_kw ",
CALL_VAR: "call_var",
CALL_VAR_KW: "call_var_kw",
CIRCUMFLEX: "circumflex",
CJMP: "cjmp",
CONSTANT: "constant",
DUP2: "dup2",
DUP: "dup",
EQL: "eql",
EXCH: "exch",
FALSE: "false",
FREE: "free",
FREECELL: "freecell",
GE: "ge",
GLOBAL: "global",
GT: "gt",
GTGT: "gtgt",
IN: "in",
INDEX: "index",
INPLACE_ADD: "inplace_add",
INPLACE_PIPE: "inplace_pipe",
ITERJMP: "iterjmp",
ITERPOP: "iterpop",
ITERPUSH: "iterpush",
JMP: "jmp",
LE: "le",
LOAD: "load",
LOCAL: "local",
LOCALCELL: "localcell",
LT: "lt",
LTLT: "ltlt",
MAKEDICT: "makedict",
MAKEFUNC: "makefunc",
MAKELIST: "makelist",
MAKETUPLE: "maketuple",
MANDATORY: "mandatory",
MINUS: "minus",
NEQ: "neq",
NONE: "none",
NOP: "nop",
NOT: "not",
PERCENT: "percent",
PIPE: "pipe",
PLUS: "plus",
POP: "pop",
PREDECLARED: "predeclared",
RETURN: "return",
SETDICT: "setdict",
SETDICTUNIQ: "setdictuniq",
SETFIELD: "setfield",
SETGLOBAL: "setglobal",
SETINDEX: "setindex",
SETLOCAL: "setlocal",
SETLOCALCELL: "setlocalcell",
SLASH: "slash",
SLASHSLASH: "slashslash",
SLICE: "slice",
STAR: "star",
TILDE: "tilde",
TRUE: "true",
UMINUS: "uminus",
UNIVERSAL: "universal",
UNPACK: "unpack",
UPLUS: "uplus",
}
const variableStackEffect = 0x7f
@@ -224,70 +229,72 @@ const variableStackEffect = 0x7f
// stackEffect records the effect on the size of the operand stack of
// each kind of instruction. For some instructions this requires computation.
var stackEffect = [...]int8{
AMP: -1,
APPEND: -2,
ATTR: 0,
CALL: variableStackEffect,
CALL_KW: variableStackEffect,
CALL_VAR: variableStackEffect,
CALL_VAR_KW: variableStackEffect,
CELL: 0,
CIRCUMFLEX: -1,
CJMP: -1,
CONSTANT: +1,
DUP2: +2,
DUP: +1,
EQL: -1,
FALSE: +1,
FREE: +1,
GE: -1,
GLOBAL: +1,
GT: -1,
GTGT: -1,
IN: -1,
INDEX: -1,
INPLACE_ADD: -1,
ITERJMP: variableStackEffect,
ITERPOP: 0,
ITERPUSH: -1,
JMP: 0,
LE: -1,
LOAD: -1,
LOCAL: +1,
LT: -1,
LTLT: -1,
MAKEDICT: +1,
MAKEFUNC: 0,
MAKELIST: variableStackEffect,
MAKETUPLE: variableStackEffect,
MANDATORY: +1,
MINUS: -1,
NEQ: -1,
NONE: +1,
NOP: 0,
NOT: 0,
PERCENT: -1,
PIPE: -1,
PLUS: -1,
POP: -1,
PREDECLARED: +1,
RETURN: -1,
SETCELL: -2,
SETDICT: -3,
SETDICTUNIQ: -3,
SETFIELD: -2,
SETGLOBAL: -1,
SETINDEX: -3,
SETLOCAL: -1,
SLASH: -1,
SLASHSLASH: -1,
SLICE: -3,
STAR: -1,
TRUE: +1,
UMINUS: 0,
UNIVERSAL: +1,
UNPACK: variableStackEffect,
UPLUS: 0,
AMP: -1,
APPEND: -2,
ATTR: 0,
CALL: variableStackEffect,
CALL_KW: variableStackEffect,
CALL_VAR: variableStackEffect,
CALL_VAR_KW: variableStackEffect,
CIRCUMFLEX: -1,
CJMP: -1,
CONSTANT: +1,
DUP2: +2,
DUP: +1,
EQL: -1,
FALSE: +1,
FREE: +1,
FREECELL: +1,
GE: -1,
GLOBAL: +1,
GT: -1,
GTGT: -1,
IN: -1,
INDEX: -1,
INPLACE_ADD: -1,
INPLACE_PIPE: -1,
ITERJMP: variableStackEffect,
ITERPOP: 0,
ITERPUSH: -1,
JMP: 0,
LE: -1,
LOAD: -1,
LOCAL: +1,
LOCALCELL: +1,
LT: -1,
LTLT: -1,
MAKEDICT: +1,
MAKEFUNC: 0,
MAKELIST: variableStackEffect,
MAKETUPLE: variableStackEffect,
MANDATORY: +1,
MINUS: -1,
NEQ: -1,
NONE: +1,
NOP: 0,
NOT: 0,
PERCENT: -1,
PIPE: -1,
PLUS: -1,
POP: -1,
PREDECLARED: +1,
RETURN: -1,
SETLOCALCELL: -1,
SETDICT: -3,
SETDICTUNIQ: -3,
SETFIELD: -2,
SETGLOBAL: -1,
SETINDEX: -3,
SETLOCAL: -1,
SLASH: -1,
SLASHSLASH: -1,
SLICE: -3,
STAR: -1,
TRUE: +1,
UMINUS: 0,
UNIVERSAL: +1,
UNPACK: variableStackEffect,
UPLUS: 0,
}
func (op Opcode) String() string {
@@ -306,12 +313,15 @@ func (op Opcode) String() string {
type Program struct {
Loads []Binding // name (really, string) and position of each load stmt
Names []string // names of attributes and predeclared variables
Constants []interface{} // = string | int64 | float64 | *big.Int
Constants []interface{} // = string | int64 | float64 | *big.Int | Bytes
Functions []*Funcode
Globals []Binding // for error messages and tracing
Toplevel *Funcode // module initialization function
}
// The type of a bytes literal value, to distinguish from text string.
type Bytes string
// A Funcode is the code of a compiled Starlark function.
//
// Funcodes are serialized by the encoder.function method,
@@ -860,6 +870,8 @@ func PrintOp(fn *Funcode, pc uint32, op Opcode, arg uint32) {
switch x := fn.Prog.Constants[arg].(type) {
case string:
comment = strconv.Quote(x)
case Bytes:
comment = "b" + strconv.Quote(string(x))
default:
comment = fmt.Sprint(x)
}
@@ -994,9 +1006,7 @@ func (fcomp *fcomp) set(id *syntax.Ident) {
case resolve.Local:
fcomp.emit1(SETLOCAL, uint32(bind.Index))
case resolve.Cell:
// TODO(adonovan): opt: make a single op for LOCAL<n>, SETCELL.
fcomp.emit1(LOCAL, uint32(bind.Index))
fcomp.emit(SETCELL)
fcomp.emit1(SETLOCALCELL, uint32(bind.Index))
case resolve.Global:
fcomp.emit1(SETGLOBAL, uint32(bind.Index))
default:
@@ -1014,13 +1024,9 @@ func (fcomp *fcomp) lookup(id *syntax.Ident) {
case resolve.Local:
fcomp.emit1(LOCAL, uint32(bind.Index))
case resolve.Free:
// TODO(adonovan): opt: make a single op for FREE<n>, CELL.
fcomp.emit1(FREE, uint32(bind.Index))
fcomp.emit(CELL)
fcomp.emit1(FREECELL, uint32(bind.Index))
case resolve.Cell:
// TODO(adonovan): opt: make a single op for LOCAL<n>, CELL.
fcomp.emit1(LOCAL, uint32(bind.Index))
fcomp.emit(CELL)
fcomp.emit1(LOCALCELL, uint32(bind.Index))
case resolve.Global:
fcomp.emit1(GLOBAL, uint32(bind.Index))
case resolve.Predeclared:
@@ -1142,11 +1148,16 @@ func (fcomp *fcomp) stmt(stmt syntax.Stmt) {
fcomp.expr(stmt.RHS)
if stmt.Op == syntax.PLUS_EQ {
// Allow the runtime to optimize list += iterable.
// In-place x+=y and x|=y have special semantics:
// the resulting x aliases the original x.
switch stmt.Op {
case syntax.PLUS_EQ:
fcomp.setPos(stmt.OpPos)
fcomp.emit(INPLACE_ADD)
} else {
case syntax.PIPE_EQ:
fcomp.setPos(stmt.OpPos)
fcomp.emit(INPLACE_PIPE)
default:
fcomp.binop(stmt.OpPos, stmt.Op-syntax.PLUS_EQ+syntax.PLUS)
}
set()
@@ -1286,8 +1297,12 @@ func (fcomp *fcomp) expr(e syntax.Expr) {
fcomp.lookup(e)
case *syntax.Literal:
// e.Value is int64, float64, *bigInt, or string.
fcomp.emit1(CONSTANT, fcomp.pcomp.constantIndex(e.Value))
// e.Value is int64, float64, *bigInt, string
v := e.Value
if e.Token == syntax.BYTES {
v = Bytes(v.(string))
}
fcomp.emit1(CONSTANT, fcomp.pcomp.constantIndex(v))
case *syntax.ListExpr:
for _, x := range e.List {
@@ -1525,7 +1540,7 @@ func (fcomp *fcomp) plus(e *syntax.BinaryExpr) {
}
// addable reports whether e is a statically addable
// expression: a [s]tring, [l]ist, or [t]uple.
// expression: a [s]tring, [b]ytes, [l]ist, or [t]uple.
func addable(e syntax.Expr) rune {
switch e := e.(type) {
case *syntax.Literal:
@@ -1533,6 +1548,8 @@ func addable(e syntax.Expr) rune {
switch e.Token {
case syntax.STRING:
return 's'
case syntax.BYTES:
return 'b'
}
case *syntax.ListExpr:
return 'l'
@@ -1547,12 +1564,16 @@ func addable(e syntax.Expr) rune {
// The resulting syntax is degenerate, lacking position, etc.
func add(code rune, args []summand) syntax.Expr {
switch code {
case 's':
var buf bytes.Buffer
case 's', 'b':
var buf strings.Builder
for _, arg := range args {
buf.WriteString(arg.x.(*syntax.Literal).Value.(string))
}
return &syntax.Literal{Token: syntax.STRING, Value: buf.String()}
tok := syntax.STRING
if code == 'b' {
tok = syntax.BYTES
}
return &syntax.Literal{Token: tok, Value: buf.String()}
case 'l':
var elems []syntax.Expr
for _, arg := range args {

View File

@@ -51,9 +51,10 @@ package compile
//
// Constant: # type data
// type varint # 0=string string
// data ... # 1=int varint
// # 2=float varint (bits as uint64)
// # 3=bigint string (decimal ASCII text)
// data ... # 1=bytes string
// # 2=int varint
// # 3=float varint (bits as uint64)
// # 4=bigint string (decimal ASCII text)
//
// The encoding starts with a four-byte magic number.
// The next four bytes are a little-endian uint32
@@ -109,14 +110,17 @@ func (prog *Program) Encode() []byte {
case string:
e.int(0)
e.string(c)
case int64:
case Bytes:
e.int(1)
e.string(string(c))
case int64:
e.int(2)
e.int64(c)
case float64:
e.int(2)
e.int(3)
e.uint64(math.Float64bits(c))
case *big.Int:
e.int(3)
e.int(4)
e.string(c.Text(10))
}
}
@@ -249,10 +253,12 @@ func DecodeProgram(data []byte) (_ *Program, err error) {
case 0:
c = d.string()
case 1:
c = d.int64()
c = Bytes(d.string())
case 2:
c = math.Float64frombits(d.uint64())
c = d.int64()
case 3:
c = math.Float64frombits(d.uint64())
case 4:
c, _ = new(big.Int).SetString(d.string(), 10)
}
constants[i] = c

View File

@@ -98,14 +98,16 @@ const doesnt = "this Starlark dialect does not "
// 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)
// obsolete flags for features that are now standard. No effect.
AllowNestedDef = true
AllowLambda = true
AllowFloat = true
AllowBitwise = true
)
// File resolves the specified file and records information about the
@@ -214,7 +216,8 @@ type resolver struct {
// isGlobal may be nil.
isGlobal, isPredeclared, isUniversal func(name string) bool
loops int // number of enclosing for loops
loops int // number of enclosing for/while loops
ifstmts int // number of enclosing if statements loops
errors ErrorList
}
@@ -417,9 +420,6 @@ func (r *resolver) useToplevel(use use) (bind *Binding) {
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")
}
@@ -497,8 +497,10 @@ func (r *resolver) stmt(stmt syntax.Stmt) {
r.errorf(stmt.If, "if statement not within a function")
}
r.expr(stmt.Cond)
r.ifstmts++
r.stmts(stmt.True)
r.stmts(stmt.False)
r.ifstmts--
case *syntax.AssignStmt:
r.expr(stmt.RHS)
@@ -506,9 +508,6 @@ func (r *resolver) stmt(stmt syntax.Stmt) {
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,
@@ -551,8 +550,13 @@ func (r *resolver) stmt(stmt syntax.Stmt) {
}
case *syntax.LoadStmt:
// A load statement may not be nested in any other statement.
if r.container().function != nil {
r.errorf(stmt.Load, "load statement within a function")
} else if r.loops > 0 {
r.errorf(stmt.Load, "load statement within a loop")
} else if r.ifstmts > 0 {
r.errorf(stmt.Load, "load statement within a conditional")
}
for i, from := range stmt.From {
@@ -597,9 +601,6 @@ func (r *resolver) assign(lhs syntax.Expr, isAugmented bool) {
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")
}
@@ -609,9 +610,6 @@ func (r *resolver) assign(lhs syntax.Expr, isAugmented bool) {
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")
}
@@ -634,9 +632,6 @@ func (r *resolver) expr(e syntax.Expr) {
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 {
@@ -711,9 +706,6 @@ func (r *resolver) expr(e syntax.Expr) {
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)
@@ -748,11 +740,13 @@ func (r *resolver) expr(e syntax.Expr) {
// k=v
n++
if seenKwargs {
r.errorf(pos, "argument may not follow **kwargs")
r.errorf(pos, "keyword argument may not follow **kwargs")
} else if seenVarargs {
r.errorf(pos, "keyword argument may not follow *args")
}
x := binop.X.(*syntax.Ident)
if seenName[x.Name] {
r.errorf(x.NamePos, "keyword argument %s repeated", x.Name)
r.errorf(x.NamePos, "keyword argument %q is repeated", x.Name)
} else {
if seenName == nil {
seenName = make(map[string]bool)
@@ -764,9 +758,9 @@ func (r *resolver) expr(e syntax.Expr) {
// positional argument
p++
if seenVarargs {
r.errorf(pos, "argument may not follow *args")
r.errorf(pos, "positional argument may not follow *args")
} else if seenKwargs {
r.errorf(pos, "argument may not follow **kwargs")
r.errorf(pos, "positional argument may not follow **kwargs")
} else if len(seenName) > 0 {
r.errorf(pos, "positional argument may not follow named")
}
@@ -785,9 +779,6 @@ func (r *resolver) expr(e syntax.Expr) {
}
case *syntax.LambdaExpr:
if !AllowLambda {
r.errorf(e.Lambda, doesnt+"support lambda")
}
fn := &Function{
Name: "lambda",
Pos: e.Lambda,

View File

@@ -9,13 +9,14 @@ import (
"io"
"io/ioutil"
"log"
"math"
"math/big"
"sort"
"strings"
"sync/atomic"
"time"
"unicode"
"unicode/utf8"
"unsafe"
"go.starlark.net/internal/compile"
"go.starlark.net/internal/spell"
@@ -46,6 +47,21 @@ type Thread struct {
// See example_test.go for some example implementations of Load.
Load func(thread *Thread, module string) (StringDict, error)
// OnMaxSteps is called when the thread reaches the limit set by SetMaxExecutionSteps.
// The default behavior is to call thread.Cancel("too many steps").
OnMaxSteps func(thread *Thread)
// Steps a count of abstract computation steps executed
// by this thread. It is incremented by the interpreter. It may be used
// as a measure of the approximate cost of Starlark execution, by
// computing the difference in its value before and after a computation.
//
// The precise meaning of "step" is not specified and may change.
Steps, maxSteps uint64
// cancelReason records the reason from the first call to Cancel.
cancelReason *string
// locals holds arbitrary "thread-local" Go values belonging to the client.
// They are accessible to the client but not to any Starlark program.
locals map[string]interface{}
@@ -54,6 +70,42 @@ type Thread struct {
proftime time.Duration
}
// ExecutionSteps returns the current value of Steps.
func (thread *Thread) ExecutionSteps() uint64 {
return thread.Steps
}
// SetMaxExecutionSteps sets a limit on the number of Starlark
// computation steps that may be executed by this thread. If the
// thread's step counter exceeds this limit, the interpreter calls
// the optional OnMaxSteps function or the default behavior
// of calling thread.Cancel("too many steps").
func (thread *Thread) SetMaxExecutionSteps(max uint64) {
thread.maxSteps = max
}
// Uncancel resets the cancellation state.
//
// Unlike most methods of Thread, it is safe to call Uncancel from any
// goroutine, even if the thread is actively executing.
func (thread *Thread) Uncancel() {
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&thread.cancelReason)), nil)
}
// Cancel causes execution of Starlark code in the specified thread to
// promptly fail with an EvalError that includes the specified reason.
// There may be a delay before the interpreter observes the cancellation
// if the thread is currently in a call to a built-in function.
//
// Call [Uncancel] to reset the cancellation state.
//
// Unlike most methods of Thread, it is safe to call Cancel from any
// goroutine, even if the thread is actively executing.
func (thread *Thread) Cancel(reason string) {
// Atomically set cancelReason, preserving earlier reason if any.
atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&thread.cancelReason)), nil, unsafe.Pointer(&reason))
}
// SetLocal sets the thread-local value associated with the specified key.
// It must not be called after execution begins.
func (thread *Thread) SetLocal(key string, value interface{}) {
@@ -178,7 +230,9 @@ func (stack *CallStack) Pop() CallFrame {
// String returns a user-friendly description of the stack.
func (stack CallStack) String() string {
out := new(strings.Builder)
fmt.Fprintf(out, "Traceback (most recent call last):\n")
if len(stack) > 0 {
fmt.Fprintf(out, "Traceback (most recent call last):\n")
}
for _, fr := range stack {
fmt.Fprintf(out, " %s: in %s\n", fr.Pos, fr.Name)
}
@@ -220,7 +274,15 @@ func (e *EvalError) Error() string { return e.Msg }
// Backtrace returns a user-friendly error message describing the stack
// of calls that led to this error.
func (e *EvalError) Backtrace() string {
return fmt.Sprintf("%sError: %s", e.CallStack, e.Msg)
// If the topmost stack frame is a built-in function,
// remove it from the stack and add print "Error in fn:".
stack := e.CallStack
suffix := ""
if last := len(stack) - 1; last >= 0 && stack[last].Pos.Filename() == builtinFilename {
suffix = " in " + stack[last].Name
stack = stack[:last]
}
return fmt.Sprintf("%sError%s: %s", stack, suffix, e.Msg)
}
func (e *EvalError) Unwrap() error { return e.cause }
@@ -429,6 +491,8 @@ func makeToplevelFunction(prog *compile.Program, predeclared StringDict) *Functi
v = MakeBigInt(c)
case string:
v = String(c)
case compile.Bytes:
v = Bytes(c)
case float64:
v = Float(c)
default:
@@ -674,14 +738,22 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case Int:
return x.Add(y), nil
case Float:
return x.Float() + y, nil
xf, err := x.finiteFloat()
if err != nil {
return nil, err
}
return xf + y, nil
}
case Float:
switch y := y.(type) {
case Float:
return x + y, nil
case Int:
return x + y.Float(), nil
yf, err := y.finiteFloat()
if err != nil {
return nil, err
}
return x + yf, nil
}
case *List:
if y, ok := y.(*List); ok {
@@ -706,14 +778,22 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case Int:
return x.Sub(y), nil
case Float:
return x.Float() - y, nil
xf, err := x.finiteFloat()
if err != nil {
return nil, err
}
return xf - y, nil
}
case Float:
switch y := y.(type) {
case Float:
return x - y, nil
case Int:
return x - y.Float(), nil
yf, err := y.finiteFloat()
if err != nil {
return nil, err
}
return x - yf, nil
}
}
@@ -724,9 +804,15 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case Int:
return x.Mul(y), nil
case Float:
return x.Float() * y, nil
xf, err := x.finiteFloat()
if err != nil {
return nil, err
}
return xf * y, nil
case String:
return stringRepeat(y, x)
case Bytes:
return bytesRepeat(y, x)
case *List:
elems, err := tupleRepeat(Tuple(y.elems), x)
if err != nil {
@@ -741,12 +827,20 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case Float:
return x * y, nil
case Int:
return x * y.Float(), nil
yf, err := y.finiteFloat()
if err != nil {
return nil, err
}
return x * yf, nil
}
case String:
if y, ok := y.(Int); ok {
return stringRepeat(x, y)
}
case Bytes:
if y, ok := y.(Int); ok {
return bytesRepeat(x, y)
}
case *List:
if y, ok := y.(Int); ok {
elems, err := tupleRepeat(Tuple(x.elems), y)
@@ -765,30 +859,40 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case syntax.SLASH:
switch x := x.(type) {
case Int:
xf, err := x.finiteFloat()
if err != nil {
return nil, err
}
switch y := y.(type) {
case Int:
yf := y.Float()
if yf == 0.0 {
return nil, fmt.Errorf("real division by zero")
yf, err := y.finiteFloat()
if err != nil {
return nil, err
}
return x.Float() / yf, nil
if yf == 0.0 {
return nil, fmt.Errorf("floating-point division by zero")
}
return xf / yf, nil
case Float:
if y == 0.0 {
return nil, fmt.Errorf("real division by zero")
return nil, fmt.Errorf("floating-point division by zero")
}
return x.Float() / y, nil
return xf / y, nil
}
case Float:
switch y := y.(type) {
case Float:
if y == 0.0 {
return nil, fmt.Errorf("real division by zero")
return nil, fmt.Errorf("floating-point division by zero")
}
return x / y, nil
case Int:
yf := y.Float()
yf, err := y.finiteFloat()
if err != nil {
return nil, err
}
if yf == 0.0 {
return nil, fmt.Errorf("real division by zero")
return nil, fmt.Errorf("floating-point division by zero")
}
return x / yf, nil
}
@@ -804,10 +908,14 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
return x.Div(y), nil
case Float:
xf, err := x.finiteFloat()
if err != nil {
return nil, err
}
if y == 0.0 {
return nil, fmt.Errorf("floored division by zero")
}
return floor((x.Float() / y)), nil
return floor(xf / y), nil
}
case Float:
switch y := y.(type) {
@@ -817,7 +925,10 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
return floor(x / y), nil
case Int:
yf := y.Float()
yf, err := y.finiteFloat()
if err != nil {
return nil, err
}
if yf == 0.0 {
return nil, fmt.Errorf("floored division by zero")
}
@@ -835,23 +946,31 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
return x.Mod(y), nil
case Float:
if y == 0 {
return nil, fmt.Errorf("float modulo by zero")
xf, err := x.finiteFloat()
if err != nil {
return nil, err
}
return x.Float().Mod(y), nil
if y == 0 {
return nil, fmt.Errorf("floating-point modulo by zero")
}
return xf.Mod(y), nil
}
case Float:
switch y := y.(type) {
case Float:
if y == 0.0 {
return nil, fmt.Errorf("float modulo by zero")
return nil, fmt.Errorf("floating-point modulo by zero")
}
return Float(math.Mod(float64(x), float64(y))), nil
return x.Mod(y), nil
case Int:
if y.Sign() == 0 {
return nil, fmt.Errorf("float modulo by zero")
return nil, fmt.Errorf("floating-point modulo by zero")
}
return x.Mod(y.Float()), nil
yf, err := y.finiteFloat()
if err != nil {
return nil, err
}
return x.Mod(yf), nil
}
case String:
return interpolate(string(x), y)
@@ -898,6 +1017,19 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
return nil, fmt.Errorf("'in <string>' requires string as left operand, not %s", x.Type())
}
return Bool(strings.Contains(string(y), string(needle))), nil
case Bytes:
switch needle := x.(type) {
case Bytes:
return Bool(strings.Contains(string(y), string(needle))), nil
case Int:
var b byte
if err := AsInt(needle, &b); err != nil {
return nil, fmt.Errorf("int in bytes: %s", err)
}
return Bool(strings.IndexByte(string(y), b) >= 0), nil
default:
return nil, fmt.Errorf("'in bytes' requires bytes or int as left operand, not %s", x.Type())
}
case rangeValue:
i, err := NumberToInt(x)
if err != nil {
@@ -912,6 +1044,12 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
if y, ok := y.(Int); ok {
return x.Or(y), nil
}
case *Dict: // union
if y, ok := y.(*Dict); ok {
return x.Union(y), nil
}
case *Set: // union
if y, ok := y.(*Set); ok {
iter := Iterate(y)
@@ -932,10 +1070,10 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
if x.Len() > y.Len() {
x, y = y, x // opt: range over smaller set
}
for _, xelem := range x.elems() {
for xe := x.ht.head; xe != nil; xe = xe.next {
// Has, Insert cannot fail here.
if found, _ := y.Has(xelem); found {
set.Insert(xelem)
if found, _ := y.Has(xe.key); found {
set.Insert(xe.key)
}
}
return set, nil
@@ -951,14 +1089,14 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case *Set: // symmetric difference
if y, ok := y.(*Set); ok {
set := new(Set)
for _, xelem := range x.elems() {
if found, _ := y.Has(xelem); !found {
set.Insert(xelem)
for xe := x.ht.head; xe != nil; xe = xe.next {
if found, _ := y.Has(xe.key); !found {
set.Insert(xe.key)
}
}
for _, yelem := range y.elems() {
if found, _ := x.Has(yelem); !found {
set.Insert(yelem)
for ye := y.ht.head; ye != nil; ye = ye.next {
if found, _ := x.Has(ye.key); !found {
set.Insert(ye.key)
}
}
return set, nil
@@ -1027,7 +1165,8 @@ func tupleRepeat(elems Tuple, n Int) (Tuple, error) {
// Inv: i > 0, len > 0
sz := len(elems) * i
if sz < 0 || sz >= maxAlloc { // sz < 0 => overflow
return nil, fmt.Errorf("excessive repeat (%d elements)", sz)
// Don't print sz.
return nil, fmt.Errorf("excessive repeat (%d * %d elements)", len(elems), i)
}
res := make([]Value, sz)
// copy elems into res, doubling each time
@@ -1039,6 +1178,11 @@ func tupleRepeat(elems Tuple, n Int) (Tuple, error) {
return res, nil
}
func bytesRepeat(b Bytes, n Int) (Bytes, error) {
res, err := stringRepeat(String(b), n)
return Bytes(res), err
}
func stringRepeat(s String, n Int) (String, error) {
if s == "" {
return "", nil
@@ -1053,7 +1197,8 @@ func stringRepeat(s String, n Int) (String, error) {
// Inv: i > 0, len > 0
sz := len(s) * i
if sz < 0 || sz >= maxAlloc { // sz < 0 => overflow
return "", fmt.Errorf("excessive repeat (%d elements)", sz)
// Don't print sz.
return "", fmt.Errorf("excessive repeat (%d * %d elements)", len(s), i)
}
return String(strings.Repeat(string(s), i)), nil
}
@@ -1075,13 +1220,35 @@ func Call(thread *Thread, fn Value, args Tuple, kwargs []Tuple) (Value, error) {
if fr == nil {
fr = new(frame)
}
if thread.stack == nil {
// one-time initialization of thread
if thread.maxSteps == 0 {
thread.maxSteps-- // (MaxUint64)
}
}
thread.stack = append(thread.stack, fr) // push
fr.callable = c
thread.beginProfSpan()
// Use defer to ensure that panics from built-ins
// pass through the interpreter without leaving
// it in a bad state.
defer func() {
thread.endProfSpan()
// clear out any references
// TODO(adonovan): opt: zero fr.Locals and
// reuse it if it is large enough.
*fr = frame{}
thread.stack = thread.stack[:len(thread.stack)-1] // pop
}()
result, err := c.CallInternal(thread, args, kwargs)
thread.endProfSpan()
// Sanity check: nil is not a valid Starlark value.
if result == nil && err == nil {
@@ -1095,9 +1262,6 @@ func Call(thread *Thread, fn Value, args Tuple, kwargs []Tuple) (Value, error) {
}
}
*fr = frame{} // clear out any references
thread.stack = thread.stack[:len(thread.stack)-1] // pop
return result, err
}
@@ -1113,7 +1277,7 @@ func slice(x, lo, hi, step_ Value) (Value, error) {
var err error
step, err = AsInt32(step_)
if err != nil {
return nil, fmt.Errorf("got %s for slice step, want int", step_.Type())
return nil, fmt.Errorf("invalid slice step: %s", err)
}
if step == 0 {
return nil, fmt.Errorf("zero is not a valid slice step")
@@ -1207,7 +1371,7 @@ func asIndex(v Value, len int, result *int) error {
var err error
*result, err = AsInt32(v)
if err != nil {
return fmt.Errorf("got %s, want int", v.Type())
return err
}
if *result < 0 {
*result += len
@@ -1448,20 +1612,7 @@ func interpolate(format string, x Value) (Value, error) {
if !ok {
return nil, fmt.Errorf("%%%c format requires float, not %s", c, arg.Type())
}
switch c {
case 'e':
fmt.Fprintf(buf, "%e", f)
case 'f':
fmt.Fprintf(buf, "%f", f)
case 'g':
fmt.Fprintf(buf, "%g", f)
case 'E':
fmt.Fprintf(buf, "%E", f)
case 'F':
fmt.Fprintf(buf, "%F", f)
case 'G':
fmt.Fprintf(buf, "%G", f)
}
Float(f).format(buf, c)
case 'c':
switch arg := arg.(type) {
case Int:

View File

@@ -12,6 +12,8 @@ import (
// 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.
//
// Initialized instances of hashtable must not be copied.
type hashtable struct {
table []bucket // len is zero or a power of two
bucket0 [1]bucket // inline allocation for small maps.
@@ -20,8 +22,17 @@ type hashtable struct {
head *entry // insertion order doubly-linked list; may be nil
tailLink **entry // address of nil link at end of list (perhaps &head)
frozen bool
_ noCopy // triggers vet copylock check on this type.
}
// noCopy is zero-sized type that triggers vet's copylock check.
// See https://github.com/golang/go/issues/8005#issuecomment-190753527.
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
const bucketSize = 8
type bucket struct {
@@ -55,26 +66,16 @@ func (ht *hashtable) init(size int) {
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()
}
}
}
for e := ht.head; e != nil; e = e.next {
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 err := ht.checkMutable("insert into"); err != nil {
return err
}
if ht.table == nil {
ht.init(1)
@@ -154,13 +155,12 @@ func overloaded(elems, buckets int) bool {
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.
//
// Even though this makes reentrant calls to ht.insert,
// calls Equals unnecessarily (since there can't be duplicate keys),
// and recomputes the hash unnecessarily, the gains from
// avoiding these steps were found to be too small to justify
// the extra logic: -2% on hashtable benchmark.
ht.table = make([]bucket, len(ht.table)<<1)
oldhead := ht.head
ht.head = nil
@@ -230,11 +230,8 @@ func (ht *hashtable) keys() []Value {
}
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 err := ht.checkMutable("delete from"); err != nil {
return nil, false, err
}
if ht.table == nil {
return None, false, nil // empty
@@ -277,12 +274,21 @@ func (ht *hashtable) delete(k Value) (v Value, found bool, err error) {
return None, false, nil // not found
}
func (ht *hashtable) clear() error {
// checkMutable reports an error if the hash table should not be mutated.
// verb+" dict" should describe the operation.
func (ht *hashtable) checkMutable(verb string) error {
if ht.frozen {
return fmt.Errorf("cannot clear frozen hash table")
return fmt.Errorf("cannot %s frozen hash table", verb)
}
if ht.itercount > 0 {
return fmt.Errorf("cannot clear hash table during iteration")
return fmt.Errorf("cannot %s hash table during iteration", verb)
}
return nil
}
func (ht *hashtable) clear() error {
if err := ht.checkMutable("clear"); err != nil {
return err
}
if ht.table != nil {
for i := range ht.table {
@@ -295,6 +301,15 @@ func (ht *hashtable) clear() error {
return nil
}
func (ht *hashtable) addAll(other *hashtable) error {
for e := other.head; e != nil; e = e.next {
if err := ht.insert(e.key, e.value); err != nil {
return err
}
}
return nil
}
// dump is provided as an aid to debugging.
func (ht *hashtable) dump() {
fmt.Printf("hashtable %p len=%d head=%p tailLink=%p",
@@ -349,6 +364,8 @@ func (it *keyIterator) Done() {
}
}
// TODO(adonovan): use go1.19's maphash.String.
// hashString computes the hash of s.
func hashString(s string) uint32 {
if len(s) >= 12 {
@@ -362,9 +379,9 @@ func hashString(s string) uint32 {
//go:linkname goStringHash runtime.stringHash
func goStringHash(s string, seed uintptr) uintptr
// softHashString computes the FNV hash of s in software.
// softHashString computes the 32-bit FNV-1a hash of s in software.
func softHashString(s string) uint32 {
var h uint32
var h uint32 = 2166136261
for i := 0; i < len(s); i++ {
h ^= uint32(s[i])
h *= 16777619

View File

@@ -8,33 +8,18 @@ import (
"fmt"
"math"
"math/big"
"reflect"
"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.
//
// The zero value is not a legal value; use MakeInt(0).
type Int struct{ impl intImpl }
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)
}
// --- high-level accessors ---
// MakeInt returns a Starlark int for the specified signed integer.
func MakeInt(x int) Int { return MakeInt64(int64(x)) }
@@ -42,9 +27,9 @@ 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 makeSmallInt(x)
}
return Int{big: newBig(x)}
return makeBigInt(big.NewInt(x))
}
// MakeUint returns a Starlark int for the specified unsigned integer.
@@ -53,27 +38,29 @@ 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)}
return makeSmallInt(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)}
return makeBigInt(new(big.Int).SetUint64(x))
}
// MakeBigInt returns a Starlark int for the specified big.Int.
// The caller must not subsequently modify x.
// The new Int value will contain a copy of x. The caller is safe to 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()}
if isSmall(x) {
return makeSmallInt(x.Int64())
}
return Int{big: x}
z := new(big.Int).Set(x)
return makeBigInt(z)
}
func isSmall(x *big.Int) bool {
n := x.BitLen()
return n < 32 || n == 32 && x.Int64() == math.MinInt32
}
var (
zero, one = Int{small: 0}, Int{small: 1}
oneBig = newBig(1)
zero, one = makeSmallInt(0), makeSmallInt(1)
oneBig = big.NewInt(1)
_ HasUnary = Int{}
)
@@ -94,39 +81,52 @@ func (i Int) Unary(op syntax.Token) (Value, error) {
// 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)
iSmall, iBig := i.get()
if iBig != nil {
x, acc := bigintToInt64(iBig)
if acc != big.Exact {
return // inexact
}
return x, true
}
return i.small, true
return iSmall, true
}
// BigInt returns the value as a big.Int.
// The returned variable must not be modified by the client.
// BigInt returns a new big.Int with the same value as the Int.
func (i Int) BigInt() *big.Int {
if i.big != nil {
return i.big
iSmall, iBig := i.get()
if iBig != nil {
return new(big.Int).Set(iBig)
}
return newBig(i.small)
return big.NewInt(iSmall)
}
// bigInt returns the value as a big.Int.
// It differs from BigInt in that this method returns the actual
// reference and any modification will change the state of i.
func (i Int) bigInt() *big.Int {
iSmall, iBig := i.get()
if iBig != nil {
return iBig
}
return big.NewInt(iSmall)
}
// 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)
iSmall, iBig := i.get()
if iBig != nil {
x, acc := bigintToUint64(iBig)
if acc != big.Exact {
return // inexact
}
return x, true
}
if i.small < 0 {
if iSmall < 0 {
return // inexact
}
return uint64(i.small), true
return uint64(iSmall), true
}
// The math/big API should provide this function.
@@ -163,104 +163,145 @@ var (
)
func (i Int) Format(s fmt.State, ch rune) {
if i.big != nil {
i.big.Format(s, ch)
iSmall, iBig := i.get()
if iBig != nil {
iBig.Format(s, ch)
return
}
newBig(i.small).Format(s, ch)
big.NewInt(iSmall).Format(s, ch)
}
func (i Int) String() string {
if i.big != nil {
return i.big.Text(10)
iSmall, iBig := i.get()
if iBig != nil {
return iBig.Text(10)
}
return strconv.FormatInt(i.small, 10)
return strconv.FormatInt(iSmall, 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) {
iSmall, iBig := i.get()
var lo big.Word
if i.big != nil {
lo = i.big.Bits()[0]
if iBig != nil {
lo = iBig.Bits()[0]
} else {
lo = big.Word(i.small)
lo = big.Word(iSmall)
}
return 12582917 * uint32(lo+3), nil
}
func (x Int) CompareSameType(op syntax.Token, v Value, depth int) (bool, error) {
// Required by the TotallyOrdered interface
func (x Int) Cmp(v Value, depth int) (int, error) {
y := v.(Int)
if x.big != nil || y.big != nil {
return threeway(op, x.BigInt().Cmp(y.BigInt())), nil
xSmall, xBig := x.get()
ySmall, yBig := y.get()
if xBig != nil || yBig != nil {
return x.bigInt().Cmp(y.bigInt()), nil
}
return threeway(op, signum64(x.small-y.small)), nil
return signum64(xSmall - ySmall), nil // safe: int32 operands
}
// Float returns the float value nearest i.
func (i Int) Float() Float {
if i.big != nil {
f, _ := new(big.Float).SetInt(i.big).Float64()
iSmall, iBig := i.get()
if iBig != nil {
// Fast path for hardware int-to-float conversions.
if iBig.IsUint64() {
return Float(iBig.Uint64())
} else if iBig.IsInt64() {
return Float(iBig.Int64())
}
f, _ := new(big.Float).SetInt(iBig).Float64()
return Float(f)
}
return Float(i.small)
return Float(iSmall)
}
// finiteFloat returns the finite float value nearest i,
// or an error if the magnitude is too large.
func (i Int) finiteFloat() (Float, error) {
f := i.Float()
if math.IsInf(float64(f), 0) {
return 0, fmt.Errorf("int too large to convert to float")
}
return f, nil
}
func (x Int) Sign() int {
if x.big != nil {
return x.big.Sign()
xSmall, xBig := x.get()
if xBig != nil {
return xBig.Sign()
}
return signum64(x.small)
return signum64(xSmall)
}
func (x Int) Add(y Int) Int {
if x.big != nil || y.big != nil {
return MakeBigInt(new(big.Int).Add(x.BigInt(), y.BigInt()))
xSmall, xBig := x.get()
ySmall, yBig := y.get()
if xBig != nil || yBig != nil {
return MakeBigInt(new(big.Int).Add(x.bigInt(), y.bigInt()))
}
return MakeInt64(x.small + y.small)
return MakeInt64(xSmall + ySmall)
}
func (x Int) Sub(y Int) Int {
if x.big != nil || y.big != nil {
return MakeBigInt(new(big.Int).Sub(x.BigInt(), y.BigInt()))
xSmall, xBig := x.get()
ySmall, yBig := y.get()
if xBig != nil || yBig != nil {
return MakeBigInt(new(big.Int).Sub(x.bigInt(), y.bigInt()))
}
return MakeInt64(x.small - y.small)
return MakeInt64(xSmall - ySmall)
}
func (x Int) Mul(y Int) Int {
if x.big != nil || y.big != nil {
return MakeBigInt(new(big.Int).Mul(x.BigInt(), y.BigInt()))
xSmall, xBig := x.get()
ySmall, yBig := y.get()
if xBig != nil || yBig != nil {
return MakeBigInt(new(big.Int).Mul(x.bigInt(), y.bigInt()))
}
return MakeInt64(x.small * y.small)
return MakeInt64(xSmall * ySmall)
}
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())}
xSmall, xBig := x.get()
ySmall, yBig := y.get()
if xBig != nil || yBig != nil {
return MakeBigInt(new(big.Int).Or(x.bigInt(), y.bigInt()))
}
return Int{small: x.small | y.small}
return makeSmallInt(xSmall | ySmall)
}
func (x Int) And(y Int) Int {
if x.big != nil || y.big != nil {
return MakeBigInt(new(big.Int).And(x.BigInt(), y.BigInt()))
xSmall, xBig := x.get()
ySmall, yBig := y.get()
if xBig != nil || yBig != nil {
return MakeBigInt(new(big.Int).And(x.bigInt(), y.bigInt()))
}
return Int{small: x.small & y.small}
return makeSmallInt(xSmall & ySmall)
}
func (x Int) Xor(y Int) Int {
if x.big != nil || y.big != nil {
return MakeBigInt(new(big.Int).Xor(x.BigInt(), y.BigInt()))
xSmall, xBig := x.get()
ySmall, yBig := y.get()
if xBig != nil || yBig != nil {
return MakeBigInt(new(big.Int).Xor(x.bigInt(), y.bigInt()))
}
return Int{small: x.small ^ y.small}
return makeSmallInt(xSmall ^ ySmall)
}
func (x Int) Not() Int {
if x.big != nil {
return MakeBigInt(new(big.Int).Not(x.big))
xSmall, xBig := x.get()
if xBig != nil {
return MakeBigInt(new(big.Int).Not(xBig))
}
return Int{small: ^x.small}
return makeSmallInt(^xSmall)
}
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)) }
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 {
xSmall, xBig := x.get()
ySmall, yBig := y.get()
// 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()
if xBig != nil || yBig != nil {
xb, yb := x.bigInt(), y.bigInt()
var quo, rem big.Int
quo.QuoRem(xb, yb, &rem)
@@ -269,9 +310,9 @@ func (x Int) Div(y Int) Int {
}
return MakeBigInt(&quo)
}
quo := x.small / y.small
rem := x.small % y.small
if (x.small < 0) != (y.small < 0) && rem != 0 {
quo := xSmall / ySmall
rem := xSmall % ySmall
if (xSmall < 0) != (ySmall < 0) && rem != 0 {
quo -= 1
}
return MakeInt64(quo)
@@ -279,8 +320,10 @@ func (x Int) Div(y Int) Int {
// Precondition: y is nonzero.
func (x Int) Mod(y Int) Int {
if x.big != nil || y.big != nil {
xb, yb := x.BigInt(), y.BigInt()
xSmall, xBig := x.get()
ySmall, yBig := y.get()
if xBig != nil || yBig != nil {
xb, yb := x.bigInt(), y.bigInt()
var quo, rem big.Int
quo.QuoRem(xb, yb, &rem)
@@ -289,18 +332,19 @@ func (x Int) Mod(y Int) Int {
}
return MakeBigInt(&rem)
}
rem := x.small % y.small
if (x.small < 0) != (y.small < 0) && rem != 0 {
rem += y.small
rem := xSmall % ySmall
if (xSmall < 0) != (ySmall < 0) && rem != 0 {
rem += ySmall
}
return Int{small: rem}
return makeSmallInt(rem)
}
func (i Int) rational() *big.Rat {
if i.big != nil {
return new(big.Rat).SetInt(i.big)
iSmall, iBig := i.get()
if iBig != nil {
return new(big.Rat).SetInt(iBig)
}
return new(big.Rat).SetInt64(i.small)
return new(big.Rat).SetInt64(iSmall)
}
// AsInt32 returns the value of x if is representable as an int32.
@@ -309,10 +353,66 @@ func AsInt32(x Value) (int, error) {
if !ok {
return 0, fmt.Errorf("got %s, want int", x.Type())
}
if i.big != nil {
iSmall, iBig := i.get()
if iBig != nil {
return 0, fmt.Errorf("%s out of range", i)
}
return int(i.small), nil
return int(iSmall), nil
}
// AsInt sets *ptr to the value of Starlark int x, if it is exactly representable,
// otherwise it returns an error.
// The type of ptr must be one of the pointer types *int, *int8, *int16, *int32, or *int64,
// or one of their unsigned counterparts including *uintptr.
func AsInt(x Value, ptr interface{}) error {
xint, ok := x.(Int)
if !ok {
return fmt.Errorf("got %s, want int", x.Type())
}
bits := reflect.TypeOf(ptr).Elem().Size() * 8
switch ptr.(type) {
case *int, *int8, *int16, *int32, *int64:
i, ok := xint.Int64()
if !ok || bits < 64 && !(-1<<(bits-1) <= i && i < 1<<(bits-1)) {
return fmt.Errorf("%s out of range (want value in signed %d-bit range)", xint, bits)
}
switch ptr := ptr.(type) {
case *int:
*ptr = int(i)
case *int8:
*ptr = int8(i)
case *int16:
*ptr = int16(i)
case *int32:
*ptr = int32(i)
case *int64:
*ptr = int64(i)
}
case *uint, *uint8, *uint16, *uint32, *uint64, *uintptr:
i, ok := xint.Uint64()
if !ok || bits < 64 && i >= 1<<bits {
return fmt.Errorf("%s out of range (want value in unsigned %d-bit range)", xint, bits)
}
switch ptr := ptr.(type) {
case *uint:
*ptr = uint(i)
case *uint8:
*ptr = uint8(i)
case *uint16:
*ptr = uint16(i)
case *uint32:
*ptr = uint32(i)
case *uint64:
*ptr = uint64(i)
case *uintptr:
*ptr = uintptr(i)
}
default:
panic(fmt.Sprintf("invalid argument type: %T", ptr))
}
return nil
}
// NumberToInt converts a number x to an integer value.
@@ -338,7 +438,9 @@ func NumberToInt(x Value) (Int, error) {
// 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 {
// We avoid '<= MaxInt64' so that both constants are exactly representable as floats.
// See https://github.com/google/starlark-go/issues/375.
if math.MinInt64 <= f && f < math.MaxInt64+1 {
// small values
return MakeInt64(int64(f))
}

34
vendor/go.starlark.net/starlark/int_generic.go generated vendored Normal file
View File

@@ -0,0 +1,34 @@
//go:build (!linux && !darwin && !dragonfly && !freebsd && !netbsd && !solaris) || (!amd64 && !arm64 && !mips64x && !ppc64x && !loong64)
// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!solaris !amd64,!arm64,!mips64x,!ppc64x,!loong64
package starlark
// generic Int implementation as a union
import "math/big"
type intImpl 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
}
// --- low-level accessors ---
// get returns the small and big components of the Int.
// small is defined only if big is nil.
// small is sign-extended to 64 bits for ease of subsequent arithmetic.
func (i Int) get() (small int64, big *big.Int) {
return i.impl.small_, i.impl.big_
}
// Precondition: math.MinInt32 <= x && x <= math.MaxInt32
func makeSmallInt(x int64) Int {
return Int{intImpl{small_: x}}
}
// Precondition: x cannot be represented as int32.
func makeBigInt(x *big.Int) Int {
return Int{intImpl{big_: x}}
}

91
vendor/go.starlark.net/starlark/int_posix64.go generated vendored Normal file
View File

@@ -0,0 +1,91 @@
//go:build (linux || darwin || dragonfly || freebsd || netbsd || solaris) && (amd64 || arm64 || mips64x || ppc64x || loong64)
// +build linux darwin dragonfly freebsd netbsd solaris
// +build amd64 arm64 mips64x ppc64x loong64
package starlark
// This file defines an optimized Int implementation for 64-bit machines
// running POSIX. It reserves a 4GB portion of the address space using
// mmap and represents int32 values as addresses within that range. This
// disambiguates int32 values from *big.Int pointers, letting all Int
// values be represented as an unsafe.Pointer, so that Int-to-Value
// interface conversion need not allocate.
// Although iOS (which, like macOS, appears as darwin/arm64) is
// POSIX-compliant, it limits each process to about 700MB of virtual
// address space, which defeats the optimization. Similarly,
// OpenBSD's default ulimit for virtual memory is a measly GB or so.
// On both those platforms the attempted optimization will fail and
// fall back to the slow implementation.
// An alternative approach to this optimization would be to embed the
// int32 values in pointers using odd values, which can be distinguished
// from (even) *big.Int pointers. However, the Go runtime does not allow
// user programs to manufacture pointers to arbitrary locations such as
// within the zero page, or non-span, non-mmap, non-stack locations,
// and it may panic if it encounters them; see Issue #382.
import (
"log"
"math"
"math/big"
"unsafe"
"golang.org/x/sys/unix"
)
// intImpl represents a union of (int32, *big.Int) in a single pointer,
// so that Int-to-Value conversions need not allocate.
//
// The pointer is either a *big.Int, if the value is big, or a pointer into a
// reserved portion of the address space (smallints), if the value is small
// and the address space allocation succeeded.
//
// See int_generic.go for the basic representation concepts.
type intImpl unsafe.Pointer
// get returns the (small, big) arms of the union.
func (i Int) get() (int64, *big.Int) {
if smallints == 0 {
// optimization disabled
if x := (*big.Int)(i.impl); isSmall(x) {
return x.Int64(), nil
} else {
return 0, x
}
}
if ptr := uintptr(i.impl); ptr >= smallints && ptr < smallints+1<<32 {
return math.MinInt32 + int64(ptr-smallints), nil
}
return 0, (*big.Int)(i.impl)
}
// Precondition: math.MinInt32 <= x && x <= math.MaxInt32
func makeSmallInt(x int64) Int {
if smallints == 0 {
// optimization disabled
return Int{intImpl(big.NewInt(x))}
}
return Int{intImpl(uintptr(x-math.MinInt32) + smallints)}
}
// Precondition: x cannot be represented as int32.
func makeBigInt(x *big.Int) Int { return Int{intImpl(x)} }
// smallints is the base address of a 2^32 byte memory region.
// Pointers to addresses in this region represent int32 values.
// We assume smallints is not at the very top of the address space.
//
// Zero means the optimization is disabled and all Ints allocate a big.Int.
var smallints = reserveAddresses(1 << 32)
func reserveAddresses(len int) uintptr {
b, err := unix.Mmap(-1, 0, len, unix.PROT_READ, unix.MAP_PRIVATE|unix.MAP_ANON)
if err != nil {
log.Printf("Starlark failed to allocate 4GB address space: %v. Integer performance may suffer.", err)
return 0 // optimization disabled
}
return uintptr(unsafe.Pointer(&b[0]))
}

View File

@@ -5,6 +5,8 @@ package starlark
import (
"fmt"
"os"
"sync/atomic"
"unsafe"
"go.starlark.net/internal/compile"
"go.starlark.net/internal/spell"
@@ -19,6 +21,9 @@ const vmdebug = false // TODO(adonovan): use a bitfield of specific kinds of err
// - opt: record MaxIterStack during compilation and preallocate the stack.
func (fn *Function) CallInternal(thread *Thread, args Tuple, kwargs []Tuple) (Value, error) {
// Postcondition: args is not mutated. This is stricter than required by Callable,
// but allows CALL to avoid a copy.
if !resolve.AllowRecursion {
// detect recursion
for _, fr := range thread.stack[:len(thread.stack)-1] {
@@ -76,12 +81,36 @@ func (fn *Function) CallInternal(thread *Thread, args Tuple, kwargs []Tuple) (Va
var iterstack []Iterator // stack of active iterators
// Use defer so that application panics can pass through
// interpreter without leaving thread in a bad state.
defer func() {
// ITERPOP the rest of the iterator stack.
for _, iter := range iterstack {
iter.Done()
}
fr.locals = nil
}()
sp := 0
var pc uint32
var result Value
code := f.Code
loop:
for {
thread.Steps++
if thread.Steps >= thread.maxSteps {
if thread.OnMaxSteps != nil {
thread.OnMaxSteps(thread)
} else {
thread.Cancel("too many steps")
}
}
if reason := atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&thread.cancelReason))); reason != nil {
err = fmt.Errorf("Starlark computation cancelled: %s", *(*string)(reason))
break loop
}
fr.pc = pc
op := compile.Opcode(code[pc])
@@ -206,6 +235,34 @@ loop:
stack[sp] = z
sp++
case compile.INPLACE_PIPE:
y := stack[sp-1]
x := stack[sp-2]
sp -= 2
// It's possible that y is not Dict but
// nonetheless defines x|y, in which case we
// should fall back to the general case.
var z Value
if xdict, ok := x.(*Dict); ok {
if ydict, ok := y.(*Dict); ok {
if err = xdict.ht.checkMutable("apply |= to"); err != nil {
break loop
}
xdict.ht.addAll(&ydict.ht) // can't fail
z = xdict
}
}
if z == nil {
z, err = Binary(syntax.PIPE, x, y)
if err != nil {
break loop
}
}
stack[sp] = z
sp++
case compile.NONE:
stack[sp] = None
sp++
@@ -276,9 +333,15 @@ loop:
// positional args
var positional Tuple
if npos := int(arg >> 8); npos > 0 {
positional = make(Tuple, npos)
positional = stack[sp-npos : sp]
sp -= npos
copy(positional, stack[sp:])
// Copy positional arguments into a new array,
// unless the callee is another Starlark function,
// in which case it can be trusted not to mutate them.
if _, ok := stack[sp-1].(*Function); !ok || args != nil {
positional = append(Tuple(nil), positional...)
}
}
if args != nil {
// Add elements from *args sequence.
@@ -527,11 +590,9 @@ loop:
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.SETLOCALCELL:
locals[arg].(*cell).v = stack[sp-1]
sp--
case compile.SETGLOBAL:
fn.module.globals[arg] = stack[sp-1]
@@ -550,9 +611,23 @@ loop:
stack[sp] = fn.freevars[arg]
sp++
case compile.CELL:
x := stack[sp-1]
stack[sp-1] = x.(*cell).v
case compile.LOCALCELL:
v := locals[arg].(*cell).v
if v == nil {
err = fmt.Errorf("local variable %s referenced before assignment", f.Locals[arg].Name)
break loop
}
stack[sp] = v
sp++
case compile.FREECELL:
v := fn.freevars[arg].(*cell).v
if v == nil {
err = fmt.Errorf("local variable %s referenced before assignment", f.Freevars[arg].Name)
break loop
}
stack[sp] = v
sp++
case compile.GLOBAL:
x := fn.module.globals[arg]
@@ -582,14 +657,7 @@ loop:
break loop
}
}
// ITERPOP the rest of the iterator stack.
for _, iter := range iterstack {
iter.Done()
}
fr.locals = nil
// (deferred cleanup runs here)
return result, err
}
@@ -621,7 +689,7 @@ 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.
// Cells are always accessed using indirect {FREE,LOCAL,SETLOCAL}CELL instructions.
// The FreeVars tuple contains only cells.
// The FREE instruction always yields a cell.
type cell struct{ v Value }

View File

@@ -12,6 +12,7 @@ package starlark
import (
"errors"
"fmt"
"math"
"math/big"
"os"
"sort"
@@ -38,15 +39,17 @@ func init() {
"None": None,
"True": True,
"False": False,
"abs": NewBuiltin("abs", abs),
"any": NewBuiltin("any", any),
"all": NewBuiltin("all", all),
"bool": NewBuiltin("bool", bool_),
"bytes": NewBuiltin("bytes", bytes_),
"chr": NewBuiltin("chr", chr),
"dict": NewBuiltin("dict", dict),
"dir": NewBuiltin("dir", dir),
"enumerate": NewBuiltin("enumerate", enumerate),
"fail": NewBuiltin("fail", fail),
"float": NewBuiltin("float", float), // requires resolve.AllowFloat
"float": NewBuiltin("float", float),
"getattr": NewBuiltin("getattr", getattr),
"hasattr": NewBuiltin("hasattr", hasattr),
"hash": NewBuiltin("hash", hash),
@@ -72,6 +75,10 @@ func init() {
// methods of built-in types
// https://github.com/google/starlark-go/blob/master/doc/spec.md#built-in-methods
var (
bytesMethods = map[string]*Builtin{
"elems": NewBuiltin("elems", bytes_elems),
}
dictMethods = map[string]*Builtin{
"clear": NewBuiltin("clear", dict_clear),
"get": NewBuiltin("get", dict_get),
@@ -116,6 +123,8 @@ var (
"lower": NewBuiltin("lower", string_lower),
"lstrip": NewBuiltin("lstrip", string_strip), // sic
"partition": NewBuiltin("partition", string_partition),
"removeprefix": NewBuiltin("removeprefix", string_removefix),
"removesuffix": NewBuiltin("removesuffix", string_removefix),
"replace": NewBuiltin("replace", string_replace),
"rfind": NewBuiltin("rfind", string_rfind),
"rindex": NewBuiltin("rindex", string_rindex),
@@ -154,6 +163,25 @@ func builtinAttrNames(methods map[string]*Builtin) []string {
// ---- built-in functions ----
// https://github.com/google/starlark-go/blob/master/doc/spec.md#abs
func abs(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var x Value
if err := UnpackPositionalArgs("abs", args, kwargs, 1, &x); err != nil {
return nil, err
}
switch x := x.(type) {
case Float:
return Float(math.Abs(float64(x))), nil
case Int:
if x.Sign() >= 0 {
return x, nil
}
return zero.Sub(x), nil
default:
return nil, fmt.Errorf("got %s, want int or float", x.Type())
}
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#all
func all(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var iterable Iterable
@@ -197,6 +225,45 @@ func bool_(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error
return x.Truth(), nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#bytes
func bytes_(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
if len(kwargs) > 0 {
return nil, fmt.Errorf("bytes does not accept keyword arguments")
}
if len(args) != 1 {
return nil, fmt.Errorf("bytes: got %d arguments, want exactly 1", len(args))
}
switch x := args[0].(type) {
case Bytes:
return x, nil
case String:
// Invalid encodings are replaced by that of U+FFFD.
return Bytes(utf8Transcode(string(x))), nil
case Iterable:
// iterable of numeric byte values
var buf strings.Builder
if n := Len(x); n >= 0 {
// common case: known length
buf.Grow(n)
}
iter := x.Iterate()
defer iter.Done()
var elem Value
var b byte
for i := 0; iter.Next(&elem); i++ {
if err := AsInt(elem, &b); err != nil {
return nil, fmt.Errorf("bytes: at index %d, %s", i, err)
}
buf.WriteByte(b)
}
return Bytes(buf.String()), nil
default:
// Unlike string(foo), which stringifies it, bytes(foo) is an error.
return nil, fmt.Errorf("bytes: got %s, want string, bytes, or iterable of ints", x.Type())
}
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#chr
func chr(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
if len(kwargs) > 0 {
@@ -207,7 +274,7 @@ func chr(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error)
}
i, err := AsInt32(args[0])
if err != nil {
return nil, fmt.Errorf("chr: got %s, want int", args[0].Type())
return nil, fmt.Errorf("chr: %s", err)
}
if i < 0 {
return nil, fmt.Errorf("chr: Unicode code point %d out of range (<0)", i)
@@ -215,7 +282,7 @@ func chr(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error)
if i > unicode.MaxRune {
return nil, fmt.Errorf("chr: Unicode code point U+%X out of range (>0x10FFFF)", i)
}
return String(string(i)), nil
return String(string(rune(i))), nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict
@@ -260,9 +327,6 @@ func enumerate(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, e
}
iter := iterable.Iterate()
if iter == nil {
return nil, fmt.Errorf("enumerate: got %s, want iterable", iterable.Type())
}
defer iter.Done()
var pairs []Value
@@ -330,13 +394,39 @@ func float(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error
return Float(0.0), nil
}
case Int:
return x.Float(), nil
return x.finiteFloat()
case Float:
return x, nil
case String:
f, err := strconv.ParseFloat(string(x), 64)
if x == "" {
return nil, fmt.Errorf("float: empty string")
}
// +/- NaN or Inf or Infinity (case insensitive)?
s := string(x)
switch x[len(x)-1] {
case 'y', 'Y':
if strings.EqualFold(s, "infinity") || strings.EqualFold(s, "+infinity") {
return inf, nil
} else if strings.EqualFold(s, "-infinity") {
return neginf, nil
}
case 'f', 'F':
if strings.EqualFold(s, "inf") || strings.EqualFold(s, "+inf") {
return inf, nil
} else if strings.EqualFold(s, "-inf") {
return neginf, nil
}
case 'n', 'N':
if strings.EqualFold(s, "nan") || strings.EqualFold(s, "+nan") || strings.EqualFold(s, "-nan") {
return nan, nil
}
}
f, err := strconv.ParseFloat(s, 64)
if math.IsInf(f, 0) {
return nil, fmt.Errorf("floating-point number too large")
}
if err != nil {
return nil, nameErr(b, err)
return nil, fmt.Errorf("invalid float literal: %s", s)
}
return Float(f), nil
default:
@@ -344,6 +434,12 @@ func float(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error
}
}
var (
inf = Float(math.Inf(+1))
neginf = Float(math.Inf(-1))
nan = Float(math.NaN())
)
// https://github.com/google/starlark-go/blob/master/doc/spec.md#getattr
func getattr(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var object, dflt Value
@@ -400,19 +496,27 @@ func hasattr(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, err
// https://github.com/google/starlark-go/blob/master/doc/spec.md#hash
func hash(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var s string
if err := UnpackPositionalArgs("hash", args, kwargs, 1, &s); err != nil {
var x Value
if err := UnpackPositionalArgs("hash", args, kwargs, 1, &x); err != nil {
return nil, err
}
// The Starlark spec requires that the hash function be
// deterministic across all runs, motivated by the need
// for reproducibility of builds. Thus we cannot call
// String.Hash, which uses the fastest implementation
// available, because as varies across process restarts,
// and may evolve with the implementation.
return MakeInt(int(javaStringHash(s))), nil
var h int64
switch x := x.(type) {
case String:
// The Starlark spec requires that the hash function be
// deterministic across all runs, motivated by the need
// for reproducibility of builds. Thus we cannot call
// String.Hash, which uses the fastest implementation
// available, because as varies across process restarts,
// and may evolve with the implementation.
h = int64(javaStringHash(string(x)))
case Bytes:
h = int64(softHashString(string(x))) // FNV32
default:
return nil, fmt.Errorf("hash: got %s, want string or bytes", x.Type())
}
return MakeInt64(h), nil
}
// javaStringHash returns the same hash as would be produced by
@@ -440,95 +544,23 @@ func int_(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error)
return nil, err
}
// "If x is not a number or base is given, x must be a string."
if s, ok := AsString(x); ok {
b := 10
if base != nil {
var err error
b, err = AsInt32(base)
if err != nil || b != 0 && (b < 2 || b > 36) {
if err != nil {
return nil, fmt.Errorf("int: for base, got %s, want int", base.Type())
}
if b != 0 && (b < 2 || b > 36) {
return nil, fmt.Errorf("int: base must be an integer >= 2 && <= 36")
}
}
orig := s // save original for error message
// remove sign
var neg bool
if s != "" {
if s[0] == '+' {
s = s[1:]
} else if s[0] == '-' {
neg = true
s = s[1:]
}
res := parseInt(s, b)
if res == nil {
return nil, fmt.Errorf("int: invalid literal with base %d: %s", b, s)
}
// remove base prefix
baseprefix := 0
if len(s) > 1 && s[0] == '0' {
if len(s) > 2 {
switch s[1] {
case 'o', 'O':
s = s[2:]
baseprefix = 8
case 'x', 'X':
s = s[2:]
baseprefix = 16
case 'b', 'B':
s = s[2:]
baseprefix = 2
}
}
// For automatic base detection,
// a string starting with zero
// must be all zeros.
// Thus we reject int("0755", 0).
if baseprefix == 0 && b == 0 {
for i := 1; i < len(s); i++ {
if s[i] != '0' {
goto invalid
}
}
return zero, nil
}
if b != 0 && baseprefix != 0 && baseprefix != b {
// Explicit base doesn't match prefix,
// e.g. int("0o755", 16).
goto invalid
}
}
// select base
if b == 0 {
if baseprefix != 0 {
b = baseprefix
} else {
b = 10
}
}
// we explicitly handled sign above.
// if a sign remains, it is invalid.
if s != "" && (s[0] == '-' || s[0] == '+') {
goto invalid
}
// s has no sign or base prefix.
//
// int(x) permits arbitrary precision, unlike the scanner.
if i, ok := new(big.Int).SetString(s, b); ok {
res := MakeBigInt(i)
if neg {
res = zero.Sub(res)
}
return res, nil
}
invalid:
return nil, fmt.Errorf("int: invalid literal with base %d: %s", b, orig)
return res, nil
}
if base != nil {
@@ -550,6 +582,76 @@ func int_(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error)
return i, nil
}
// parseInt defines the behavior of int(string, base=int). It returns nil on error.
func parseInt(s string, base int) Value {
// remove sign
var neg bool
if s != "" {
if s[0] == '+' {
s = s[1:]
} else if s[0] == '-' {
neg = true
s = s[1:]
}
}
// remove optional base prefix
baseprefix := 0
if len(s) > 1 && s[0] == '0' {
if len(s) > 2 {
switch s[1] {
case 'o', 'O':
baseprefix = 8
case 'x', 'X':
baseprefix = 16
case 'b', 'B':
baseprefix = 2
}
}
if baseprefix != 0 {
// Remove the base prefix if it matches
// the explicit base, or if base=0.
if base == 0 || baseprefix == base {
base = baseprefix
s = s[2:]
}
} else {
// For automatic base detection,
// a string starting with zero
// must be all zeros.
// Thus we reject int("0755", 0).
if base == 0 {
for i := 1; i < len(s); i++ {
if s[i] != '0' {
return nil
}
}
return zero
}
}
}
if base == 0 {
base = 10
}
// we explicitly handled sign above.
// if a sign remains, it is invalid.
if s != "" && (s[0] == '-' || s[0] == '+') {
return nil
}
// s has no sign or base prefix.
if i, ok := new(big.Int).SetString(s, base); ok {
res := MakeBigInt(i)
if neg {
res = zero.Sub(res)
}
return res
}
return nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#len
func len_(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var x Value
@@ -660,16 +762,26 @@ func ord(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error)
if len(args) != 1 {
return nil, fmt.Errorf("ord: got %d arguments, want 1", len(args))
}
s, ok := AsString(args[0])
if !ok {
return nil, fmt.Errorf("ord: got %s, want string", args[0].Type())
switch x := args[0].(type) {
case String:
// ord(string) returns int value of sole rune.
s := string(x)
r, sz := utf8.DecodeRuneInString(s)
if sz == 0 || sz != len(s) {
n := utf8.RuneCountInString(s)
return nil, fmt.Errorf("ord: string encodes %d Unicode code points, want 1", n)
}
return MakeInt(int(r)), nil
case Bytes:
// ord(bytes) returns int value of sole byte.
if len(x) != 1 {
return nil, fmt.Errorf("ord: bytes has length %d, want 1", len(x))
}
return MakeInt(int(x[0])), nil
default:
return nil, fmt.Errorf("ord: got %s, want string or bytes", x.Type())
}
r, sz := utf8.DecodeRuneInString(s)
if sz == 0 || sz != len(s) {
n := utf8.RuneCountInString(s)
return nil, fmt.Errorf("ord: string encodes %d Unicode code points, want 1", n)
}
return MakeInt(int(r)), nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#print
@@ -685,6 +797,8 @@ func print(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error
}
if s, ok := AsString(v); ok {
buf.WriteString(s)
} else if b, ok := v.(Bytes); ok {
buf.WriteString(string(b))
} else {
writeValue(buf, v, nil)
}
@@ -707,7 +821,6 @@ func range_(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, erro
return nil, err
}
// TODO(adonovan): analyze overflow/underflows cases for 32-bit implementations.
if len(args) == 1 {
// range(stop)
start, stop = 0, start
@@ -963,11 +1076,29 @@ func str(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error)
if len(args) != 1 {
return nil, fmt.Errorf("str: got %d arguments, want exactly 1", len(args))
}
x := args[0]
if _, ok := AsString(x); !ok {
x = String(x.String())
switch x := args[0].(type) {
case String:
return x, nil
case Bytes:
// Invalid encodings are replaced by that of U+FFFD.
return String(utf8Transcode(string(x))), nil
default:
return String(x.String()), nil
}
return x, nil
}
// utf8Transcode returns the UTF-8-to-UTF-8 transcoding of s.
// The effect is that each code unit that is part of an
// invalid sequence is replaced by U+FFFD.
func utf8Transcode(s string) string {
if utf8.ValidString(s) {
return s
}
var out strings.Builder
for _, r := range s {
out.WriteRune(r)
}
return out.String()
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#tuple
@@ -1344,13 +1475,51 @@ func string_iterable(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value,
if err := UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil {
return nil, err
}
return stringIterable{
s: b.Receiver().(String),
ords: b.Name()[len(b.Name())-2] == 'd',
codepoints: b.Name()[0] == 'c',
}, nil
s := b.Receiver().(String)
ords := b.Name()[len(b.Name())-2] == 'd'
codepoints := b.Name()[0] == 'c'
if codepoints {
return stringCodepoints{s, ords}, nil
} else {
return stringElems{s, ords}, nil
}
}
// bytes_elems returns an unspecified iterable value whose
// iterator yields the int values of successive elements.
func bytes_elems(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
if err := UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil {
return nil, err
}
return bytesIterable{b.Receiver().(Bytes)}, nil
}
// A bytesIterable is an iterable returned by bytes.elems(),
// whose iterator yields a sequence of numeric bytes values.
type bytesIterable struct{ bytes Bytes }
var _ Iterable = (*bytesIterable)(nil)
func (bi bytesIterable) String() string { return bi.bytes.String() + ".elems()" }
func (bi bytesIterable) Type() string { return "bytes.elems" }
func (bi bytesIterable) Freeze() {} // immutable
func (bi bytesIterable) Truth() Bool { return True }
func (bi bytesIterable) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", bi.Type()) }
func (bi bytesIterable) Iterate() Iterator { return &bytesIterator{bi.bytes} }
type bytesIterator struct{ bytes Bytes }
func (it *bytesIterator) Next(p *Value) bool {
if it.bytes == "" {
return false
}
*p = MakeInt(int(it.bytes[0]))
it.bytes = it.bytes[1:]
return true
}
func (*bytesIterator) Done() {}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#string·count
func string_count(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var sub string
@@ -1722,6 +1891,22 @@ func string_partition(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value,
return tuple, nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#string·removeprefix
// https://github.com/google/starlark-go/blob/master/doc/spec.md#string·removesuffix
func string_removefix(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
recv := string(b.Receiver().(String))
var fix string
if err := UnpackPositionalArgs(b.Name(), args, kwargs, 1, &fix); err != nil {
return nil, err
}
if b.name[len("remove")] == 'p' {
recv = strings.TrimPrefix(recv, fix)
} else {
recv = strings.TrimSuffix(recv, fix)
}
return String(recv), nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#string·replace
func string_replace(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
recv := string(b.Receiver().(String))

View File

@@ -8,37 +8,103 @@ import (
"log"
"reflect"
"strings"
"go.starlark.net/internal/spell"
)
// An Unpacker defines custom argument unpacking behavior.
// See UnpackArgs.
type Unpacker interface {
Unpack(v Value) error
}
// 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,
// If the variable is a bool, integer, 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.
// Predeclared Go integer types uses the AsInt check.
//
// If the parameter name ends with "?", it is optional.
//
// If the parameter name ends with "??", it is optional and treats the None value
// as if the argument was absent.
//
// If a parameter is marked optional, then all following parameters are
// implicitly optional where or not they are marked.
//
// If the variable implements Unpacker, its Unpack argument
// is called with the argument value, allowing an application
// to define its own argument validation and conversion.
//
// 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.
// Examples:
//
// var (
// a Value
// b = MakeInt(42)
// c Value = starlark.None
// )
//
// // 1. mixed parameters, like def f(a, b=42, c=None).
// err := UnpackArgs("f", args, kwargs, "a", &a, "b?", &b, "c?", &c)
//
// // 2. keyword parameters only, like def f(*, a, b, c=None).
// if len(args) > 0 {
// return fmt.Errorf("f: unexpected positional arguments")
// }
// err := UnpackArgs("f", args, kwargs, "a", &a, "b?", &b, "c?", &c)
//
// // 3. positional parameters only, like def f(a, b=42, c=None, /) in Python 3.8.
// err := UnpackPositionalArgs("f", args, kwargs, 1, &a, &b, &c)
//
// More complex forms such as def f(a, b=42, *args, c, d=123, **kwargs)
// require additional logic, but their need in built-ins is exceedingly rare.
//
// In the examples above, the declaration of b with type Int causes UnpackArgs
// to require that b's argument value, if provided, is also an int.
// To allow arguments of any type, while retaining the default value of 42,
// declare b as a Value:
//
// var b Value = MakeInt(42)
//
// The zero value of a variable of type Value, such as 'a' in the
// examples above, is not a valid Starlark value, so if the parameter is
// optional, the caller must explicitly handle the default case by
// interpreting nil as None or some computed default. The same is true
// for the zero values of variables of type *List, *Dict, Callable, or
// Iterable. For example:
//
// // def myfunc(d=None, e=[], f={})
// var (
// d Value
// e *List
// f *Dict
// )
// err := UnpackArgs("myfunc", args, kwargs, "d?", &d, "e?", &e, "f?", &f)
// if d == nil { d = None; }
// if e == nil { e = new(List); }
// if f == nil { f = new(Dict); }
//
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] == '?' {
paramName := func(x interface{}) (name string, skipNone bool) { // (no free variables)
name = x.(string)
if strings.HasSuffix(name, "??") {
name = strings.TrimSuffix(name, "??")
skipNone = true
} else if name[len(name)-1] == '?' {
name = name[:len(name)-1]
}
return name
return name, skipNone
}
// positional arguments
@@ -48,8 +114,13 @@ func UnpackArgs(fnname string, args Tuple, kwargs []Tuple, pairs ...interface{})
}
for i, arg := range args {
defined.set(i)
name, skipNone := paramName(pairs[2*i])
if skipNone {
if _, isNone := arg.(NoneType); isNone {
continue
}
}
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)
}
}
@@ -59,12 +130,20 @@ 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) {
pName, skipNone := paramName(pairs[2*i])
if pName == string(name) {
// found it
if defined.set(i) {
return fmt.Errorf("%s: got multiple values for keyword argument %s",
fnname, name)
}
if skipNone {
if _, isNone := arg.(NoneType); isNone {
continue kwloop
}
}
ptr := pairs[2*i+1]
if err := unpackOneArg(arg, ptr); err != nil {
return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err)
@@ -72,7 +151,16 @@ kwloop:
continue kwloop
}
}
return fmt.Errorf("%s: unexpected keyword argument %s", fnname, name)
err := fmt.Errorf("%s: unexpected keyword argument %s", fnname, name)
names := make([]string, 0, nparams)
for i := 0; i < nparams; i += 2 {
param, _ := paramName(pairs[i])
names = append(names, param)
}
if n := spell.Nearest(string(name), names); n != "" {
err = fmt.Errorf("%s (did you mean %s?)", err.Error(), n)
}
return err
}
// Check that all non-optional parameters are defined.
@@ -97,6 +185,8 @@ kwloop:
// 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.
//
// See UnpackArgs for general comments.
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)
@@ -127,6 +217,8 @@ func UnpackPositionalArgs(fnname string, args Tuple, kwargs []Tuple, min int, va
func unpackOneArg(v Value, ptr interface{}) error {
// On failure, don't clobber *ptr.
switch ptr := ptr.(type) {
case Unpacker:
return ptr.Unpack(v)
case *Value:
*ptr = v
case *string:
@@ -141,12 +233,15 @@ func unpackOneArg(v Value, ptr interface{}) error {
return fmt.Errorf("got %s, want bool", v.Type())
}
*ptr = bool(b)
case *int:
i, err := AsInt32(v)
if err != nil {
return err
case *int, *int8, *int16, *int32, *int64,
*uint, *uint8, *uint16, *uint32, *uint64, *uintptr:
return AsInt(v, ptr)
case *float64:
f, ok := v.(Float)
if !ok {
return fmt.Errorf("got %s, want float", v.Type())
}
*ptr = i
*ptr = float64(f)
case **List:
list, ok := v.(*List)
if !ok {
@@ -205,7 +300,9 @@ func unpackOneArg(v Value, ptr interface{}) error {
// Attempt to call Value.Type method.
func() {
defer func() { recover() }()
paramType = paramVar.MethodByName("Type").Call(nil)[0].String()
if typer, _ := paramVar.Interface().(interface{ Type() string }); typer != nil {
paramType = typer.Type()
}
}()
return fmt.Errorf("got %s, want %s", v.Type(), paramType)
}

View File

@@ -7,34 +7,35 @@
// Starlark values are represented by the Value interface.
// The following built-in Value types are known to the evaluator:
//
// NoneType -- NoneType
// Bool -- bool
// Int -- int
// Float -- float
// String -- string
// *List -- list
// Tuple -- tuple
// *Dict -- dict
// *Set -- set
// *Function -- function (implemented in Starlark)
// *Builtin -- builtin_function_or_method (function or method implemented in Go)
// NoneType -- NoneType
// Bool -- bool
// Bytes -- bytes
// Int -- int
// Float -- float
// String -- string
// *List -- list
// Tuple -- tuple
// *Dict -- dict
// *Set -- set
// *Function -- function (implemented in Starlark)
// *Builtin -- builtin_function_or_method (function or method implemented in Go)
//
// Client applications may define new data types that satisfy at least
// the Value interface. Such types may provide additional operations by
// implementing any of these optional interfaces:
//
// Callable -- value is callable like a function
// Comparable -- value defines its own comparison operations
// Iterable -- value is iterable using 'for' loops
// Sequence -- value is iterable sequence of known length
// Indexable -- value is sequence with efficient random access
// Mapping -- value maps from keys to values, like a dictionary
// HasBinary -- value defines binary operations such as * and +
// HasAttrs -- value has readable fields or methods x.f
// HasSetField -- value has settable fields x.f
// HasSetIndex -- value supports element update using x[i]=y
// HasSetKey -- value supports map update using x[k]=v
// HasUnary -- value defines unary operations such as + and -
// Callable -- value is callable like a function
// Comparable -- value defines its own comparison operations
// Iterable -- value is iterable using 'for' loops
// Sequence -- value is iterable sequence of known length
// Indexable -- value is sequence with efficient random access
// Mapping -- value maps from keys to values, like a dictionary
// HasBinary -- value defines binary operations such as * and +
// HasAttrs -- value has readable fields or methods x.f
// HasSetField -- value has settable fields x.f
// HasSetIndex -- value supports element update using x[i]=y
// HasSetKey -- value supports map update using x[k]=v
// HasUnary -- value defines unary operations such as + and -
//
// Client applications may also define domain-specific functions in Go
// and make them available to Starlark programs. Use NewBuiltin to
@@ -62,7 +63,6 @@
// through Starlark code and into callbacks. When evaluation fails it
// returns an EvalError from which the application may obtain a
// backtrace of active Starlark calls.
//
package starlark // import "go.starlark.net/starlark"
// This file defines the data types of Starlark and their basic operations.
@@ -131,16 +131,41 @@ type Comparable interface {
CompareSameType(op syntax.Token, y Value, depth int) (bool, error)
}
// A TotallyOrdered is a type whose values form a total order:
// if x and y are of the same TotallyOrdered type, then x must be less than y,
// greater than y, or equal to y.
//
// It is simpler than Comparable and should be preferred in new code,
// but if a type implements both interfaces, Comparable takes precedence.
type TotallyOrdered interface {
Value
// Cmp compares two values x and y of the same totally ordered type.
// It returns negative if x < y, positive if x > y, and zero if the values are equal.
//
// Implementations that recursively compare subcomponents of
// the value should use the CompareDepth function, not Cmp, to
// avoid infinite recursion on cyclic structures.
//
// The depth parameter is used to bound comparisons of cyclic
// data structures. Implementations should decrement depth
// before calling CompareDepth and should return an error if depth
// < 1.
//
// Client code should not call this method. Instead, use the
// standalone Compare or Equals functions, which are defined for
// all pairs of operands.
Cmp(y Value, depth int) (int, error)
}
var (
_ Comparable = None
_ Comparable = Int{}
_ Comparable = False
_ Comparable = Float(0)
_ Comparable = String("")
_ Comparable = (*Dict)(nil)
_ Comparable = (*List)(nil)
_ Comparable = Tuple(nil)
_ Comparable = (*Set)(nil)
_ TotallyOrdered = Int{}
_ TotallyOrdered = Float(0)
_ Comparable = False
_ Comparable = String("")
_ Comparable = (*Dict)(nil)
_ Comparable = (*List)(nil)
_ Comparable = Tuple(nil)
_ Comparable = (*Set)(nil)
)
// A Callable value f may be the operand of a function call, f(x).
@@ -229,13 +254,12 @@ var (
//
// Example usage:
//
// iter := iterable.Iterator()
// iter := iterable.Iterator()
// defer iter.Done()
// var x Value
// for iter.Next(&x) {
// ...
// }
//
type Iterator interface {
// If the iterator is exhausted, Next returns false.
// Otherwise it sets *p to the current element of the sequence,
@@ -276,7 +300,7 @@ type HasSetKey interface {
var _ HasSetKey = (*Dict)(nil)
// A HasBinary value may be used as either operand of these binary operators:
// + - * / // % in not in | & ^ << >>
// + - * / // % in not in | & ^ << >>
//
// The Side argument indicates whether the receiver is the left or right operand.
//
@@ -296,7 +320,7 @@ const (
)
// A HasUnary value may be used as the operand of these unary operators:
// + - ~
// + - ~
//
// An implementation may decline to handle an operation by returning (nil, nil).
// For this reason, clients should always call the standalone Unary(op, x)
@@ -354,9 +378,6 @@ func (NoneType) Type() string { return "NoneType" }
func (NoneType) Freeze() {} // immutable
func (NoneType) Truth() Bool { return False }
func (NoneType) Hash() (uint32, error) { return 0, nil }
func (NoneType) CompareSameType(op syntax.Token, y Value, depth int) (bool, error) {
return threeway(op, 0), nil
}
// Bool is the type of a Starlark bool.
type Bool bool
@@ -385,10 +406,47 @@ func (x Bool) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error
// Float is the type of a Starlark float.
type Float float64
func (f Float) String() string { return strconv.FormatFloat(float64(f), 'g', 6, 64) }
func (f Float) Type() string { return "float" }
func (f Float) Freeze() {} // immutable
func (f Float) Truth() Bool { return f != 0.0 }
func (f Float) String() string {
var buf strings.Builder
f.format(&buf, 'g')
return buf.String()
}
func (f Float) format(buf *strings.Builder, conv byte) {
ff := float64(f)
if !isFinite(ff) {
if math.IsInf(ff, +1) {
buf.WriteString("+inf")
} else if math.IsInf(ff, -1) {
buf.WriteString("-inf")
} else {
buf.WriteString("nan")
}
return
}
// %g is the default format used by str.
// It uses the minimum precision to avoid ambiguity,
// and always includes a '.' or an 'e' so that the value
// is self-evidently a float, not an int.
if conv == 'g' || conv == 'G' {
s := strconv.FormatFloat(ff, conv, -1, 64)
buf.WriteString(s)
// Ensure result always has a decimal point if no exponent.
// "123" -> "123.0"
if strings.IndexByte(s, conv-'g'+'e') < 0 && strings.IndexByte(s, '.') < 0 {
buf.WriteString(".0")
}
return
}
// %[eEfF] use 6-digit precision
buf.WriteString(strconv.FormatFloat(ff, conv, 6, 64))
}
func (f Float) Type() string { return "float" }
func (f Float) Freeze() {} // immutable
func (f Float) Truth() Bool { return f != 0.0 }
func (f Float) Hash() (uint32, error) {
// Equal float and int values must yield the same hash.
// TODO(adonovan): opt: if f is non-integral, and thus not equal
@@ -407,29 +465,36 @@ func isFinite(f float64) bool {
return math.Abs(f) <= math.MaxFloat64
}
func (x Float) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error) {
func (x Float) Cmp(y_ Value, depth int) (int, error) {
y := y_.(Float)
switch op {
case syntax.EQL:
return x == y, nil
case syntax.NEQ:
return x != y, nil
case syntax.LE:
return x <= y, nil
case syntax.LT:
return x < y, nil
case syntax.GE:
return x >= y, nil
case syntax.GT:
return x > y, nil
return floatCmp(x, y), nil
}
// floatCmp performs a three-valued comparison on floats,
// which are totally ordered with NaN > +Inf.
func floatCmp(x, y Float) int {
if x > y {
return +1
} else if x < y {
return -1
} else if x == y {
return 0
}
panic(op)
// At least one operand is NaN.
if x == x {
return -1 // y is NaN
} else if y == y {
return +1 // x is NaN
}
return 0 // both NaN
}
func (f Float) rational() *big.Rat { return new(big.Rat).SetFloat64(float64(f)) }
// AsFloat returns the float64 value closest to x.
// The f result is undefined if x is not a float or int.
// The f result is undefined if x is not a float or Int.
// The result may be infinite if x is a very large Int.
func AsFloat(x Value) (f float64, ok bool) {
switch x := x.(type) {
case Float:
@@ -440,7 +505,13 @@ func AsFloat(x Value) (f float64, ok bool) {
return 0, false
}
func (x Float) Mod(y Float) Float { return Float(math.Mod(float64(x), float64(y))) }
func (x Float) Mod(y Float) Float {
z := Float(math.Mod(float64(x), float64(y)))
if (x < 0) != (y < 0) && z != 0 {
z += y
}
return z
}
// Unary implements the operations +float and -float.
func (f Float) Unary(op syntax.Token) (Value, error) {
@@ -453,13 +524,20 @@ func (f Float) Unary(op syntax.Token) (Value, error) {
return nil, nil
}
// String is the type of a Starlark string.
// String is the type of a Starlark text string.
//
// A String encapsulates an an immutable sequence of bytes,
// but strings are not directly iterable. Instead, iterate
// over the result of calling one of these four methods:
// codepoints, codepoint_ords, elems, elem_ords.
//
// Strings typically contain text; use Bytes for binary strings.
// The Starlark spec defines text strings as sequences of UTF-k
// codes that encode Unicode code points. In this Go implementation,
// k=8, whereas in a Java implementation, k=16. For portability,
// operations on strings should aim to avoid assumptions about
// the value of k.
//
// Warning: the contract of the Value interface's String method is that
// it returns the value printed in Starlark notation,
// so s.String() or fmt.Sprintf("%s", s) returns a quoted string.
@@ -467,7 +545,7 @@ func (f Float) Unary(op syntax.Token) (Value, error) {
// of a Starlark string as a Go string.
type String string
func (s String) String() string { return strconv.Quote(string(s)) }
func (s String) String() string { return syntax.Quote(string(s), false) }
func (s String) GoString() string { return string(s) }
func (s String) Type() string { return "string" }
func (s String) Freeze() {} // immutable
@@ -499,73 +577,106 @@ func (x String) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, err
func AsString(x Value) (string, bool) { v, ok := x.(String); return string(v), ok }
// A stringIterable is an iterable whose iterator yields a sequence of
// either Unicode code points or elements (bytes),
// either numerically or as successive substrings.
type stringIterable struct {
s String
ords bool
codepoints bool
// A stringElems is an iterable whose iterator yields a sequence of
// elements (bytes), either numerically or as successive substrings.
// It is an indexable sequence.
type stringElems struct {
s String
ords bool
}
var _ Iterable = (*stringIterable)(nil)
var (
_ Iterable = (*stringElems)(nil)
_ Indexable = (*stringElems)(nil)
)
func (si stringIterable) String() string {
var etype string
if si.codepoints {
etype = "codepoint"
} else {
etype = "elem"
}
func (si stringElems) String() string {
if si.ords {
return si.s.String() + "." + etype + "_ords()"
return si.s.String() + ".elem_ords()"
} else {
return si.s.String() + "." + etype + "s()"
return si.s.String() + ".elems()"
}
}
func (si stringIterable) Type() string {
if si.codepoints {
return "codepoints"
func (si stringElems) Type() string { return "string.elems" }
func (si stringElems) Freeze() {} // immutable
func (si stringElems) Truth() Bool { return True }
func (si stringElems) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", si.Type()) }
func (si stringElems) Iterate() Iterator { return &stringElemsIterator{si, 0} }
func (si stringElems) Len() int { return len(si.s) }
func (si stringElems) Index(i int) Value {
if si.ords {
return MakeInt(int(si.s[i]))
} else {
return "elems"
// TODO(adonovan): opt: preallocate canonical 1-byte strings
// to avoid interface allocation.
return si.s[i : i+1]
}
}
func (si stringIterable) Freeze() {} // immutable
func (si stringIterable) Truth() Bool { return True }
func (si stringIterable) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", si.Type()) }
func (si stringIterable) Iterate() Iterator { return &stringIterator{si, 0} }
type stringIterator struct {
si stringIterable
type stringElemsIterator struct {
si stringElems
i int
}
func (it *stringIterator) Next(p *Value) bool {
func (it *stringElemsIterator) Next(p *Value) bool {
if it.i == len(it.si.s) {
return false
}
*p = it.si.Index(it.i)
it.i++
return true
}
func (*stringElemsIterator) Done() {}
// A stringCodepoints is an iterable whose iterator yields a sequence of
// Unicode code points, either numerically or as successive substrings.
// It is not indexable.
type stringCodepoints struct {
s String
ords bool
}
var _ Iterable = (*stringCodepoints)(nil)
func (si stringCodepoints) String() string {
if si.ords {
return si.s.String() + ".codepoint_ords()"
} else {
return si.s.String() + ".codepoints()"
}
}
func (si stringCodepoints) Type() string { return "string.codepoints" }
func (si stringCodepoints) Freeze() {} // immutable
func (si stringCodepoints) Truth() Bool { return True }
func (si stringCodepoints) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", si.Type()) }
func (si stringCodepoints) Iterate() Iterator { return &stringCodepointsIterator{si, 0} }
type stringCodepointsIterator struct {
si stringCodepoints
i int
}
func (it *stringCodepointsIterator) Next(p *Value) bool {
s := it.si.s[it.i:]
if s == "" {
return false
}
if it.si.codepoints {
r, sz := utf8.DecodeRuneInString(string(s))
if !it.si.ords {
r, sz := utf8.DecodeRuneInString(string(s))
if !it.si.ords {
if r == utf8.RuneError {
*p = String(r)
} else {
*p = s[:sz]
} else {
*p = MakeInt(int(r))
}
it.i += sz
} else {
b := int(s[0])
if !it.si.ords {
*p = s[:1]
} else {
*p = MakeInt(b)
}
it.i += 1
*p = MakeInt(int(r))
}
it.i += sz
return true
}
func (*stringIterator) Done() {}
func (*stringCodepointsIterator) Done() {}
// A Function is a function defined by a Starlark def statement or lambda expression.
// The initialization behavior of a Starlark module is also represented by a Function.
@@ -624,6 +735,34 @@ func (fn *Function) Param(i int) (string, syntax.Position) {
id := fn.funcode.Locals[i]
return id.Name, id.Pos
}
// ParamDefault returns the default value of the specified parameter
// (0 <= i < NumParams()), or nil if the parameter is not optional.
func (fn *Function) ParamDefault(i int) Value {
if i < 0 || i >= fn.NumParams() {
panic(i)
}
// fn.defaults omits all required params up to the first optional param. It
// also does not include *args or **kwargs at the end.
firstOptIdx := fn.NumParams() - len(fn.defaults)
if fn.HasVarargs() {
firstOptIdx--
}
if fn.HasKwargs() {
firstOptIdx--
}
if i < firstOptIdx || i >= firstOptIdx+len(fn.defaults) {
return nil
}
dflt := fn.defaults[i-firstOptIdx]
if _, ok := dflt.(mandatory); ok {
return nil
}
return dflt
}
func (fn *Function) HasVarargs() bool { return fn.funcode.HasVarargs }
func (fn *Function) HasKwargs() bool { return fn.funcode.HasKwargs }
@@ -667,13 +806,12 @@ func NewBuiltin(name string, fn func(thread *Thread, fn *Builtin, args Tuple, kw
// In the example below, the value of f is the string.index
// built-in method bound to the receiver value "abc":
//
// f = "abc".index; f("a"); f("b")
// f = "abc".index; f("a"); f("b")
//
// In the common case, the receiver is bound only during the call,
// but this still results in the creation of a temporary method closure:
//
// "abc".index("a")
//
// "abc".index("a")
func (b *Builtin) BindReceiver(recv Value) *Builtin {
return &Builtin{name: b.name, fn: b.fn, recv: recv}
}
@@ -708,6 +846,14 @@ func (d *Dict) Freeze() { d.ht.freeze()
func (d *Dict) Truth() Bool { return d.Len() > 0 }
func (d *Dict) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: dict") }
func (x *Dict) Union(y *Dict) *Dict {
z := new(Dict)
z.ht.init(x.Len()) // a lower bound
z.ht.addAll(&x.ht) // can't fail
z.ht.addAll(&y.ht) // can't fail
return z
}
func (d *Dict) Attr(name string) (Value, error) { return builtinAttr(d, name, dictMethods) }
func (d *Dict) AttrNames() []string { return builtinAttrNames(dictMethods) }
@@ -729,8 +875,8 @@ func dictsEqual(x, y *Dict, depth int) (bool, error) {
if x.Len() != y.Len() {
return false, nil
}
for _, xitem := range x.Items() {
key, xval := xitem[0], xitem[1]
for e := x.ht.head; e != nil; e = e.next {
key, xval := e.key, e.value
if yval, found, _ := y.Get(key); !found {
return false, nil
@@ -970,7 +1116,6 @@ func (s *Set) Len() int { return int(s.ht.len) }
func (s *Set) Iterate() Iterator { return s.ht.iterate() }
func (s *Set) String() string { return toString(s) }
func (s *Set) Type() string { return "set" }
func (s *Set) elems() []Value { return s.ht.keys() }
func (s *Set) Freeze() { s.ht.freeze() }
func (s *Set) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: set") }
func (s *Set) Truth() Bool { return s.Len() > 0 }
@@ -996,8 +1141,8 @@ func setsEqual(x, y *Set, depth int) (bool, error) {
if x.Len() != y.Len() {
return false, nil
}
for _, elem := range x.elems() {
if found, _ := y.Has(elem); !found {
for e := x.ht.head; e != nil; e = e.next {
if found, _ := y.Has(e.key); !found {
return false, nil
}
}
@@ -1006,8 +1151,8 @@ func setsEqual(x, y *Set, depth int) (bool, error) {
func (s *Set) Union(iter Iterator) (Value, error) {
set := new(Set)
for _, elem := range s.elems() {
set.Insert(elem) // can't fail
for e := s.ht.head; e != nil; e = e.next {
set.Insert(e.key) // can't fail
}
var x Value
for iter.Next(&x) {
@@ -1038,6 +1183,7 @@ func writeValue(out *strings.Builder, x Value, path []Value) {
case nil:
out.WriteString("<nil>") // indicates a bug
// These four cases are duplicates of T.String(), for efficiency.
case NoneType:
out.WriteString("None")
@@ -1052,7 +1198,7 @@ func writeValue(out *strings.Builder, x Value, path []Value) {
}
case String:
fmt.Fprintf(out, "%q", string(x))
out.WriteString(syntax.Quote(string(x), false))
case *List:
out.WriteByte('[')
@@ -1097,8 +1243,8 @@ func writeValue(out *strings.Builder, x Value, path []Value) {
out.WriteString("...") // dict contains itself
} else {
sep := ""
for _, item := range x.Items() {
k, v := item[0], item[1]
for e := x.ht.head; e != nil; e = e.next {
k, v := e.key, e.value
out.WriteString(sep)
writeValue(out, k, path)
out.WriteString(": ")
@@ -1110,11 +1256,11 @@ func writeValue(out *strings.Builder, x Value, path []Value) {
case *Set:
out.WriteString("set([")
for i, elem := range x.elems() {
if i > 0 {
for e := x.ht.head; e != nil; e = e.next {
if e != x.ht.head {
out.WriteString(", ")
}
writeValue(out, elem, path)
writeValue(out, e.key, path)
}
out.WriteString("])")
@@ -1132,14 +1278,16 @@ func pathContains(path []Value, x Value) bool {
return false
}
const maxdepth = 10
// CompareLimit is the depth limit on recursive comparison operations such as == and <.
// Comparison of data structures deeper than this limit may fail.
var CompareLimit = 10
// Equal reports whether two Starlark values are equal.
func Equal(x, y Value) (bool, error) {
if x, ok := x.(String); ok {
return x == y, nil // fast path for an important special case
}
return EqualDepth(x, y, maxdepth)
return EqualDepth(x, y, CompareLimit)
}
// EqualDepth reports whether two Starlark values are equal.
@@ -1158,7 +1306,7 @@ func EqualDepth(x, y Value, depth int) (bool, error) {
// Recursive comparisons by implementations of Value.CompareSameType
// should use CompareDepth to prevent infinite recursion.
func Compare(op syntax.Token, x, y Value) (bool, error) {
return CompareDepth(op, x, y, maxdepth)
return CompareDepth(op, x, y, CompareLimit)
}
// CompareDepth compares two Starlark values.
@@ -1177,6 +1325,14 @@ func CompareDepth(op syntax.Token, x, y Value, depth int) (bool, error) {
return xcomp.CompareSameType(op, y, depth)
}
if xcomp, ok := x.(TotallyOrdered); ok {
t, err := xcomp.Cmp(y, depth)
if err != nil {
return false, err
}
return threeway(op, t), nil
}
// use identity comparison
switch op {
case syntax.EQL:
@@ -1193,11 +1349,10 @@ func CompareDepth(op syntax.Token, x, y Value, depth int) (bool, error) {
switch x := x.(type) {
case Int:
if y, ok := y.(Float); ok {
if y != y {
return false, nil // y is NaN
}
var cmp int
if !math.IsInf(float64(y), 0) {
if y != y {
cmp = -1 // y is NaN
} else if !math.IsInf(float64(y), 0) {
cmp = x.rational().Cmp(y.rational()) // y is finite
} else if y > 0 {
cmp = -1 // y is +Inf
@@ -1208,16 +1363,15 @@ func CompareDepth(op syntax.Token, x, y Value, depth int) (bool, error) {
}
case Float:
if y, ok := y.(Int); ok {
if x != x {
return false, nil // x is NaN
}
var cmp int
if !math.IsInf(float64(x), 0) {
if x != x {
cmp = +1 // x is NaN
} else if !math.IsInf(float64(x), 0) {
cmp = x.rational().Cmp(y.rational()) // x is finite
} else if x > 0 {
cmp = -1 // x is +Inf
cmp = +1 // x is +Inf
} else {
cmp = +1 // x is -Inf
cmp = -1 // x is -Inf
}
return threeway(op, cmp), nil
}
@@ -1274,6 +1428,8 @@ func Len(x Value) int {
switch x := x.(type) {
case String:
return x.Len()
case Indexable:
return x.Len()
case Sequence:
return x.Len()
}
@@ -1291,3 +1447,54 @@ func Iterate(x Value) Iterator {
}
return nil
}
// Bytes is the type of a Starlark binary string.
//
// A Bytes encapsulates an immutable sequence of bytes.
// It is comparable, indexable, and sliceable, but not direcly iterable;
// use bytes.elems() for an iterable view.
//
// In this Go implementation, the elements of 'string' and 'bytes' are
// both bytes, but in other implementations, notably Java, the elements
// of a 'string' are UTF-16 codes (Java chars). The spec abstracts text
// strings as sequences of UTF-k codes that encode Unicode code points,
// and operations that convert from text to binary incur UTF-k-to-UTF-8
// transcoding; conversely, conversion from binary to text incurs
// UTF-8-to-UTF-k transcoding. Because k=8 for Go, these operations
// are the identity function, at least for valid encodings of text.
type Bytes string
var (
_ Comparable = Bytes("")
_ Sliceable = Bytes("")
_ Indexable = Bytes("")
)
func (b Bytes) String() string { return syntax.Quote(string(b), true) }
func (b Bytes) Type() string { return "bytes" }
func (b Bytes) Freeze() {} // immutable
func (b Bytes) Truth() Bool { return len(b) > 0 }
func (b Bytes) Hash() (uint32, error) { return String(b).Hash() }
func (b Bytes) Len() int { return len(b) }
func (b Bytes) Index(i int) Value { return b[i : i+1] }
func (b Bytes) Attr(name string) (Value, error) { return builtinAttr(b, name, bytesMethods) }
func (b Bytes) AttrNames() []string { return builtinAttrNames(bytesMethods) }
func (b Bytes) Slice(start, end, step int) Value {
if step == 1 {
return b[start:end]
}
sign := signum(step)
var str []byte
for i := start; signum(end-i) == sign; i += step {
str = append(str, b[i])
}
return Bytes(str)
}
func (x Bytes) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error) {
y := y_.(Bytes)
return threeway(op, strings.Compare(string(x), string(y))), nil
}

View File

@@ -66,7 +66,7 @@ func FromKeywords(constructor starlark.Value, kwargs []starlark.Tuple) *Struct {
return s
}
// FromStringDict returns a whose elements are those of d.
// FromStringDict returns a new struct instance 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 {
@@ -132,11 +132,12 @@ func (s *Struct) ToStringDict(d starlark.StringDict) {
func (s *Struct) String() string {
buf := new(strings.Builder)
if s.constructor == Default {
switch constructor := s.constructor.(type) {
case starlark.String:
// NB: The Java implementation always prints struct
// even for Bazel provider instances.
buf.WriteString("struct") // avoid String()'s quotation
} else {
buf.WriteString(constructor.GoString()) // avoid String()'s quotation
default:
buf.WriteString(s.constructor.String())
}
buf.WriteByte('(')

View File

@@ -28,7 +28,7 @@ const (
// If src != nil, ParseFile parses the source from src and the filename
// is only used when recording position information.
// The type of the argument for the src parameter must be string,
// []byte, or io.Reader.
// []byte, io.Reader, or FilePortion.
// If src == nil, ParseFile parses the file specified by filename.
func Parse(filename string, src interface{}, mode Mode) (f *File, err error) {
in, err := newScanner(filename, src, mode&RetainComments != 0)
@@ -771,8 +771,7 @@ func (p *parser) parseArgs() []Expr {
}
// primary = IDENT
// | INT | FLOAT
// | STRING
// | INT | FLOAT | STRING | BYTES
// | '[' ... // list literal or comprehension
// | '{' ... // dict literal or comprehension
// | '(' ... // tuple or parenthesized expression
@@ -782,7 +781,7 @@ func (p *parser) parsePrimary() Expr {
case IDENT:
return p.parseIdent()
case INT, FLOAT, STRING:
case INT, FLOAT, STRING, BYTES:
var val interface{}
tok := p.tok
switch tok {
@@ -794,7 +793,7 @@ func (p *parser) parsePrimary() Expr {
}
case FLOAT:
val = p.tokval.float
case STRING:
case STRING, BYTES:
val = p.tokval.string
}
raw := p.tokval.raw

View File

@@ -10,6 +10,8 @@ import (
"fmt"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
// unesc maps single-letter chars following \ to their actual values.
@@ -40,23 +42,21 @@ var esc = [256]byte{
'"': '"',
}
// 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) {
// string value, whether the original was triple-quoted,
// whether it was a byte string, and an error describing invalid input.
func unquote(quoted string) (s string, triple, isByte 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:]
}
// Check for bytes prefix.
if strings.HasPrefix(quoted, "b") {
isByte = true
quoted = quoted[1:]
}
if len(quoted) < 2 {
err = fmt.Errorf("string literal too short")
@@ -127,22 +127,25 @@ func unquote(quoted string) (s string, triple bool, err error) {
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:]
// In Starlark, like Go, a backslash must escape something.
// (Python still treats unnecessary backslashes literally,
// but since 3.6 has emitted a deprecation warning.)
err = fmt.Errorf("invalid escape sequence \\%c", quoted[1])
return
case '\n':
// Ignore the escape and the line break.
quoted = quoted[2:]
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '\'', '"':
// One-char escape
// One-char escape.
// Escapes are allowed for both kinds of quotation
// mark, not just the kind in use.
buf.WriteByte(unesc[quoted[1]])
quoted = quoted[2:]
case '0', '1', '2', '3', '4', '5', '6', '7':
// Octal escape, up to 3 digits.
// Octal escape, up to 3 digits, \OOO.
n := int(quoted[1] - '0')
quoted = quoted[2:]
for i := 1; i < 3; i++ {
@@ -152,6 +155,10 @@ func unquote(quoted string) (s string, triple bool, err error) {
n = n*8 + int(quoted[0]-'0')
quoted = quoted[1:]
}
if !isByte && n > 127 {
err = fmt.Errorf(`non-ASCII octal escape \%o (use \u%04X for the UTF-8 encoding of U+%04X)`, n, n, n)
return
}
if n >= 256 {
// NOTE: Python silently discards the high bit,
// so that '\541' == '\141' == 'a'.
@@ -162,7 +169,7 @@ func unquote(quoted string) (s string, triple bool, err error) {
buf.WriteByte(byte(n))
case 'x':
// Hexadecimal escape, exactly 2 digits.
// Hexadecimal escape, exactly 2 digits, \xXX. [0-127]
if len(quoted) < 4 {
err = fmt.Errorf(`truncated escape sequence %s`, quoted)
return
@@ -172,8 +179,41 @@ func unquote(quoted string) (s string, triple bool, err error) {
err = fmt.Errorf(`invalid escape sequence %s`, quoted[:4])
return
}
if !isByte && n > 127 {
err = fmt.Errorf(`non-ASCII hex escape %s (use \u%04X for the UTF-8 encoding of U+%04X)`,
quoted[:4], n, n)
return
}
buf.WriteByte(byte(n))
quoted = quoted[4:]
case 'u', 'U':
// Unicode code point, 4 (\uXXXX) or 8 (\UXXXXXXXX) hex digits.
sz := 6
if quoted[1] == 'U' {
sz = 10
}
if len(quoted) < sz {
err = fmt.Errorf(`truncated escape sequence %s`, quoted)
return
}
n, err1 := strconv.ParseUint(quoted[2:sz], 16, 0)
if err1 != nil {
err = fmt.Errorf(`invalid escape sequence %s`, quoted[:sz])
return
}
if n > unicode.MaxRune {
err = fmt.Errorf(`code point out of range: %s (max \U%08x)`,
quoted[:sz], n)
return
}
// As in Go, surrogates are disallowed.
if 0xD800 <= n && n < 0xE000 {
err = fmt.Errorf(`invalid Unicode code point U+%04X`, n)
return
}
buf.WriteRune(rune(n))
quoted = quoted[sz:]
}
}
@@ -191,79 +231,79 @@ func indexByte(s string, b byte) int {
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 a Starlark literal that denotes s.
// If b, it returns a bytes literal.
func Quote(s string, b bool) string {
const hex = "0123456789abcdef"
var runeTmp [utf8.UTFMax]byte
// 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 := make([]byte, 0, 3*len(s)/2)
if b {
buf = append(buf, 'b')
}
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++
}
buf = append(buf, '"')
for width := 0; len(s) > 0; s = s[width:] {
r := rune(s[0])
width = 1
if r >= utf8.RuneSelf {
r, width = utf8.DecodeRuneInString(s)
}
if width == 1 && r == utf8.RuneError {
// String (!b) literals accept \xXX escapes only for ASCII,
// but we must use them here to represent invalid bytes.
// The result is not a legal literal.
buf = append(buf, `\x`...)
buf = append(buf, hex[s[0]>>4])
buf = append(buf, hex[s[0]&0xF])
continue
}
if triple && c == '\n' {
// Can allow newline in triple-quoted string.
buf.WriteByte(c)
if r == '"' || r == '\\' { // always backslashed
buf = append(buf, '\\')
buf = append(buf, byte(r))
continue
}
if c == '\'' {
// Can allow ' since we always use ".
buf.WriteByte(c)
if strconv.IsPrint(r) {
n := utf8.EncodeRune(runeTmp[:], r)
buf = append(buf, runeTmp[:n]...)
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
switch r {
case '\a':
buf = append(buf, `\a`...)
case '\b':
buf = append(buf, `\b`...)
case '\f':
buf = append(buf, `\f`...)
case '\n':
buf = append(buf, `\n`...)
case '\r':
buf = append(buf, `\r`...)
case '\t':
buf = append(buf, `\t`...)
case '\v':
buf = append(buf, `\v`...)
default:
switch {
case r < ' ' || r == 0x7f:
buf = append(buf, `\x`...)
buf = append(buf, hex[byte(r)>>4])
buf = append(buf, hex[byte(r)&0xF])
case r > utf8.MaxRune:
r = 0xFFFD
fallthrough
case r < 0x10000:
buf = append(buf, `\u`...)
for s := 12; s >= 0; s -= 4 {
buf = append(buf, hex[r>>uint(s)&0xF])
}
default:
buf = append(buf, `\U`...)
for s := 28; s >= 0; s -= 4 {
buf = append(buf, hex[r>>uint(s)&0xF])
}
}
}
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()
buf = append(buf, '"')
return string(buf)
}

View File

@@ -35,6 +35,7 @@ const (
INT // 123
FLOAT // 1.23e45
STRING // "foo" or 'foo' or '''foo''' or r'foo' or r"foo"
BYTES // b"foo", etc
// Punctuation
PLUS // +
@@ -182,6 +183,15 @@ var tokenNames = [...]string{
WHILE: "while",
}
// A FilePortion describes the content of a portion of a file.
// Callers may provide a FilePortion for the src argument of Parse
// when the desired initial line and column numbers are not (1, 1),
// such as when an expression is parsed from within larger file.
type FilePortion struct {
Content []byte
FirstLine, FirstCol int32
}
// A Position describes the location of a rune of input.
type Position struct {
file *string // filename (indirect for compactness)
@@ -249,13 +259,17 @@ type scanner struct {
}
func newScanner(filename string, src interface{}, keepComments bool) (*scanner, error) {
var firstLine, firstCol int32 = 1, 1
if portion, ok := src.(FilePortion); ok {
firstLine, firstCol = portion.FirstLine, portion.FirstCol
}
sc := &scanner{
pos: Position{file: &filename, Line: 1, Col: 1},
pos: MakePosition(&filename, firstLine, firstCol),
indentstk: make([]int, 1, 10), // []int{0} + spare capacity
lineStart: true,
keepComments: keepComments,
}
sc.readline, _ = src.(func() ([]byte, error)) // REPL only
sc.readline, _ = src.(func() ([]byte, error)) // ParseCompoundStmt (REPL) only
if sc.readline == nil {
data, err := readSource(filename, src)
if err != nil {
@@ -279,6 +293,8 @@ func readSource(filename string, src interface{}) ([]byte, error) {
return nil, err
}
return data, nil
case FilePortion:
return src.Content, nil
case nil:
return ioutil.ReadFile(filename)
default:
@@ -407,7 +423,7 @@ type tokenValue struct {
int int64 // decoded int
bigInt *big.Int // decoded integers > int64
float float64 // decoded float
string string // decoded string
string string // decoded string or bytes
pos Position // start position of token
}
@@ -627,8 +643,15 @@ start:
// identifier or keyword
if isIdentStart(c) {
// raw string literal
if c == 'r' && len(sc.rest) > 1 && (sc.rest[1] == '"' || sc.rest[1] == '\'') {
if (c == 'r' || c == 'b') && len(sc.rest) > 1 && (sc.rest[1] == '"' || sc.rest[1] == '\'') {
// r"..."
// b"..."
sc.readRune()
c = sc.peekRune()
return sc.scanString(val, c)
} else if c == 'r' && len(sc.rest) > 2 && sc.rest[1] == 'b' && (sc.rest[2] == '"' || sc.rest[2] == '\'') {
// rb"..."
sc.readRune()
sc.readRune()
c = sc.peekRune()
return sc.scanString(val, c)
@@ -805,13 +828,26 @@ func (sc *scanner) scanString(val *tokenValue, quote rune) Token {
start := sc.pos
triple := len(sc.rest) >= 3 && sc.rest[0] == byte(quote) && sc.rest[1] == byte(quote) && sc.rest[2] == byte(quote)
sc.readRune()
// String literals may contain escaped or unescaped newlines,
// causing them to span multiple lines (gulps) of REPL input;
// they are the only such token. Thus we cannot call endToken,
// as it assumes sc.rest is unchanged since startToken.
// Instead, buffer the token here.
// TODO(adonovan): opt: buffer only if we encounter a newline.
raw := new(strings.Builder)
// Copy the prefix, e.g. r' or " (see startToken).
raw.Write(sc.token[:len(sc.token)-len(sc.rest)])
if !triple {
// Precondition: startToken was already called.
// single-quoted string literal
for {
if sc.eof() {
sc.error(val.pos, "unexpected EOF in string")
}
c := sc.readRune()
raw.WriteRune(c)
if c == quote {
break
}
@@ -822,22 +858,16 @@ func (sc *scanner) scanString(val *tokenValue, quote rune) Token {
if sc.eof() {
sc.error(val.pos, "unexpected EOF in string")
}
sc.readRune()
c = sc.readRune()
raw.WriteRune(c)
}
}
sc.endToken(val)
} else {
// triple-quoted string literal
sc.readRune()
raw.WriteRune(quote)
sc.readRune()
// A triple-quoted string literal may span multiple
// gulps of REPL input; it is the only such token.
// Thus we must avoid {start,end}Token.
raw := new(strings.Builder)
// Copy the prefix, e.g. r''' or """ (see startToken).
raw.Write(sc.token[:len(sc.token)-len(sc.rest)])
raw.WriteRune(quote)
quoteCount := 0
for {
@@ -862,15 +892,19 @@ func (sc *scanner) scanString(val *tokenValue, quote rune) Token {
raw.WriteRune(c)
}
}
val.raw = raw.String()
}
val.raw = raw.String()
s, _, err := unquote(val.raw)
s, _, isByte, err := unquote(val.raw)
if err != nil {
sc.error(start, err.Error())
}
val.string = s
return STRING
if isByte {
return BYTES
} else {
return STRING
}
}
func (sc *scanner) scanNumber(val *tokenValue, c rune) Token {

View File

@@ -251,10 +251,10 @@ func (x *Ident) Span() (start, end Position) {
// A Literal represents a literal string or number.
type Literal struct {
commentsRef
Token Token // = STRING | INT
Token Token // = STRING | BYTES | INT | FLOAT
TokenPos Position
Raw string // uninterpreted text
Value interface{} // = string | int64 | *big.Int
Value interface{} // = string | int64 | *big.Int | float64
}
func (x *Literal) Span() (start, end Position) {
@@ -398,10 +398,6 @@ func (x *DictEntry) Span() (start, end Position) {
}
// 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

View File

@@ -119,9 +119,7 @@ func Walk(n Node, f func(Node) bool) {
case *DictExpr:
for _, entry := range n.List {
entry := entry.(*DictEntry)
Walk(entry.Key, f)
Walk(entry.Value, f)
Walk(entry, f)
}
case *UnaryExpr: