Added merge_map and symbol_table
This commit is contained in:
parent
4774e6b135
commit
4c9bc1c9be
22 changed files with 1067 additions and 0 deletions
12
packages/merge_map/AUTHORS.md
Normal file
12
packages/merge_map/AUTHORS.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
16
packages/merge_map/CHANGELOG.md
Normal file
16
packages/merge_map/CHANGELOG.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# 2.0.2
|
||||||
|
* Resolve static analysis warnings
|
||||||
|
|
||||||
|
# 2.0.1
|
||||||
|
* Updated README
|
||||||
|
|
||||||
|
# 2.0.0
|
||||||
|
* Migrated to work with Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
# 1.0.2
|
||||||
|
* Add an example, for Pub's sake.
|
||||||
|
|
||||||
|
# 1.0.1
|
||||||
|
* Add a specific constraint on Dart versions, to prevent Pub from rejecting all packages that depend on
|
||||||
|
`merge_map` (the entire Angel framework).
|
||||||
|
* Add generic type support
|
21
packages/merge_map/LICENSE
Normal file
21
packages/merge_map/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2021 dukefirehawk.com
|
||||||
|
|
||||||
|
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.
|
25
packages/merge_map/README.md
Normal file
25
packages/merge_map/README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# angel3_merge_map
|
||||||
|
[![version](https://img.shields.io/badge/pub-v2.0.2-brightgreen)](https://pub.dartlang.org/packages/angel3_merge_map)
|
||||||
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||||
|
|
||||||
|
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/merge_map/LICENSE)
|
||||||
|
|
||||||
|
Combine multiple Maps into one. Equivalent to
|
||||||
|
[Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
|
||||||
|
in JS.
|
||||||
|
|
||||||
|
# Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import "package:angel3_merge_map/angel3_merge_map.dart";
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Map map1 = {'hello': 'world'};
|
||||||
|
Map map2 = {'foo': {'bar': 'baz', 'this': 'will be overwritten'}};
|
||||||
|
Map map3 = {'foo': {'john': 'doe', 'this': 'overrides previous maps'}};
|
||||||
|
Map merged = mergeMap(map1, map2, map3);
|
||||||
|
|
||||||
|
// {hello: world, foo: {bar: baz, john: doe, this: overrides previous maps}}
|
||||||
|
}
|
||||||
|
```
|
4
packages/merge_map/analysis_options.yaml
Normal file
4
packages/merge_map/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:pedantic/analysis_options.yaml
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
20
packages/merge_map/example/main.dart
Normal file
20
packages/merge_map/example/main.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:angel3_merge_map/angel3_merge_map.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map1 = {'hello': 'world'};
|
||||||
|
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map2 = {
|
||||||
|
'foo': {'bar': 'baz', 'this': 'will be overwritten'}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map3 = {
|
||||||
|
'foo': {'john': 'doe', 'this': 'overrides previous maps'}
|
||||||
|
};
|
||||||
|
var merged = mergeMap([map1, map2, map3]);
|
||||||
|
print(merged);
|
||||||
|
|
||||||
|
// {hello: world, foo: {bar: baz, john: doe, this: overrides previous maps}}
|
||||||
|
}
|
34
packages/merge_map/lib/angel3_merge_map.dart
Normal file
34
packages/merge_map/lib/angel3_merge_map.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/// Exposes the [mergeMap] function, which... merges Maps.
|
||||||
|
library angel3_merge_map;
|
||||||
|
|
||||||
|
dynamic _copyValues<K, V>(
|
||||||
|
Map<K, V> from, Map<K, V?>? to, bool recursive, bool acceptNull) {
|
||||||
|
for (var key in from.keys) {
|
||||||
|
if (from[key] is Map<K, V> && recursive) {
|
||||||
|
if (!(to![key] is Map<K, V>)) {
|
||||||
|
to[key] = <K, V>{} as V;
|
||||||
|
}
|
||||||
|
_copyValues(from[key] as Map, to[key] as Map?, recursive, acceptNull);
|
||||||
|
} else {
|
||||||
|
if (from[key] != null || acceptNull) to![key] = from[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges the values of the given maps together.
|
||||||
|
///
|
||||||
|
/// `recursive` is set to `true` by default. If set to `true`,
|
||||||
|
/// then nested maps will also be merged. Otherwise, nested maps
|
||||||
|
/// will overwrite others.
|
||||||
|
///
|
||||||
|
/// `acceptNull` is set to `false` by default. If set to `false`,
|
||||||
|
/// then if the value on a map is `null`, it will be ignored, and
|
||||||
|
/// that `null` will not be copied.
|
||||||
|
Map<K, V> mergeMap<K, V>(Iterable<Map<K, V>> maps,
|
||||||
|
{bool recursive = true, bool acceptNull = false}) {
|
||||||
|
var result = <K, V>{};
|
||||||
|
maps.forEach((Map<K, V> map) {
|
||||||
|
_copyValues(map, result, recursive, acceptNull);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
9
packages/merge_map/pubspec.yaml
Normal file
9
packages/merge_map/pubspec.yaml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
name: angel3_merge_map
|
||||||
|
version: 2.0.2
|
||||||
|
description: Combine multiple Maps into one. Equivalent to Object.assign in JS.
|
||||||
|
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/merge_map
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^1.17.4
|
||||||
|
pedantic: ^1.11.0
|
104
packages/merge_map/test/all_test.dart
Normal file
104
packages/merge_map/test/all_test.dart
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import 'package:angel3_merge_map/angel3_merge_map.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('can merge two simple maps', () {
|
||||||
|
var merged = mergeMap([
|
||||||
|
{'hello': 'world'},
|
||||||
|
{'hello': 'dolly'}
|
||||||
|
]);
|
||||||
|
expect(merged['hello'], equals('dolly'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the last map's values supersede those of prior", () {
|
||||||
|
var merged = mergeMap([
|
||||||
|
{'letter': 'a'},
|
||||||
|
{'letter': 'b'},
|
||||||
|
{'letter': 'c'}
|
||||||
|
]);
|
||||||
|
expect(merged['letter'], equals('c'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can merge two once-nested maps', () {
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map1 = {
|
||||||
|
'hello': 'world',
|
||||||
|
'foo': {'nested': false}
|
||||||
|
};
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map2 = {
|
||||||
|
'goodbye': 'sad life',
|
||||||
|
'foo': {'nested': true, 'it': 'works'}
|
||||||
|
};
|
||||||
|
var merged = mergeMap([map1, map2]);
|
||||||
|
|
||||||
|
expect(merged['hello'], equals('world'));
|
||||||
|
expect(merged['goodbye'], equals('sad life'));
|
||||||
|
expect(merged['foo']['nested'], equals(true));
|
||||||
|
expect(merged['foo']['it'], equals('works'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('once-nested map supersession', () {
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map1 = {
|
||||||
|
'hello': 'world',
|
||||||
|
'foo': {'nested': false}
|
||||||
|
};
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map2 = {
|
||||||
|
'goodbye': 'sad life',
|
||||||
|
'foo': {'nested': true, 'it': 'works'}
|
||||||
|
};
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map3 = {
|
||||||
|
'foo': {'nested': 'supersession'}
|
||||||
|
};
|
||||||
|
|
||||||
|
var merged = mergeMap([map1, map2, map3]);
|
||||||
|
expect(merged['foo']['nested'], equals('supersession'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can merge two twice-nested maps', () {
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map1 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'c': 'd'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map2 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'c': 'D', 'e': 'f'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var merged = mergeMap([map1, map2]);
|
||||||
|
|
||||||
|
expect(merged['a']['b']['c'], equals('D'));
|
||||||
|
expect(merged['a']['b']['e'], equals('f'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('twice-nested map supersession', () {
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map1 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'c': 'd'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map2 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'c': 'D', 'e': 'f'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// ignore: omit_local_variable_types
|
||||||
|
Map map3 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'e': 'supersession'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var merged = mergeMap([map1, map2, map3]);
|
||||||
|
|
||||||
|
expect(merged['a']['b']['c'], equals('D'));
|
||||||
|
expect(merged['a']['b']['e'], equals('supersession'));
|
||||||
|
});
|
||||||
|
}
|
12
packages/symbol_table/AUTHORS.md
Normal file
12
packages/symbol_table/AUTHORS.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
26
packages/symbol_table/CHANGELOG.md
Normal file
26
packages/symbol_table/CHANGELOG.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# 2.0.2
|
||||||
|
* Resolved static analysis warnings
|
||||||
|
|
||||||
|
# 2.0.1
|
||||||
|
* Resolved static analysis warnings
|
||||||
|
|
||||||
|
# 2.0.0
|
||||||
|
* Migrated to work with Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
## 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`.
|
21
packages/symbol_table/LICENSE
Normal file
21
packages/symbol_table/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2021 dukefirehawk.com
|
||||||
|
|
||||||
|
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.
|
161
packages/symbol_table/README.md
Normal file
161
packages/symbol_table/README.md
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
# angel3_symbol_table
|
||||||
|
[![version](https://img.shields.io/badge/pub-v2.0.2-brightgreen)](https://pub.dartlang.org/packages/angel3_symbol_table)
|
||||||
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||||
|
|
||||||
|
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/symbol_table/LICENSE)
|
||||||
|
|
||||||
|
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 = Variable<String>('foo');
|
||||||
|
var bar = Variable<String>('bar', value: 'baz');
|
||||||
|
|
||||||
|
// Call `lock` to mark a symbol as immutable.
|
||||||
|
var shelley = Variable<String>('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 = SymbolTable<int>();
|
||||||
|
var doubles = SymbolTable<double>(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', 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);
|
||||||
|
}
|
||||||
|
```
|
4
packages/symbol_table/analysis_options.yaml
Normal file
4
packages/symbol_table/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:pedantic/analysis_options.yaml
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
26
packages/symbol_table/example/main.dart
Normal file
26
packages/symbol_table/example/main.dart
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import 'package:angel3_symbol_table/angel3_symbol_table.dart';
|
||||||
|
|
||||||
|
void main(List<String> args) {
|
||||||
|
//var mySymbolTable = SymbolTable<int>();
|
||||||
|
var doubles =
|
||||||
|
SymbolTable<double>(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', 2.0);
|
||||||
|
|
||||||
|
// Completely remove a variable.
|
||||||
|
doubles.remove('two');
|
||||||
|
|
||||||
|
// Find a symbol, either in this symbol table or an ancestor.
|
||||||
|
//var symbol1 = doubles.resolve('one');
|
||||||
|
|
||||||
|
// Find OR create a symbol.
|
||||||
|
//var symbol2 = doubles.resolveOrCreate('one');
|
||||||
|
//var symbol3 = doubles.resolveOrCreate('one', value: 1.0);
|
||||||
|
//var symbol4 = doubles.resolveOrCreate('one', value: 1.0, constant: true);
|
||||||
|
}
|
1
packages/symbol_table/lib/angel3_symbol_table.dart
Normal file
1
packages/symbol_table/lib/angel3_symbol_table.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export 'src/symbol_table.dart';
|
320
packages/symbol_table/lib/src/symbol_table.dart
Normal file
320
packages/symbol_table/lib/src/symbol_table.dart
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
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<T> {
|
||||||
|
final List<SymbolTable<T>> _children = [];
|
||||||
|
final Map<String, Variable<T>?> _lookupCache = {};
|
||||||
|
final Map<String, int> _names = {};
|
||||||
|
final List<Variable<T>> _variables = [];
|
||||||
|
int _depth = 0;
|
||||||
|
T? _context;
|
||||||
|
SymbolTable<T>? _parent, _root;
|
||||||
|
|
||||||
|
/// Initializes an empty symbol table.
|
||||||
|
///
|
||||||
|
/// You can optionally provide a [Map] of starter [values].
|
||||||
|
SymbolTable({Map<String, T> values = const {}}) {
|
||||||
|
if (values.isNotEmpty == true) {
|
||||||
|
values.forEach((k, v) {
|
||||||
|
_variables.add(Variable<T>._(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<T>? 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.
|
||||||
|
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<T>? get parent => _parent;
|
||||||
|
|
||||||
|
/// Resolves the symbol table at the very root of the hierarchy.
|
||||||
|
///
|
||||||
|
/// This value is memoized to speed up future lookups.
|
||||||
|
SymbolTable<T>? get root {
|
||||||
|
if (_root != null) return _root;
|
||||||
|
|
||||||
|
var 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<Variable<T>> get allVariables {
|
||||||
|
var distinct = <String>[];
|
||||||
|
var out = <Variable<T>>[];
|
||||||
|
|
||||||
|
void crawl(SymbolTable<T> 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 List<Variable<T>>.unmodifiable(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper for calling [allVariablesWithVisibility] to fetch all public variables.
|
||||||
|
List<Variable<T>> get allPublicVariables {
|
||||||
|
return allVariablesWithVisibility(Visibility.public);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use [allVariablesWithVisibility] instead.
|
||||||
|
@deprecated
|
||||||
|
List<Variable<T>> 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<Variable<T>> allVariablesWithVisibility(Visibility visibility) {
|
||||||
|
var distinct = <String>[];
|
||||||
|
var out = <Variable<T>>[];
|
||||||
|
|
||||||
|
void crawl(SymbolTable<T> 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 List<Variable<T>>.unmodifiable(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable<T>? 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<T> 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<T> create(String name, {T? value, bool? constant}) {
|
||||||
|
// Check if it exists first.
|
||||||
|
if (_variables.any((v) => v.name == name)) {
|
||||||
|
throw StateError(
|
||||||
|
'A symbol named "$name" already exists within the current context.');
|
||||||
|
}
|
||||||
|
|
||||||
|
_wipeLookupCache(name);
|
||||||
|
var v = Variable<T>._(name, this, value: value);
|
||||||
|
if (constant == true) v.lock();
|
||||||
|
_variables.add(v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use [assign] instead.
|
||||||
|
@deprecated
|
||||||
|
Variable<T> 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<T> 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<T>? remove(String name) {
|
||||||
|
SymbolTable<T>? 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<T>? 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<T> 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<T> createChild({Map<String, T> 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<T> clone() {
|
||||||
|
var table = SymbolTable<T>();
|
||||||
|
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<T> fork({Map<String, T> values = const {}}) {
|
||||||
|
var table = SymbolTable<T>();
|
||||||
|
|
||||||
|
table
|
||||||
|
.._depth = _depth
|
||||||
|
.._parent = _parent
|
||||||
|
.._root = _root;
|
||||||
|
|
||||||
|
table._variables.addAll(_variables.map((Variable v) {
|
||||||
|
var variable = Variable<T>._(v.name, this, value: v.value as T?);
|
||||||
|
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) {
|
||||||
|
var 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';
|
||||||
|
}
|
||||||
|
}
|
51
packages/symbol_table/lib/src/variable.dart
Normal file
51
packages/symbol_table/lib/src/variable.dart
Normal file
|
@ -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<T> extends Variable<T> {
|
||||||
|
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<T> {
|
||||||
|
final String name;
|
||||||
|
final SymbolTable<T>? 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;
|
||||||
|
|
||||||
|
set value(T? value) {
|
||||||
|
if (_locked) {
|
||||||
|
throw 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;
|
||||||
|
}
|
||||||
|
}
|
31
packages/symbol_table/lib/src/visibility.dart
Normal file
31
packages/symbol_table/lib/src/visibility.dart
Normal file
|
@ -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<Visibility> {
|
||||||
|
static const Visibility private = Visibility._(0);
|
||||||
|
static const Visibility protected = Visibility._(1);
|
||||||
|
static const Visibility public = 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);
|
||||||
|
}
|
||||||
|
}
|
11
packages/symbol_table/pubspec.yaml
Normal file
11
packages/symbol_table/pubspec.yaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
name: angel3_symbol_table
|
||||||
|
version: 2.0.2
|
||||||
|
description: A generic symbol table implementation in Dart, with support for scopes and constants.
|
||||||
|
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/symbol_table
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
|
dependencies:
|
||||||
|
collection: ^1.15.0
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^1.17.4
|
||||||
|
pedantic: ^1.11.0
|
14
packages/symbol_table/symbol_table.iml
Normal file
14
packages/symbol_table/symbol_table.iml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
144
packages/symbol_table/test/all_test.dart
Normal file
144
packages/symbol_table/test/all_test.dart
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import 'package:angel3_symbol_table/angel3_symbol_table.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late SymbolTable<int> scope;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
scope = SymbolTable<int>(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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue