OLD | NEW |
| (Empty) |
1 part of petitparser; | |
2 | |
3 /// Helper to conveniently define and build complex, recursive grammars using | |
4 /// plain Dart code. | |
5 /// | |
6 /// To create a new grammar definition subclass [GrammarDefinition]. For every | |
7 /// production create a new method returning the primitive parser defining it. | |
8 /// The method called [start] is supposed to return the start production of the | |
9 /// grammar. To refer to a production defined in the same definition use [ref] | |
10 /// with the function reference as the first argument. | |
11 /// | |
12 /// Consider the following example to parse a list of numbers: | |
13 /// | |
14 /// class ListGrammarDefinition extends GrammarDefinition { | |
15 /// start() => ref(list).end(); | |
16 /// list() => ref(element) & char(',') & ref(list) | |
17 /// | ref(element); | |
18 /// element() => digit().plus().flatten(); | |
19 /// } | |
20 /// | |
21 /// Since this is plain Dart code, common refactorings such as renaming a produc
tion | |
22 /// updates all references correctly. Also code navigation and code completion | |
23 /// works as expected. | |
24 /// | |
25 /// To attach custom production actions you might want to further subclass your | |
26 /// grammar definition and override overriding the necessary productions defined | |
27 /// in the superclass: | |
28 /// | |
29 /// class ListParserDefinition extends ListGrammarDefinition { | |
30 /// element() => super.element().map((value) => int.parse(value)); | |
31 /// } | |
32 /// | |
33 /// Note that productions can be parametrized. Define such productions with posi
tional | |
34 /// arguments and reference to multiple instances by passing the arguments to [r
ef]. | |
35 /// | |
36 /// Consider extending the above grammar with a parametrized token production: | |
37 /// | |
38 /// class TokenizedListGrammarDefinition extends GrammarDefinition { | |
39 /// start() => ref(list).end(); | |
40 /// list() => ref(element) & ref(token, char(',')) & ref(list) | |
41 /// | ref(element); | |
42 /// element() => ref(token, digit().plus()); | |
43 /// token(p) => p.token().trim(); | |
44 /// } | |
45 abstract class GrammarDefinition { | |
46 const GrammarDefinition(); | |
47 | |
48 /// The starting production of this definition. | |
49 Parser start(); | |
50 | |
51 /// Returns a parser reference to a production defined by a [function]. | |
52 /// | |
53 /// The optional arguments parametrize the called production. | |
54 Parser ref(Function function, [arg1, arg2, arg3, arg4, arg5, arg6]) { | |
55 var arguments = [arg1, arg2, arg3, arg4, arg5, arg6] | |
56 .takeWhile((each) => each != null) | |
57 .toList(growable: false); | |
58 return new _Reference(function, arguments); | |
59 } | |
60 | |
61 /// Builds a composite parser from this definition. | |
62 /// | |
63 /// The optional [start] reference specifies a different starting production i
nto | |
64 /// the grammar. The optional [arguments] list parametrizes the called product
ion. | |
65 Parser build({Function start: null, List arguments: const []}) { | |
66 return _resolve( | |
67 new _Reference(start != null ? start : this.start, arguments)); | |
68 } | |
69 | |
70 /// Internal helper to resolve a complete parser graph. | |
71 Parser _resolve(_Reference reference) { | |
72 var mapping = new Map(); | |
73 | |
74 Parser _dereference(_Reference reference) { | |
75 var parser = mapping[reference]; | |
76 if (parser == null) { | |
77 var references = [reference]; | |
78 parser = reference.resolve(); | |
79 while (parser is _Reference) { | |
80 if (references.contains(parser)) { | |
81 throw new StateError('Recursive references detected: $references'); | |
82 } | |
83 references.add(parser); | |
84 parser = parser.resolve(); | |
85 } | |
86 for (var each in references) { | |
87 mapping[each] = parser; | |
88 } | |
89 } | |
90 return parser; | |
91 } | |
92 | |
93 var todo = [_dereference(reference)]; | |
94 var seen = new Set.from(todo); | |
95 | |
96 while (todo.isNotEmpty) { | |
97 var parent = todo.removeLast(); | |
98 for (var child in parent.children) { | |
99 if (child is _Reference) { | |
100 var referenced = _dereference(child); | |
101 parent.replace(child, referenced); | |
102 child = referenced; | |
103 } | |
104 if (!seen.contains(child)) { | |
105 seen.add(child); | |
106 todo.add(child); | |
107 } | |
108 } | |
109 } | |
110 | |
111 return mapping[reference]; | |
112 } | |
113 } | |
114 | |
115 /// A helper to build a parser from a {@link GrammarDefinition}. | |
116 class GrammarParser extends DelegateParser { | |
117 GrammarParser(GrammarDefinition definition) : super(definition.build()); | |
118 } | |
119 | |
120 class _Reference extends Parser { | |
121 final Function function; | |
122 final List arguments; | |
123 | |
124 _Reference(this.function, this.arguments); | |
125 | |
126 Parser resolve() => Function.apply(function, arguments); | |
127 | |
128 @override | |
129 bool operator ==(other) { | |
130 if (other is! _Reference || | |
131 other.function != function || | |
132 other.arguments.length != arguments.length) { | |
133 return false; | |
134 } | |
135 for (var i = 0; i < arguments.length; i++) { | |
136 var a = arguments[i], | |
137 b = other.arguments[i]; | |
138 if (a is Parser && a is! _Reference && b is Parser && b is! _Reference) { | |
139 // for parsers do a deep equality check | |
140 if (!a.isEqualTo(b)) { | |
141 return false; | |
142 } | |
143 } else { | |
144 // for everything else just do standard equality | |
145 if (a != b) { | |
146 return false; | |
147 } | |
148 } | |
149 } | |
150 return true; | |
151 } | |
152 | |
153 @override | |
154 int get hashCode => function.hashCode; | |
155 | |
156 @override | |
157 Parser copy() => throw new UnsupportedError('References cannot be copied.'); | |
158 | |
159 @override | |
160 Result parseOn(Context context) => throw new UnsupportedError('References cann
ot be parsed.'); | |
161 } | |
OLD | NEW |