Added merge_map and symbol_table

This commit is contained in:
thomashii 2021-09-11 22:09:54 +08:00
parent 4774e6b135
commit 4c9bc1c9be
22 changed files with 1067 additions and 0 deletions

View 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.

View 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

View 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.

View 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}}
}
```

View file

@ -0,0 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

View 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}}
}

View 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;
}

View 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

View 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'));
});
}

View 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.

View 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`.

View 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.

View 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);
}
```

View file

@ -0,0 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

View 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);
}

View file

@ -0,0 +1 @@
export 'src/symbol_table.dart';

View 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';
}
}

View 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;
}
}

View 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);
}
}

View 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

View 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>

View 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);
});
}