platform/common/symbol_table/lib/src/symbol_table.dart

323 lines
9.2 KiB
Dart
Raw Normal View History

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("allVariablesWithVisibility")
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);
for (var c in _children) {
c._wipeLookupCache(key);
}
}
/// Use [create] instead.
@Deprecated("create")
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("assign")
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';
}
}