diff --git a/cmd/skylark/skylark.go b/cmd/skylark/skylark.go index d236269..e403f3b 100644 --- a/cmd/skylark/skylark.go +++ b/cmd/skylark/skylark.go @@ -55,7 +55,6 @@ var ( // non-standard dialect flags func init() { flag.BoolVar(&resolve.AllowFloat, "fp", resolve.AllowFloat, "allow floating-point numbers") - flag.BoolVar(&resolve.AllowFreeze, "freeze", resolve.AllowFreeze, "add freeze built-in function") flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type") flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions") flag.BoolVar(&resolve.AllowNestedDef, "nesteddef", resolve.AllowNestedDef, "allow nested def statements") diff --git a/doc/impl.md b/doc/impl.md index 6ac20e0..588a59f 100644 --- a/doc/impl.md +++ b/doc/impl.md @@ -147,10 +147,10 @@ simple bit flip, no need to traverse the object graph---but coarser grained. Also, it complicates the API slightly because to construct a list, say, requires a reference to the frozen flag it should use. -The Go implementation also permits the `freeze` built-in to be exposed -to the program. (This requires the `-freeze` dialect flag.) This has -proven valuable in writing tests of the freeze mechanism itself, but -is mostly a curiosity. +The Go implementation would also permit the freeze operation to be +exposed to the program, for example as a built-in function. +This has proven valuable in writing tests of the freeze mechanism +itself, but is otherwise mostly a curiosity. ### Fail-fast iterators diff --git a/doc/spec.md b/doc/spec.md index b48d5d1..14a79a4 100644 --- a/doc/spec.md +++ b/doc/spec.md @@ -116,7 +116,6 @@ concurrency, and other such features of Python. * [dir](#dir) * [enumerate](#enumerate) * [float](#float) - * [freeze](#freeze) * [getattr](#getattr) * [hasattr](#hasattr) * [hash](#hash) @@ -916,6 +915,7 @@ If the function becomes frozen, its parameters' default values become frozen too. ```python +# module a.sky def f(x, list=[]): list.append(x) return list @@ -923,7 +923,9 @@ def f(x, list=[]): f(4, [1,2,3]) # [1, 2, 3, 4] f(1) # [1] f(2) # [1, 2], not [2]! -freeze(f) + +# module b.sky +load("a.sky", "f") f(3) # error: cannot append to frozen list ``` @@ -1235,7 +1237,6 @@ application-defined, implement a few basic behaviors: ```text str(x) -- return a string representation of x type(x) -- return a string describing the type of x -freeze(x) -- make x, and everything it transitively refers to, immutable bool(x) -- convert x to a Boolean truth value hash(x) -- return a hash code for x ``` @@ -1311,13 +1312,6 @@ and .bzl files in parallel, and two modules being executed concurrently may freely access variables or call functions from a third without the possibility of a race condition. -Implementation note: -The Go implementation of Skylark permits user code to freeze arbitrary -values by calling the `freeze` built-in function. -This feature must be enabled in the REPL by the `-freeze` flag. -This function is not present in the Java implementation, which freezes -values only _en masse_ at the end of module initialization. - ### Hashing The `dict` and `set` data types are implemented using hash tables, so @@ -2850,19 +2844,6 @@ function, and the real division operator `/`. The Java implementation does not yet support floating-point numbers. -### freeze - -`freeze(x)` freezes x and all values transitively reachable from it. -Subsequent attempts to modify any of those values will fail. - -At the end of module execution, the value of each global in the module -dictionary is frozen as if by `freeze`. - -Implementation note: -The `freeze` function is an optional feature of the Go implementation, -and it must be enabled in the REPL using the `-freeze` flag. -It is not present in the Java implementation. - ### getattr `getattr(x, name)` returns the value of the attribute (field or method) of x named `name`. @@ -3922,7 +3903,6 @@ eventually to eliminate all such differences on a case-by-case basis. * `assert` is a valid identifier. * `&` is a token; `int & int` and `set & set` are supported. * `int | int` is supported. -* The `freeze` built-in function is provided (option: `-freeze`). * The parser accepts unary `+` expressions. * A method call `x.f()` may be separated into two steps: `y = x.f; y()`. * Dot expressions may appear on the left side of an assignment: `x.f = 1`. diff --git a/eval_test.go b/eval_test.go index 6c24078..e74445c 100644 --- a/eval_test.go +++ b/eval_test.go @@ -24,7 +24,6 @@ func init() { resolve.AllowLambda = true resolve.AllowNestedDef = true resolve.AllowFloat = true - resolve.AllowFreeze = true resolve.AllowSet = true } diff --git a/library.go b/library.go index 7f1e7cb..1da0547 100644 --- a/library.go +++ b/library.go @@ -46,8 +46,7 @@ func init() { "dict": NewBuiltin("dict", dict), "dir": NewBuiltin("dir", dir), "enumerate": NewBuiltin("enumerate", enumerate), - "float": NewBuiltin("float", float), // requires resolve.AllowFloat - "freeze": NewBuiltin("freeze", freeze), // requires resolve.AllowFreeze + "float": NewBuiltin("float", float), // requires resolve.AllowFloat "getattr": NewBuiltin("getattr", getattr), "hasattr": NewBuiltin("hasattr", hasattr), "hash": NewBuiltin("hash", hash), @@ -506,17 +505,6 @@ func float(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error } } -func freeze(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error) { - if len(kwargs) > 0 { - return nil, fmt.Errorf("freeze does not accept keyword arguments") - } - if len(args) != 1 { - return nil, fmt.Errorf("freeze got %d arguments, wants 1", len(args)) - } - args[0].Freeze() - return args[0], nil -} - // https://github.com/google/skylark/blob/master/doc/spec.md#getattr func getattr(thread *Thread, _ *Builtin, args Tuple, kwargs []Tuple) (Value, error) { var object, dflt Value diff --git a/resolve/resolve.go b/resolve/resolve.go index d0c818a..4649914 100644 --- a/resolve/resolve.go +++ b/resolve/resolve.go @@ -84,7 +84,6 @@ 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 - AllowFreeze = false // allow the 'freeze' built-in AllowSet = false // allow the 'set' built-in AllowGlobalReassign = false // allow reassignment to globals declared in same file (deprecated) ) @@ -346,9 +345,6 @@ func (r *resolver) useGlobal(id *syntax.Ident) (scope Scope) { if !AllowSet && id.Name == "set" { r.errorf(id.NamePos, doesnt+"support sets") } - if !AllowFreeze && id.Name == "freeze" { - r.errorf(id.NamePos, doesnt+"provide the 'freeze' built-in function") - } } else { scope = Undefined r.errorf(id.NamePos, "undefined: %s", id.Name) diff --git a/resolve/resolve_test.go b/resolve/resolve_test.go index 11e953c..cfd7ddf 100644 --- a/resolve/resolve_test.go +++ b/resolve/resolve_test.go @@ -27,7 +27,6 @@ func TestResolve(t *testing.T) { resolve.AllowNestedDef = option(chunk.Source, "nesteddef") resolve.AllowLambda = option(chunk.Source, "lambda") resolve.AllowFloat = option(chunk.Source, "float") - resolve.AllowFreeze = option(chunk.Source, "freeze") resolve.AllowSet = option(chunk.Source, "set") resolve.AllowGlobalReassign = option(chunk.Source, "global_reassign") diff --git a/skylark b/skylark new file mode 100755 index 0000000..886686d Binary files /dev/null and b/skylark differ diff --git a/skylarktest/assert.sky b/skylarktest/assert.sky index d1dfeee..ca66a6f 100644 --- a/skylarktest/assert.sky +++ b/skylarktest/assert.sky @@ -5,6 +5,7 @@ # catch(f): evaluate f() and returns its evaluation error message, if any # matches(str, pattern): report whether str matches regular expression pattern. # struct: a constructor for a simple HasFields implementation. +# freeze(x): freeze the value x and everything reachable from it. # # Clients may use these functions to define their own testing abstractions. @@ -36,6 +37,8 @@ def _fails(f, pattern): elif not matches(pattern, msg): error("regular expression (%s) did not match error (%s)" % (pattern, msg)) +freeze = freeze # an exported global whose value is the built-in freeze function + assert = struct( fail = error, eq = _eq, diff --git a/skylarktest/skylarktest.go b/skylarktest/skylarktest.go index 3c09571..6db139d 100644 --- a/skylarktest/skylarktest.go +++ b/skylarktest/skylarktest.go @@ -64,6 +64,7 @@ func LoadAssertModule() (skylark.StringDict, error) { "catch": skylark.NewBuiltin("catch", catch), "matches": skylark.NewBuiltin("matches", matches), "struct": skylark.NewBuiltin("struct", skylarkstruct.Make), + "freeze": skylark.NewBuiltin("freeze", freeze), } filename := DataFile("skylark/skylarktest", "assert.sky") thread := new(skylark.Thread) @@ -116,6 +117,18 @@ func error_(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwar return skylark.None, nil } +// freeze(x) freezes its operand. +func freeze(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) { + if len(kwargs) > 0 { + return nil, fmt.Errorf("freeze does not accept keyword arguments") + } + if len(args) != 1 { + return nil, fmt.Errorf("freeze got %d arguments, wants 1", len(args)) + } + args[0].Freeze() + return args[0], nil +} + // DataFile returns the effective filename of the specified // test data resource. The function abstracts differences between // 'go build', under which a test runs in its package directory, diff --git a/testdata/assign.sky b/testdata/assign.sky index 194165b..556041f 100644 --- a/testdata/assign.sky +++ b/testdata/assign.sky @@ -242,7 +242,7 @@ assert.eq(z[0], 1) --- # assignment to/from fields. -load("assert.sky", "assert") +load("assert.sky", "assert", "freeze") hf = hasfields() hf.x = 1 diff --git a/testdata/dict.sky b/testdata/dict.sky index 6e6f1e5..e948e72 100644 --- a/testdata/dict.sky +++ b/testdata/dict.sky @@ -1,6 +1,6 @@ # Tests of Skylark 'dict' -load("assert.sky", "assert") +load("assert.sky", "assert", "freeze") # literals assert.eq({}, {}) diff --git a/testdata/function.sky b/testdata/function.sky index e936c16..a86c703 100644 --- a/testdata/function.sky +++ b/testdata/function.sky @@ -5,7 +5,7 @@ # and test that functions have correct position, free vars, names of locals, etc. # - move the hard-coded tests of parameter passing from eval_test.go to here. -load("assert.sky", "assert") +load("assert.sky", "assert", "freeze") # Test lexical scope and closures: def outer(x): @@ -104,7 +104,7 @@ assert.eq(len(hashes), 1) --- # Default values of function parameters are mutable. -load("assert.sky", "assert") +load("assert.sky", "assert", "freeze") def f(x=[0]): return x diff --git a/testdata/list.sky b/testdata/list.sky index 2b50ce3..d2409df 100644 --- a/testdata/list.sky +++ b/testdata/list.sky @@ -1,6 +1,6 @@ # Tests of Skylark 'list' -load("assert.sky", "assert") +load("assert.sky", "assert", "freeze") # literals assert.eq([], [])