| 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 |