OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE.md file. | |
4 | |
5 library fletchc.hub.sentence_parser; | |
6 | |
7 import 'dart:convert' show | |
8 JSON; | |
9 | |
10 import '../verbs/actions.dart' show | |
11 Action, | |
12 commonActions, | |
13 uncommonActions; | |
14 | |
15 import '../verbs/infrastructure.dart' show | |
16 AnalyzedSentence, | |
17 DiagnosticKind, | |
18 throwFatalError; | |
19 | |
20 Sentence parseSentence( | |
21 Iterable<String> arguments, | |
22 {bool includesProgramName}) { | |
23 SentenceParser parser = | |
24 new SentenceParser(arguments, includesProgramName == true); | |
25 return parser.parseSentence(); | |
26 } | |
27 | |
28 class SentenceParser { | |
29 final String version; | |
30 final String programName; | |
31 final String shortProgramName; | |
32 final String currentDirectory; | |
33 Words tokens; | |
34 | |
35 SentenceParser(Iterable<String> tokens, bool includesProgramName) | |
36 : version = includesProgramName ? tokens.first : null, | |
37 currentDirectory = includesProgramName ? tokens.skip(1).first : null, | |
38 programName = includesProgramName ? tokens.skip(2).first : null, | |
39 shortProgramName = includesProgramName ? tokens.skip(3).first : null, | |
40 tokens = new Words(tokens.skip(includesProgramName ? 4 : 0)); | |
41 | |
42 Sentence parseSentence() { | |
43 Verb verb; | |
44 if (!tokens.isAtEof) { | |
45 verb = parseVerb(); | |
46 } else { | |
47 verb = new Verb("help", commonActions["help"]); | |
48 } | |
49 List<Preposition> prepositions = <Preposition>[]; | |
50 List<Target> targets = <Target>[]; | |
51 while (!tokens.isAtEof) { | |
52 Preposition preposition = parsePrepositionOpt(); | |
53 if (preposition != null) { | |
54 prepositions.add(preposition); | |
55 continue; | |
56 } | |
57 Target target = parseTargetOpt(); | |
58 if (target != null) { | |
59 targets.add(target); | |
60 continue; | |
61 } | |
62 break; | |
63 } | |
64 List<String> trailing = <String>[]; | |
65 while (!tokens.isAtEof) { | |
66 trailing.add(tokens.current); | |
67 tokens.consume(); | |
68 } | |
69 if (trailing.isEmpty) { | |
70 trailing = null; | |
71 } | |
72 return new Sentence( | |
73 verb, prepositions, targets, trailing, | |
74 version, currentDirectory, programName, | |
75 // TODO(ahe): Get rid of the following argument: | |
76 tokens.originalInput.skip(2).toList()); | |
77 } | |
78 | |
79 Verb parseVerb() { | |
80 String name = tokens.current; | |
81 Action action = commonActions[name]; | |
82 if (action != null) { | |
83 tokens.consume(); | |
84 return new Verb(name, action); | |
85 } | |
86 action = uncommonActions[name]; | |
87 if (action != null) { | |
88 tokens.consume(); | |
89 return new Verb(name, action); | |
90 } | |
91 return new ErrorVerb(name); | |
92 } | |
93 | |
94 Preposition parsePrepositionOpt() { | |
95 // TODO(ahe): toLowerCase()? | |
96 String word = tokens.current; | |
97 Preposition makePreposition(PrepositionKind kind) { | |
98 tokens.consume(); | |
99 Target target = tokens.isAtEof ? null : parseTarget(); | |
100 return new Preposition(kind, target); | |
101 } | |
102 switch (word) { | |
103 case "with": | |
104 return makePreposition(PrepositionKind.WITH); | |
105 | |
106 case "in": | |
107 return makePreposition(PrepositionKind.IN); | |
108 | |
109 case "to": | |
110 return makePreposition(PrepositionKind.TO); | |
111 | |
112 | |
113 default: | |
114 return null; | |
115 } | |
116 } | |
117 | |
118 // @private_to.instance | |
119 Target internalParseTarget() { | |
120 // TODO(ahe): toLowerCase()? | |
121 String word = tokens.current; | |
122 | |
123 NamedTarget makeNamedTarget(TargetKind kind) { | |
124 tokens.consume(); | |
125 return new NamedTarget(kind, parseName()); | |
126 } | |
127 | |
128 Target makeTarget(TargetKind kind) { | |
129 tokens.consume(); | |
130 return new Target(kind); | |
131 } | |
132 | |
133 if (looksLikeAUri(word)) { | |
134 return new NamedTarget(TargetKind.FILE, parseName()); | |
135 } | |
136 | |
137 switch (word) { | |
138 case "session": | |
139 return makeNamedTarget(TargetKind.SESSION); | |
140 | |
141 case "class": | |
142 return makeNamedTarget(TargetKind.CLASS); | |
143 | |
144 case "method": | |
145 return makeNamedTarget(TargetKind.METHOD); | |
146 | |
147 case "file": | |
148 return makeNamedTarget(TargetKind.FILE); | |
149 | |
150 case "agent": | |
151 return makeTarget(TargetKind.AGENT); | |
152 | |
153 case "settings": | |
154 return makeTarget(TargetKind.SETTINGS); | |
155 | |
156 case "tcp_socket": | |
157 return makeNamedTarget(TargetKind.TCP_SOCKET); | |
158 | |
159 case "sessions": | |
160 return makeTarget(TargetKind.SESSIONS); | |
161 | |
162 case "classes": | |
163 return makeTarget(TargetKind.CLASSES); | |
164 | |
165 case "methods": | |
166 return makeTarget(TargetKind.METHODS); | |
167 | |
168 case "files": | |
169 return makeTarget(TargetKind.FILES); | |
170 | |
171 case "all": | |
172 return makeTarget(TargetKind.ALL); | |
173 | |
174 case "run-to-main": | |
175 return makeTarget(TargetKind.RUN_TO_MAIN); | |
176 | |
177 case "backtrace": | |
178 return makeTarget(TargetKind.BACKTRACE); | |
179 | |
180 case "continue": | |
181 return makeTarget(TargetKind.CONTINUE); | |
182 | |
183 case "break": | |
184 return makeNamedTarget(TargetKind.BREAK); | |
185 | |
186 case "list": | |
187 return makeTarget(TargetKind.LIST); | |
188 | |
189 case "disasm": | |
190 return makeTarget(TargetKind.DISASM); | |
191 | |
192 case "frame": | |
193 return makeNamedTarget(TargetKind.FRAME); | |
194 | |
195 case "delete-breakpoint": | |
196 return makeNamedTarget(TargetKind.DELETE_BREAKPOINT); | |
197 | |
198 case "list-breakpoints": | |
199 return makeTarget(TargetKind.LIST_BREAKPOINTS); | |
200 | |
201 case "step": | |
202 return makeTarget(TargetKind.STEP); | |
203 | |
204 case "step-over": | |
205 return makeTarget(TargetKind.STEP_OVER); | |
206 | |
207 case "fibers": | |
208 return makeTarget(TargetKind.FIBERS); | |
209 | |
210 case "finish": | |
211 return makeTarget(TargetKind.FINISH); | |
212 | |
213 case "restart": | |
214 return makeTarget(TargetKind.RESTART); | |
215 | |
216 case "step-bytecode": | |
217 return makeTarget(TargetKind.STEP_BYTECODE); | |
218 | |
219 case "step-over-bytecode": | |
220 return makeTarget(TargetKind.STEP_OVER_BYTECODE); | |
221 | |
222 case "print": | |
223 return makeNamedTarget(TargetKind.PRINT); | |
224 | |
225 case "print-all": | |
226 return makeTarget(TargetKind.PRINT_ALL); | |
227 | |
228 case "toggle": | |
229 return makeNamedTarget(TargetKind.TOGGLE); | |
230 | |
231 case "help": | |
232 return makeTarget(TargetKind.HELP); | |
233 | |
234 case "log": | |
235 return makeTarget(TargetKind.LOG); | |
236 | |
237 case "devices": | |
238 return makeTarget(TargetKind.DEVICES); | |
239 | |
240 case "apply": | |
241 return makeTarget(TargetKind.APPLY); | |
242 | |
243 default: | |
244 return new ErrorTarget(DiagnosticKind.expectedTargetButGot, word); | |
245 } | |
246 } | |
247 | |
248 Target parseTargetOpt() { | |
249 Target target = internalParseTarget(); | |
250 return target is ErrorTarget ? null : target; | |
251 } | |
252 | |
253 Target parseTarget() { | |
254 Target target = internalParseTarget(); | |
255 if (target is ErrorTarget) { | |
256 tokens.consume(); | |
257 } | |
258 return target; | |
259 } | |
260 | |
261 String parseName() { | |
262 // TODO(ahe): Rename this method? It doesn't necessarily parse a name, just | |
263 // whatever is the next word. | |
264 String name = tokens.current; | |
265 tokens.consume(); | |
266 return name; | |
267 } | |
268 | |
269 /// Returns true if [word] looks like it is a (relative) URI. | |
270 bool looksLikeAUri(String word) { | |
271 return | |
272 word != null && | |
273 !word.startsWith("-") && | |
274 word.contains("."); | |
275 } | |
276 } | |
277 | |
278 String quoteString(String string) => JSON.encode(string); | |
279 | |
280 class Words { | |
281 final Iterable<String> originalInput; | |
282 | |
283 final Iterator<String> iterator; | |
284 | |
285 // @private_to.instance | |
286 bool internalIsAtEof; | |
287 | |
288 // @private_to.instance | |
289 int internalPosition = 0; | |
290 | |
291 Words(Iterable<String> input) | |
292 : this.internal(input, input.iterator); | |
293 | |
294 Words.internal(this.originalInput, Iterator<String> iterator) | |
295 : iterator = iterator, | |
296 internalIsAtEof = !iterator.moveNext(); | |
297 | |
298 bool get isAtEof => internalIsAtEof; | |
299 | |
300 int get position => internalPosition; | |
301 | |
302 String get current => iterator.current; | |
303 | |
304 void consume() { | |
305 internalIsAtEof = !iterator.moveNext(); | |
306 if (!isAtEof) { | |
307 internalPosition++; | |
308 } | |
309 } | |
310 } | |
311 | |
312 class Verb { | |
313 final String name; | |
314 final Action action; | |
315 | |
316 const Verb(this.name, this.action); | |
317 | |
318 bool get isErroneous => false; | |
319 | |
320 String toString() => "Verb(${quoteString(name)})"; | |
321 } | |
322 | |
323 class ErrorVerb implements Verb { | |
324 final String name; | |
325 | |
326 const ErrorVerb(this.name); | |
327 | |
328 bool get isErroneous => true; | |
329 | |
330 Action get action { | |
331 throwFatalError(DiagnosticKind.unknownAction, userInput: name); | |
332 } | |
333 } | |
334 | |
335 class Preposition { | |
336 final PrepositionKind kind; | |
337 final Target target; | |
338 | |
339 const Preposition(this.kind, this.target); | |
340 | |
341 String toString() => "Preposition($kind, $target)"; | |
342 } | |
343 | |
344 enum PrepositionKind { | |
345 WITH, | |
346 IN, | |
347 TO, | |
348 } | |
349 | |
350 class Target { | |
351 final TargetKind kind; | |
352 | |
353 const Target(this.kind); | |
354 | |
355 bool get isErroneous => false; | |
356 | |
357 String toString() => "Target($kind)"; | |
358 } | |
359 | |
360 enum TargetKind { | |
361 AGENT, | |
362 ALL, | |
363 APPLY, | |
364 BACKTRACE, | |
365 BREAK, | |
366 CLASS, | |
367 CLASSES, | |
368 CONTINUE, | |
369 DELETE_BREAKPOINT, | |
370 DEVICES, | |
371 DISASM, | |
372 FIBERS, | |
373 FILE, | |
374 FILES, | |
375 FINISH, | |
376 FRAME, | |
377 HELP, | |
378 LIST, | |
379 LIST_BREAKPOINTS, | |
380 LOG, | |
381 METHOD, | |
382 METHODS, | |
383 PRINT, | |
384 PRINT_ALL, | |
385 RESTART, | |
386 RUN_TO_MAIN, | |
387 SESSION, | |
388 SESSIONS, | |
389 SETTINGS, | |
390 STEP, | |
391 STEP_BYTECODE, | |
392 STEP_OVER, | |
393 STEP_OVER_BYTECODE, | |
394 TCP_SOCKET, | |
395 TOGGLE, | |
396 } | |
397 | |
398 class NamedTarget extends Target { | |
399 final String name; | |
400 | |
401 const NamedTarget(TargetKind kind, this.name) | |
402 : super(kind); | |
403 | |
404 String toString() { | |
405 return "NamedTarget($kind, ${quoteString(name)})"; | |
406 } | |
407 } | |
408 | |
409 class ErrorTarget extends Target { | |
410 final DiagnosticKind errorKind; | |
411 final String userInput; | |
412 | |
413 const ErrorTarget(this.errorKind, this.userInput) | |
414 : super(null); | |
415 | |
416 bool get isErroneous => true; | |
417 | |
418 String toString() => "ErrorTarget($errorKind, ${quoteString(userInput)})"; | |
419 } | |
420 | |
421 /// A sentence is a written command to fletch. Normally, this command is | |
422 /// written on the command-line and should be easy to write without having | |
423 /// getting into conflict with Unix shell command line parsing. | |
424 /// | |
425 /// An example sentence is: | |
426 /// `create class MyClass in session MySession` | |
427 /// | |
428 /// In this example, `create` is a [Verb], `class MyClass` is a [Target], and | |
429 /// `in session MySession` is a [Preposition]. | |
430 class Sentence { | |
431 /// For example, `create`. | |
432 final Verb verb; | |
433 | |
434 /// For example, `in session MySession` | |
435 final List<Preposition> prepositions; | |
436 | |
437 /// For example, `class MyClass` | |
438 final List<Target> targets; | |
439 | |
440 /// Any tokens found after this sentence. | |
441 final List<String> trailing; | |
442 | |
443 /// The current directory of the C++ client. | |
444 final String currentDirectory; | |
445 | |
446 // TODO(ahe): Get rid of this. | |
447 final String programName; | |
448 | |
449 final String version; | |
450 | |
451 // TODO(ahe): Get rid of this. | |
452 final List<String> arguments; | |
453 | |
454 const Sentence( | |
455 this.verb, | |
456 this.prepositions, | |
457 this.targets, | |
458 this.trailing, | |
459 this.version, | |
460 this.currentDirectory, | |
461 this.programName, | |
462 this.arguments); | |
463 | |
464 String toString() => "Sentence($verb, $prepositions, $targets)"; | |
465 } | |
OLD | NEW |