import '../core/parser.dart'; import '../parser/misc/epsilon.dart'; import '../shared/types.dart'; import 'internal/cycle_set.dart'; import 'internal/first_set.dart'; import 'internal/follow_set.dart'; import 'internal/path.dart'; import 'iterable.dart'; /// Helper to reflect on properties of a grammar. class Analyzer { /// Constructs an analyzer on the parser graph starting at [root]. Analyzer(this.root); /// The start parser of analysis. final Parser root; /// Returns a set of all parsers reachable from [root]. Iterable get parsers => _parsers; late final Set _parsers = allParser(root).toSet(); /// Returns a set of all deep children reachable from [parser]. /// /// The returned set does only include the [parser] itself, if it is /// recursively calling itself. Set allChildren(Parser parser) { assert(parsers.contains(parser), 'parser is not part of the analyzer'); return _allChildren.putIfAbsent( parser, () => parser.children.fold( {}, (result, child) => result..addAll(allParser(child)), ), ); } late final Map> _allChildren = {}; /// Returns the shortest path from [source] that satisfies the given /// [predicate], if any. ParserPath? findPath(Parser source, Predicate predicate) { ParserPath? path; for (final current in findAllPaths(source, predicate)) { if (path == null || current.length < path.length) { path = current; } } return path; } /// Returns the shortest path from [source] to [target], if any. ParserPath? findPathTo(Parser source, Parser target) { assert(parsers.contains(target), 'target is not part of the analyzer'); return findPath(source, (path) => path.target == target); } /// Returns all paths starting at [source] that satisfy the given [predicate]. Iterable findAllPaths( Parser source, Predicate predicate, ) { assert(parsers.contains(source), 'source is not part of the analyzer'); return depthFirstSearch(ParserPath([source], []), predicate); } /// Returns all paths starting at [source] that end in [target]. Iterable findAllPathsTo(Parser source, Parser target) { assert(parsers.contains(target), 'target is not part of the analyzer'); return findAllPaths(source, (path) => path.target == target); } /// Returns `true` if [parser] is transitively nullable, that is it can /// successfully parse nothing. bool isNullable(Parser parser) => _firstSets[parser]!.contains(sentinel); /// Returns the first-set of [parser]. /// /// The first-set of a parser is the set of terminal parsers which can appear /// as the first element of any chain of parsers derivable from [parser]. /// Includes [sentinel], if the set is nullable. Iterable firstSet(Parser parser) => _firstSets[parser]!; late final Map> _firstSets = computeFirstSets( parsers: _parsers, sentinel: sentinel, ); /// Returns the follow-set of a [parser]. /// /// The follow-set of a parser is the list of terminal parsers that can /// appear immediately after [parser]. Includes [sentinel], if the parse can /// complete when starting at [root]. Iterable followSet(Parser parser) => _followSet[parser]!; late final Map> _followSet = computeFollowSets( root: root, parsers: _parsers, firstSets: _firstSets, sentinel: sentinel, ); /// Returns the cycle-set of a [parser]. Iterable cycleSet(Parser parser) => _cycleSet[parser]!; late final Map> _cycleSet = computeCycleSets( parsers: _parsers, firstSets: _firstSets, ); /// A unique parser used as a marker in [firstSet] and [followSet] /// computations. static final sentinel = EpsilonParser(null); }