Index: packages/petitparser/lib/src/petitparser/composite.dart |
diff --git a/packages/petitparser/lib/src/petitparser/composite.dart b/packages/petitparser/lib/src/petitparser/composite.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b7e8583bf40716a0da2a2a0c949416e4255d502d |
--- /dev/null |
+++ b/packages/petitparser/lib/src/petitparser/composite.dart |
@@ -0,0 +1,167 @@ |
+part of petitparser; |
+ |
+/// Helper to compose complex grammars from various primitive parsers. |
+/// |
+/// Note, this class is deprecated in favor of [GrammarDefinition] that provides |
+/// a more flexible way to define composite parsers. |
+/// |
+/// To create a new composite grammar subclass [CompositeParser]. Override |
+/// the method [initialize] and for every production call [def] giving the |
+/// production a name. The start production must be named 'start'. To refer |
+/// to other productions (forward and backward) use [ref]. |
+/// |
+/// Consider the following example to parse a list of numbers: |
+/// |
+/// class NumberListGrammar extends CompositeParser { |
+/// void initialize() { |
+/// def('start', ref('list').end()); |
+/// def('list', ref('element').separatedBy(char(','), |
+/// includeSeparators: false)); |
+/// def('element', digit().plus().flatten()); |
+/// } |
+/// } |
+/// |
+/// You might want to create future subclasses of your composite grammar |
+/// to redefine the grammar or attach custom actions. In such a subclass |
+/// override the method [initialize] again and call super. Then use |
+/// [redef] to redefine an existing production, and [action] to attach an |
+/// action to an existing production. |
+/// |
+/// Consider the following example that attaches a production action and |
+/// converts the digits to actual numbers: |
+/// |
+/// class NumberListParser extends NumberListGrammar { |
+/// void initialize() { |
+/// action('element', (value) => int.parse(value)); |
+/// } |
+/// } |
+@deprecated |
+abstract class CompositeParser extends DelegateParser { |
+ bool _completed = false; |
+ final Map<String, Parser> _defined = new Map(); |
+ final Map<String, SettableParser> _undefined = new Map(); |
+ |
+ CompositeParser() : super(failure('Not initalized production: start')) { |
+ initialize(); |
+ _complete(); |
+ } |
+ |
+ /// Initializes the composite grammar. |
+ void initialize(); |
+ |
+ /// Internal method to complete the grammar. |
+ void _complete() { |
+ _delegate = ref('start'); |
+ _undefined.forEach((name, parser) { |
+ if (!_defined.containsKey(name)) { |
+ throw new UndefinedProductionError(name); |
+ } |
+ parser.set(_defined[name]); |
+ }); |
+ _undefined.clear(); |
+ _completed = true; |
+ _delegate = ref('start'); |
+ } |
+ |
+ /// Returns a reference to a production with a [name]. |
+ /// |
+ /// This method works during initialization and after completion of the |
+ /// initialization. During the initialization it returns delegate parsers |
+ /// that are eventually replaced by the real parsers. Afterwards it |
+ /// returns the defined parser (mostly useful for testing). |
+ Parser ref(String name) { |
+ if (_completed) { |
+ if (_defined.containsKey(name)) { |
+ return _defined[name]; |
+ } else { |
+ throw new UndefinedProductionError(name); |
+ } |
+ } else { |
+ return _undefined.putIfAbsent(name, () { |
+ return failure('Not initalized production: $name').settable(); |
+ }); |
+ } |
+ } |
+ |
+ /// Convenience operator returning a reference to a production with |
+ /// a [name]. See [CompositeParser.ref] for details. |
+ Parser operator [](String name) => ref(name); |
+ |
+ /// Defines a production with a [name] and a [parser]. Only call this method |
+ /// from [initialize]. |
+ /// |
+ /// The following example defines a list production that consumes |
+ /// several elements separated by a comma. |
+ /// |
+ /// def('list', ref('element').separatedBy(char(','))); |
+ void def(String name, Parser parser) { |
+ if (_completed) { |
+ throw new CompletedParserError(); |
+ } else if (_defined.containsKey(name)) { |
+ throw new RedefinedProductionError(name); |
+ } else { |
+ _defined[name] = parser; |
+ } |
+ } |
+ |
+ /// Redefines an existing production with a [name] and a [replacement] |
+ /// parser or function producing a new parser. The code raises an |
+ /// [UndefinedProductionError] if [name] is an undefined production. Only call |
+ /// this method from [initialize]. |
+ /// |
+ /// The following example redefines the previously defined list production |
+ /// by making it optional: |
+ /// |
+ /// redef('list', (parser) => parser.optional()); |
+ void redef(String name, replacement) { |
+ if (_completed) { |
+ throw new CompletedParserError(); |
+ } else if (!_defined.containsKey(name)) { |
+ throw new UndefinedProductionError(name); |
+ } else { |
+ _defined[name] = |
+ replacement is Parser ? replacement : replacement(_defined[name]); |
+ } |
+ } |
+ |
+ /// Attaches an action [function] to an existing production [name]. The code |
+ /// raises an [UndefinedProductionError] if [name] is an undefined production. |
+ /// Only call this method from [initialize]. |
+ /// |
+ /// The following example attaches an action returning the size of list of |
+ /// the previously defined list production: |
+ /// |
+ /// action('list', (list) => list.length); |
+ void action(String name, Function function) { |
+ redef(name, (parser) => parser.map(function)); |
+ } |
+} |
+ |
+/// Error raised when somebody tries to modify a [CompositeParser] outside |
+/// the [CompositeParser.initialize] method. |
+class CompletedParserError extends Error { |
+ CompletedParserError(); |
+ |
+ @override |
+ String toString() => 'Completed parser'; |
+} |
+ |
+/// Error raised when an undefined production is accessed. |
+class UndefinedProductionError extends Error { |
+ final String name; |
+ |
+ UndefinedProductionError(this.name); |
+ |
+ @override |
+ String toString() => 'Undefined production: $name'; |
+} |
+ |
+/// Error raised when a production is accidentally redefined. |
+class RedefinedProductionError extends Error { |
+ final String name; |
+ |
+ RedefinedProductionError(this.name); |
+ |
+ @override |
+ String toString() => 'Redefined production: $name'; |
+} |