From e546ee2bd7ecbc59e11eed691760a683bafadece Mon Sep 17 00:00:00 2001 From: "thomashii@dukefirehawk.com" Date: Wed, 28 Apr 2021 07:58:38 +0800 Subject: [PATCH] Migrated symbol_table --- CHANGELOG.md | 1 + packages/symbol_table/CHANGELOG.md | 17 + packages/symbol_table/LICENSE | 21 ++ packages/symbol_table/README.md | 158 +++++++++ .../symbol_table/lib/src/symbol_table.dart | 316 ++++++++++++++++++ packages/symbol_table/lib/src/variable.dart | 51 +++ packages/symbol_table/lib/src/visibility.dart | 31 ++ packages/symbol_table/lib/symbol_table.dart | 1 + packages/symbol_table/pubspec.yaml | 11 + packages/symbol_table/symbol_table.iml | 14 + packages/symbol_table/test/all_test.dart | 144 ++++++++ 11 files changed, 765 insertions(+) create mode 100644 packages/symbol_table/CHANGELOG.md create mode 100644 packages/symbol_table/LICENSE create mode 100644 packages/symbol_table/README.md create mode 100644 packages/symbol_table/lib/src/symbol_table.dart create mode 100644 packages/symbol_table/lib/src/variable.dart create mode 100644 packages/symbol_table/lib/src/visibility.dart create mode 100644 packages/symbol_table/lib/symbol_table.dart create mode 100644 packages/symbol_table/pubspec.yaml create mode 100644 packages/symbol_table/symbol_table.iml create mode 100644 packages/symbol_table/test/all_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d4ab078..6e30f50d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * Migrated angel_client to 4.0.0 (6/13 tests passed) * Migrated angel_websocket to 4.0.0 (2/3 tests passed) * Updated test to 4.0.0 (1/1 test passed) +* Added symbol_table and migrated to 2.0.0 (16/16 tests passed) * Updated jael to 3.0.0 (in progress) * Updated jael_preprocessor to 3.0.0 (in progress) * Updated angel_jael to 3.0.0 (in progress) diff --git a/packages/symbol_table/CHANGELOG.md b/packages/symbol_table/CHANGELOG.md new file mode 100644 index 00000000..3d328168 --- /dev/null +++ b/packages/symbol_table/CHANGELOG.md @@ -0,0 +1,17 @@ +## 1.0.4 +* Added `context` to `SymbolTable`. + +## 1.0.3 +* Converted `Visibility` into a `Comparable` class. +* Renamed `add` -> `create`, `put` -> `assign`, and `allVariablesOfVisibility` -> `allVariablesWithVisibility`. +* Added tests for `Visibility` comparing, and `depth`. +* Added `uniqueName()` to `SymbolTable`. +* Fixed a typo in `remove` that would have prevented it from working correctly. + +## 1.0.2 +* Added `depth` to `SymbolTable`. +* Added `symbolTable` to `Variable`. +* Deprecated the redundant `Constant` class. +* Deprecated `Variable.markAsPrivate()`. +* Added the `Visibility` enumerator. +* Added the field `visibility` to `Variable`. \ No newline at end of file diff --git a/packages/symbol_table/LICENSE b/packages/symbol_table/LICENSE new file mode 100644 index 00000000..3de28325 --- /dev/null +++ b/packages/symbol_table/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Tobe O + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/symbol_table/README.md b/packages/symbol_table/README.md new file mode 100644 index 00000000..6731496d --- /dev/null +++ b/packages/symbol_table/README.md @@ -0,0 +1,158 @@ +# symbol_table +[![Pub](https://img.shields.io/pub/v/symbol_table.svg)](https://pub.dartlang.org/packages/symbol_table) +[![build status](https://travis-ci.org/thosakwe/symbol_table.svg)](https://travis-ci.org/thosakwe/symbol_table) + +A generic symbol table implementation in Dart, with support for scopes and constants. +The symbol tables produced by this package are hierarchical (in this case, tree-shaped), +and utilize basic memoization to speed up repeated lookups. + +# Variables +To represent a symbol, use `Variable`. I opted for the name +`Variable` to avoid conflict with the Dart primitive `Symbol`. + +```dart +var foo = new Variable('foo'); +var bar = new Variable('bar', value: 'baz'); + +// Call `lock` to mark a symbol as immutable. +var shelley = new Variable('foo', value: 'bar')..lock(); + +foo.value = 'bar'; +shelley.value = 'Mary'; // Throws a StateError - constants cannot be overwritten. + +foo.lock(); +foo.value = 'baz'; // Also throws a StateError - Once a variable is locked, it cannot be overwritten. +``` + +## Visibility +Variables are *public* by default, but can also be marked as *private* or *protected*. This can be helpful if you are trying +to determine which symbols should be exported from a library or class. + +```dart +myVariable.visibility = Visibility.protected; +myVariable.visibility = Visibility.private; +``` + +# Symbol Tables +It's easy to create a basic symbol table: + +```dart +var mySymbolTable = new SymbolTable(); +var doubles = new SymbolTable(values: { + 'hydrogen': 1.0, + 'avogadro': 6.022e23 +}); + +// Create a new variable within the scope. +doubles.create('one'); +doubles.create('one', value: 1.0); +doubles.create('one', value: 1.0, constant: true); + +// Set a variable within an ancestor, OR create a new variable if none exists. +doubles.assign('two', value: 2.0); + +// Completely remove a variable. +doubles.remove('two'); + +// Find a symbol, either in this symbol table or an ancestor. +var symbol = doubles.resolve('one'); + +// Find OR create a symbol. +var symbol = doubles.resolveOrCreate('one'); +var symbol = doubles.resolveOrCreate('one', value: 1.0); +var symbol = doubles.resolveOrCreate('one', value: 1.0, constant: true); +``` + +# Exporting Symbols +Due to the tree structure of symbol tables, it is extremely easy to +extract a linear list of distinct variables, with variables lower in the hierarchy superseding their parents +(effectively accomplishing variable shadowing). + +```dart +var allSymbols = mySymbolTable.allVariables; +``` + +We can also extract symbols which are *not* private. This helps us export symbols from libraries +or classes. + +```dart +var exportedSymbols = mySymbolTable.allPublicVariables; +``` + +It's easy to extract symbols of a given visibility: +```dart +var exportedSymbols = mySymbolTable.allVariablesWithVisibility(Visibility.protected); +``` + +# Child Scopes +There are three ways to create a new symbol table: + + +## Regular Children +This is what most interpreters need; it simply creates a symbol table with the current symbol table +as its parent. The new scope can define its own symbols, which will only shadow the ancestors within the +correct scope. + +```dart +var child = mySymbolTable.createChild(); +var child = mySymbolTable.createChild(values: {...}); +``` + +### Depth +Every symbol table has an associated `depth` attached to it, with the `depth` at the root +being `0`. When `createChild` is called, the resulting child has an incremented `depth`. + +## Clones +This creates a scope at the same level as the current one, with all the same variables. + +```dart +var clone = mySymbolTable.clone(); +``` + +## Forked Scopes +If you are implementing a language with closure functions, you might consider looking into this. +A forked scope is a scope identical to the current one, but instead of merely copying references +to variables, the values of variables are copied into new ones. + +The new scope is essentially a "frozen" version of the current one. + +It is also effectively orphaned - though it is aware of its `parent`, the parent scope is unaware +that the forked scope is a child. Thus, calls to `resolve` may return old variables, if a parent +has called `remove` on a symbol. + +```dart +var forked = mySymbolTable.fork(); +var forked = mySymbolTable.fork(values: {...}); +``` + +# Creating Names +In languages with block scope, oftentimes, identifiers will collide within a global scope. +To avoid this, symbol tables expose a `uniqueName()` method that simply attaches a numerical suffix to +an input name. The name is guaranteed to never be repeated within a specific scope. + +```dart +var name0 = mySymbolTable.uniqueName('foo'); // foo0 +var name1 = mySymbolTable.uniqueName('foo'); // foo1 +var name2 = mySymbolTable.uniqueName('foo'); // foo2 +``` + +# `this` Context +Many languages handle a sort of `this` context that values within a scope may +optionally be resolved against. Symbol tables can easily set their context +as follows: + +```dart +void foo() { + mySymbolTable.context = thisContext; +} +``` + +Resolution of the `context` getter functions just like a symbol; if none is +set locally, then it will refer to the parent. + +```dart +void bar() { + mySymbolTable.context = thisContext; + expect(mySymbolTable.createChild().createChild().context, thisContext); +} +``` \ No newline at end of file diff --git a/packages/symbol_table/lib/src/symbol_table.dart b/packages/symbol_table/lib/src/symbol_table.dart new file mode 100644 index 00000000..4dec0cd4 --- /dev/null +++ b/packages/symbol_table/lib/src/symbol_table.dart @@ -0,0 +1,316 @@ +library symbol_table; + +import 'package:collection/collection.dart' show IterableExtension; +part 'variable.dart'; +part 'visibility.dart'; + +/// A hierarchical mechanism to hold a set of variables, which supports scoping and constant variables. +class SymbolTable { + final List> _children = []; + final Map?> _lookupCache = {}; + final Map _names = {}; + final List> _variables = []; + int _depth = 0; + T? _context; + SymbolTable? _parent, _root; + + /// Initializes an empty symbol table. + /// + /// You can optionally provide a [Map] of starter [values]. + SymbolTable({Map values: const {}}) { + if (values.isNotEmpty == true) { + values.forEach((k, v) { + _variables.add(Variable._(k, this, value: v)); + }); + } + } + + /// Returns the nearest context this symbol table belongs to. Returns `null` if none was set within the entire tree. + /// + /// This can be used to bind values to a `this` scope within a compiler. + T? get context { + SymbolTable? search = this; + + while (search != null) { + if (search._context != null) return search._context; + search = search._parent; + } + + return null; + } + + /// Sets a local context for values within this scope to be resolved against. + void set context(T? value) { + _context = value; + } + + /// The depth of this symbol table within the tree. At the root, this is `0`. + int get depth => _depth; + + /// Returns `true` if this scope has no parent. + bool get isRoot => _parent == null; + + /// Gets the parent of this symbol table. + SymbolTable? get parent => _parent; + + /// Resolves the symbol table at the very root of the hierarchy. + /// + /// This value is memoized to speed up future lookups. + SymbolTable? get root { + if (_root != null) return _root; + + SymbolTable out = this; + + while (out._parent != null) out = out._parent!; + + return _root = out; + } + + /// Retrieves every variable within this scope and its ancestors. + /// + /// Variable names will not be repeated; this produces the effect of + /// shadowed variables. + /// + /// This list is unmodifiable. + List> get allVariables { + List distinct = []; + List> out = []; + + void crawl(SymbolTable table) { + for (var v in table._variables) { + if (!distinct.contains(v.name)) { + distinct.add(v.name); + out.add(v); + } + } + + if (table._parent != null) crawl(table._parent!); + } + + crawl(this); + return new List>.unmodifiable(out); + } + + /// Helper for calling [allVariablesWithVisibility] to fetch all public variables. + List> get allPublicVariables { + return allVariablesWithVisibility(Visibility.public); + } + + /// Use [allVariablesWithVisibility] instead. + @deprecated + List> allVariablesOfVisibility(Visibility visibility) { + return allVariablesWithVisibility(visibility); + } + + /// Retrieves every variable of the given [visibility] within this scope and its ancestors. + /// + /// Variable names will not be repeated; this produces the effect of + /// shadowed variables. + /// + /// Use this to "export" symbols out of a library or class. + /// + /// This list is unmodifiable. + List> allVariablesWithVisibility(Visibility visibility) { + List distinct = []; + List> out = []; + + void crawl(SymbolTable table) { + for (var v in table._variables) { + if (!distinct.contains(v.name) && v.visibility == visibility) { + distinct.add(v.name); + out.add(v); + } + } + + if (table._parent != null) crawl(table._parent!); + } + + crawl(this); + return new List>.unmodifiable(out); + } + + Variable? operator [](String name) => resolve(name); + + void operator []=(String name, T value) { + assign(name, value); + } + + void _wipeLookupCache(String key) { + _lookupCache.remove(key); + _children.forEach((c) => c._wipeLookupCache(key)); + } + + /// Use [create] instead. + @deprecated + Variable add(String name, {T? value, bool? constant}) { + return create(name, value: value, constant: constant); + } + + /// Create a new variable *within this scope*. + /// + /// You may optionally provide a [value], or mark the variable as [constant]. + Variable create(String name, {T? value, bool? constant}) { + // Check if it exists first. + if (_variables.any((v) => v.name == name)) + throw new StateError( + 'A symbol named "$name" already exists within the current context.'); + + _wipeLookupCache(name); + Variable v = new Variable._(name, this, value: value); + if (constant == true) v.lock(); + _variables.add(v); + return v; + } + + /// Use [assign] instead. + @deprecated + Variable put(String name, T value) { + return assign(name, value); + } + + /// Assigns a [value] to the variable with the given [name], or creates a new variable. + /// + /// You cannot use this method to assign constants. + /// + /// Returns the variable whose value was just assigned. + Variable assign(String name, T value) { + return resolveOrCreate(name)..value = value; + } + + /// Removes the variable with the given [name] from this scope, or an ancestor. + /// + /// Returns the deleted variable, or `null`. + /// + /// *Note: This may cause [resolve] calls in [fork]ed scopes to return `null`.* + /// *Note: There is a difference between symbol tables created via [fork], [createdChild], and [clone].* + Variable? remove(String name) { + SymbolTable? search = this; + + while (search != null) { + var variable = search._variables.firstWhereOrNull((v) => v.name == name); + + if (variable != null) { + search._wipeLookupCache(name); + search._variables.remove(variable); + return variable; + } + + search = search._parent; + } + + return null; + } + + /// Finds the variable with the given name, either within this scope or an ancestor. + /// + /// Returns `null` if none has been found. + Variable? resolve(String name) { + var v = _lookupCache.putIfAbsent(name, () { + var variable = _variables.firstWhereOrNull((v) => v.name == name); + + if (variable != null) + return variable; + else if (_parent != null) + return _parent?.resolve(name); + else + return null; + }); + + if (v == null) { + _lookupCache.remove(name); + return null; + } else { + return v; + } + } + + /// Finds the variable with the given name, either within this scope or an ancestor. + /// Creates a new variable if none was found. + /// + /// If a new variable is created, you may optionally give it a [value]. + /// You can also mark the new variable as a [constant]. + Variable resolveOrCreate(String name, {T? value, bool? constant}) { + var resolved = resolve(name); + if (resolved != null) return resolved; + return create(name, value: value, constant: constant); + } + + /// Creates a child scope within this one. + /// + /// You may optionally provide starter [values]. + SymbolTable createChild({Map values: const {}}) { + var child = SymbolTable(values: values); + child + .._depth = _depth + 1 + .._parent = this + .._root = _root; + _children.add(child); + return child; + } + + /// Creates a scope identical to this one, but with no children. + /// + /// The [parent] scope will see the new scope as a child. + SymbolTable clone() { + var table = SymbolTable(); + table._variables.addAll(_variables); + table + .._depth = _depth + .._parent = _parent + .._root = _root; + _parent?._children.add(table); + return table; + } + + /// Creates a *forked* scope, derived from this one. + /// You may provide starter [values]. + /// + /// As opposed to [createChild], all variables in the resulting forked + /// scope will be *copies* of those in this class. This makes forked + /// scopes useful for implementations of concepts like closure functions, + /// where the current values of variables are trapped. + /// + /// The forked scope is essentially orphaned and stands alone; although its + /// [parent] getter will point to the parent of the original scope, the parent + /// will not be aware of the new scope's existence. + SymbolTable fork({Map values: const {}}) { + var table = SymbolTable(); + + table + .._depth = _depth + .._parent = _parent + .._root = _root; + + table._variables.addAll(_variables.map((Variable v) { + Variable variable = new Variable._(v.name, this, value: v.value); + variable.visibility = v.visibility; + + if (v.isImmutable) variable.lock(); + return variable; + })); + + return table; + } + + /// Returns a variation on the input [name] that is guaranteed to never be repeated within this scope. + /// + /// The variation will the input [name], but with a numerical suffix appended. + /// Ex. `foo1`, `bar24` + String uniqueName(String name) { + int count = 0; + SymbolTable? search = this; + + while (search != null) { + if (search._names.containsKey(name)) count += search._names[name]!; + search = search._parent; + } + + _names.putIfAbsent(name, () => 0); + var n = _names[name]; + if (n != null) { + n++; + _names[name] = n; + } + return '$name$count'; + } +} diff --git a/packages/symbol_table/lib/src/variable.dart b/packages/symbol_table/lib/src/variable.dart new file mode 100644 index 00000000..c9933b26 --- /dev/null +++ b/packages/symbol_table/lib/src/variable.dart @@ -0,0 +1,51 @@ +part of symbol_table; + +/// Holds an immutable symbol, the value of which is set once and only once. +@deprecated +class Constant extends Variable { + Constant(String name, T value) : super._(name, null, value: value) { + lock(); + } +} + +/// Holds a symbol, the value of which may change or be marked immutable. +class Variable { + final String name; + final SymbolTable? symbolTable; + Visibility visibility = Visibility.public; + bool _locked = false; + T? _value; + + Variable._(this.name, this.symbolTable, {T? value}) { + _value = value; + } + + /// If `true`, then the value of this variable cannot be overwritten. + bool get isImmutable => _locked; + + /// This flag has no meaning within the context of this library, but if you + /// are implementing some sort of interpreter, you may consider acting based on + /// whether a variable is private. + @deprecated + bool get isPrivate => visibility == Visibility.private; + + T? get value => _value; + + void set value(T? value) { + if (_locked) + throw new StateError( + 'The value of constant "$name" cannot be overwritten.'); + _value = value; + } + + /// Locks this symbol, and prevents its [value] from being overwritten. + void lock() { + _locked = true; + } + + /// Marks this symbol as private. + @deprecated + void markAsPrivate() { + visibility = Visibility.private; + } +} diff --git a/packages/symbol_table/lib/src/visibility.dart b/packages/symbol_table/lib/src/visibility.dart new file mode 100644 index 00000000..23d4a248 --- /dev/null +++ b/packages/symbol_table/lib/src/visibility.dart @@ -0,0 +1,31 @@ +part of symbol_table; + +/// Represents the visibility of a symbol. +/// +/// Symbols may be [public], [protected], or [private]. +/// The significance of a symbol's visibility is semantic and specific to the interpreter/compiler; +/// this package attaches no specific meaning to it. +/// +/// [Visibility] instances can be compared using the `<`, `<=`, `>`, and `>=` operators. +/// The evaluation of the aforementioned operators is logical; +/// for example, a [private] symbol is *less visible* than a [public] symbol, +/// so [private] < [public]. +/// +/// In a nutshell: [private] < [protected] < [public]. +class Visibility implements Comparable { + static const Visibility private = const Visibility._(0); + static const Visibility protected = const Visibility._(1); + static const Visibility public = const Visibility._(2); + final int _n; + const Visibility._(this._n); + + bool operator >(Visibility other) => _n > other._n; + bool operator >=(Visibility other) => _n >= other._n; + bool operator <(Visibility other) => _n < other._n; + bool operator <=(Visibility other) => _n <= other._n; + + @override + int compareTo(Visibility other) { + return _n.compareTo(other._n); + } +} \ No newline at end of file diff --git a/packages/symbol_table/lib/symbol_table.dart b/packages/symbol_table/lib/symbol_table.dart new file mode 100644 index 00000000..46cd7b63 --- /dev/null +++ b/packages/symbol_table/lib/symbol_table.dart @@ -0,0 +1 @@ +export 'src/symbol_table.dart'; \ No newline at end of file diff --git a/packages/symbol_table/pubspec.yaml b/packages/symbol_table/pubspec.yaml new file mode 100644 index 00000000..bc90e868 --- /dev/null +++ b/packages/symbol_table/pubspec.yaml @@ -0,0 +1,11 @@ +dependencies: + collection: ^1.15.0-nullsafety.4 +name: symbol_table +version: 2.0.0 +description: A generic symbol table implementation in Dart, with support for scopes and constants. +author: Tobe O +homepage: https://github.com/thosakwe/symbol_table +environment: + sdk: '>=2.12.0 <3.0.0' +dev_dependencies: + test: ^1.15.7 \ No newline at end of file diff --git a/packages/symbol_table/symbol_table.iml b/packages/symbol_table/symbol_table.iml new file mode 100644 index 00000000..5a5ced28 --- /dev/null +++ b/packages/symbol_table/symbol_table.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/symbol_table/test/all_test.dart b/packages/symbol_table/test/all_test.dart new file mode 100644 index 00000000..5ba7d885 --- /dev/null +++ b/packages/symbol_table/test/all_test.dart @@ -0,0 +1,144 @@ +import 'package:symbol_table/symbol_table.dart'; +import 'package:test/test.dart'; + +main() { + late SymbolTable scope; + + setUp(() { + scope = SymbolTable(values: {'one': 1}); + }); + + test('starter values', () { + expect(scope['one']?.value, 1); + }); + + test('add', () { + var two = scope.create('two', value: 2); + expect(two.value, 2); + expect(two.isImmutable, isFalse); + }); + + test('put', () { + var one = scope.resolve('one'); + var child = scope.createChild(); + var three = child.assign('one', 3); + expect(three.value, 3); + expect(three, one); + }); + + test('private', () { + var three = scope.create('three', value: 3) + ..visibility = Visibility.private; + expect(scope.allVariables, contains(three)); + expect( + scope.allVariablesWithVisibility(Visibility.private), contains(three)); + expect(scope.allPublicVariables, isNot(contains(three))); + }); + + test('protected', () { + var three = scope.create('three', value: 3) + ..visibility = Visibility.protected; + expect(scope.allVariables, contains(three)); + expect(scope.allVariablesWithVisibility(Visibility.protected), + contains(three)); + expect(scope.allPublicVariables, isNot(contains(three))); + }); + + test('constants', () { + var two = scope.create('two', value: 2, constant: true); + expect(two.value, 2); + expect(two.isImmutable, isTrue); + expect(() => scope['two'] = 3, throwsStateError); + }); + + test('lock', () { + expect(scope['one']?.isImmutable, isFalse); + scope['one']!.lock(); + expect(scope['one']?.isImmutable, isTrue); + expect(() => scope['one'] = 2, throwsStateError); + }); + + test('child', () { + expect(scope.createChild().createChild().resolve('one')!.value, 1); + }); + + test('clone', () { + var child = scope.createChild(); + var clone = child.clone(); + expect(clone.resolve('one'), child.resolve('one')); + expect(clone.parent, child.parent); + }); + + test('fork', () { + var fork = scope.fork(); + scope.assign('three', 3); + + expect(scope.resolve('three'), isNotNull); + expect(fork.resolve('three'), isNull); + }); + + test('remove', () { + var one = scope.remove('one')!; + expect(one.value, 1); + + expect(scope.resolve('one'), isNull); + }); + + test('root', () { + expect(scope.isRoot, isTrue); + expect(scope.root, scope); + + var child = scope + .createChild() + .createChild() + .createChild() + .createChild() + .createChild() + .createChild() + .createChild(); + expect(child.isRoot, false); + expect(child.root, scope); + }); + + test('visibility comparisons', () { + expect([Visibility.private, Visibility.protected], + everyElement(lessThan(Visibility.public))); + expect(Visibility.private, lessThan(Visibility.protected)); + expect(Visibility.protected, greaterThan(Visibility.private)); + expect(Visibility.public, greaterThan(Visibility.private)); + expect(Visibility.public, greaterThan(Visibility.protected)); + }); + + test('depth', () { + expect(scope.depth, 0); + expect(scope.clone().depth, 0); + expect(scope.fork().depth, 0); + expect(scope.createChild().depth, 1); + expect(scope.createChild().createChild().depth, 2); + expect(scope.createChild().createChild().createChild().depth, 3); + }); + + test('unique name', () { + expect(scope.uniqueName('foo'), 'foo0'); + expect(scope.uniqueName('foo'), 'foo1'); + expect(scope.createChild().uniqueName('foo'), 'foo2'); + expect(scope.createChild().uniqueName('foo'), 'foo2'); + var child = scope.createChild(); + expect(child.uniqueName('foo'), 'foo2'); + expect(child.uniqueName('foo'), 'foo3'); + expect(child.createChild().uniqueName('foo'), 'foo4'); + }); + + test('context', () { + scope.context = 24; + expect(scope.context, 24); + expect(scope.createChild().context, 24); + expect(scope.createChild().createChild().context, 24); + + var child = scope.createChild().createChild()..context = 35; + expect(child.context, 35); + expect(child.createChild().context, 35); + expect(child.createChild().createChild().context, 35); + expect(scope.context, 24); + }); +}