OLD | NEW |
| (Empty) |
1 part of petitparser; | |
2 | |
3 /// Helper to compose complex grammars from various primitive parsers. | |
4 /// | |
5 /// Note, this class is deprecated in favor of [GrammarDefinition] that provides | |
6 /// a more flexible way to define composite parsers. | |
7 /// | |
8 /// To create a new composite grammar subclass [CompositeParser]. Override | |
9 /// the method [initialize] and for every production call [def] giving the | |
10 /// production a name. The start production must be named 'start'. To refer | |
11 /// to other productions (forward and backward) use [ref]. | |
12 /// | |
13 /// Consider the following example to parse a list of numbers: | |
14 /// | |
15 /// class NumberListGrammar extends CompositeParser { | |
16 /// void initialize() { | |
17 /// def('start', ref('list').end()); | |
18 /// def('list', ref('element').separatedBy(char(','), | |
19 /// includeSeparators: false)); | |
20 /// def('element', digit().plus().flatten()); | |
21 /// } | |
22 /// } | |
23 /// | |
24 /// You might want to create future subclasses of your composite grammar | |
25 /// to redefine the grammar or attach custom actions. In such a subclass | |
26 /// override the method [initialize] again and call super. Then use | |
27 /// [redef] to redefine an existing production, and [action] to attach an | |
28 /// action to an existing production. | |
29 /// | |
30 /// Consider the following example that attaches a production action and | |
31 /// converts the digits to actual numbers: | |
32 /// | |
33 /// class NumberListParser extends NumberListGrammar { | |
34 /// void initialize() { | |
35 /// action('element', (value) => int.parse(value)); | |
36 /// } | |
37 /// } | |
38 @deprecated | |
39 abstract class CompositeParser extends DelegateParser { | |
40 bool _completed = false; | |
41 final Map<String, Parser> _defined = new Map(); | |
42 final Map<String, SettableParser> _undefined = new Map(); | |
43 | |
44 CompositeParser() : super(failure('Not initalized production: start')) { | |
45 initialize(); | |
46 _complete(); | |
47 } | |
48 | |
49 /// Initializes the composite grammar. | |
50 void initialize(); | |
51 | |
52 /// Internal method to complete the grammar. | |
53 void _complete() { | |
54 _delegate = ref('start'); | |
55 _undefined.forEach((name, parser) { | |
56 if (!_defined.containsKey(name)) { | |
57 throw new UndefinedProductionError(name); | |
58 } | |
59 parser.set(_defined[name]); | |
60 }); | |
61 _undefined.clear(); | |
62 _completed = true; | |
63 _delegate = ref('start'); | |
64 } | |
65 | |
66 /// Returns a reference to a production with a [name]. | |
67 /// | |
68 /// This method works during initialization and after completion of the | |
69 /// initialization. During the initialization it returns delegate parsers | |
70 /// that are eventually replaced by the real parsers. Afterwards it | |
71 /// returns the defined parser (mostly useful for testing). | |
72 Parser ref(String name) { | |
73 if (_completed) { | |
74 if (_defined.containsKey(name)) { | |
75 return _defined[name]; | |
76 } else { | |
77 throw new UndefinedProductionError(name); | |
78 } | |
79 } else { | |
80 return _undefined.putIfAbsent(name, () { | |
81 return failure('Not initalized production: $name').settable(); | |
82 }); | |
83 } | |
84 } | |
85 | |
86 /// Convenience operator returning a reference to a production with | |
87 /// a [name]. See [CompositeParser.ref] for details. | |
88 Parser operator [](String name) => ref(name); | |
89 | |
90 /// Defines a production with a [name] and a [parser]. Only call this method | |
91 /// from [initialize]. | |
92 /// | |
93 /// The following example defines a list production that consumes | |
94 /// several elements separated by a comma. | |
95 /// | |
96 /// def('list', ref('element').separatedBy(char(','))); | |
97 void def(String name, Parser parser) { | |
98 if (_completed) { | |
99 throw new CompletedParserError(); | |
100 } else if (_defined.containsKey(name)) { | |
101 throw new RedefinedProductionError(name); | |
102 } else { | |
103 _defined[name] = parser; | |
104 } | |
105 } | |
106 | |
107 /// Redefines an existing production with a [name] and a [replacement] | |
108 /// parser or function producing a new parser. The code raises an | |
109 /// [UndefinedProductionError] if [name] is an undefined production. Only call | |
110 /// this method from [initialize]. | |
111 /// | |
112 /// The following example redefines the previously defined list production | |
113 /// by making it optional: | |
114 /// | |
115 /// redef('list', (parser) => parser.optional()); | |
116 void redef(String name, replacement) { | |
117 if (_completed) { | |
118 throw new CompletedParserError(); | |
119 } else if (!_defined.containsKey(name)) { | |
120 throw new UndefinedProductionError(name); | |
121 } else { | |
122 _defined[name] = | |
123 replacement is Parser ? replacement : replacement(_defined[name]); | |
124 } | |
125 } | |
126 | |
127 /// Attaches an action [function] to an existing production [name]. The code | |
128 /// raises an [UndefinedProductionError] if [name] is an undefined production. | |
129 /// Only call this method from [initialize]. | |
130 /// | |
131 /// The following example attaches an action returning the size of list of | |
132 /// the previously defined list production: | |
133 /// | |
134 /// action('list', (list) => list.length); | |
135 void action(String name, Function function) { | |
136 redef(name, (parser) => parser.map(function)); | |
137 } | |
138 } | |
139 | |
140 /// Error raised when somebody tries to modify a [CompositeParser] outside | |
141 /// the [CompositeParser.initialize] method. | |
142 class CompletedParserError extends Error { | |
143 CompletedParserError(); | |
144 | |
145 @override | |
146 String toString() => 'Completed parser'; | |
147 } | |
148 | |
149 /// Error raised when an undefined production is accessed. | |
150 class UndefinedProductionError extends Error { | |
151 final String name; | |
152 | |
153 UndefinedProductionError(this.name); | |
154 | |
155 @override | |
156 String toString() => 'Undefined production: $name'; | |
157 } | |
158 | |
159 /// Error raised when a production is accidentally redefined. | |
160 class RedefinedProductionError extends Error { | |
161 final String name; | |
162 | |
163 RedefinedProductionError(this.name); | |
164 | |
165 @override | |
166 String toString() => 'Redefined production: $name'; | |
167 } | |
OLD | NEW |