@@ -24,6 +24,7 @@ export const JumpLocation = {
24
24
EndOfCycle : Symbol ( 'EndOfCycle' ) ,
25
25
StartOfCycle : Symbol ( 'StartOfCycle' ) ,
26
26
Label : Symbol ( 'Label' ) ,
27
+ GroupEnd : Symbol ( 'GroupEnd' ) ,
27
28
Quit : Symbol ( 'Quit' ) ,
28
29
QuitSilent : Symbol ( 'QuitSilent' ) ,
29
30
} ;
@@ -54,34 +55,55 @@ export class Command {
54
55
}
55
56
56
57
// '{}' - Group other commands
57
- export class GroupCommand extends Command {
58
- constructor ( addressRange , subCommands ) {
58
+ export class GroupStartCommand extends Command {
59
+ constructor ( addressRange , id ) {
59
60
super ( addressRange ) ;
60
- this . subCommands = subCommands ;
61
+ this . id = id ;
61
62
}
62
63
63
- updateMatchState ( context ) {
64
- super . updateMatchState ( context ) ;
65
- for ( const command of this . subCommands ) {
66
- command . updateMatchState ( context ) ;
64
+ async runCommand ( context ) {
65
+ if ( ! this . addressRange . matches ( context . lineNumber , context . patternSpace ) ) {
66
+ context . jumpParameter = this . id ;
67
+ return JumpLocation . GroupEnd ;
67
68
}
69
+ return JumpLocation . None ;
70
+ }
71
+
72
+ dump ( indent ) {
73
+ return `${ makeIndent ( indent ) } GROUP-START: #${ this . id } \n`
74
+ + this . addressRange . dump ( indent + 1 ) ;
75
+ }
76
+ }
77
+ export class GroupEndCommand extends Command {
78
+ constructor ( id ) {
79
+ super ( ) ;
80
+ this . id = id ;
68
81
}
69
82
70
83
async run ( context ) {
71
- for ( const command of this . subCommands ) {
72
- const result = await command . runCommand ( context ) ;
73
- if ( result !== JumpLocation . None ) {
74
- return result ;
75
- }
76
- }
77
84
return JumpLocation . None ;
78
85
}
79
86
80
87
dump ( indent ) {
81
- return `${ makeIndent ( indent ) } GROUP:\n`
88
+ return `${ makeIndent ( indent ) } GROUP-END: #${ this . id } \n` ;
89
+ }
90
+ }
91
+
92
+ // ':' - Label
93
+ export class LabelCommand extends Command {
94
+ constructor ( label ) {
95
+ super ( ) ;
96
+ this . label = label ;
97
+ }
98
+
99
+ async run ( context ) {
100
+ return JumpLocation . None ;
101
+ }
102
+
103
+ dump ( indent ) {
104
+ return `${ makeIndent ( indent ) } LABEL:\n`
82
105
+ this . addressRange . dump ( indent + 1 )
83
- + `${ makeIndent ( indent + 1 ) } CHILDREN:\n`
84
- + this . subCommands . map ( command => command . dump ( indent + 2 ) ) . join ( '' ) ;
106
+ + `${ makeIndent ( indent + 1 ) } NAME: ${ this . label } \n` ;
85
107
}
86
108
}
87
109
@@ -121,6 +143,28 @@ export class AppendTextCommand extends Command {
121
143
}
122
144
}
123
145
146
+ // 'b' - Branch to label
147
+ export class BranchCommand extends Command {
148
+ constructor ( addressRange , label ) {
149
+ super ( addressRange ) ;
150
+ this . label = label ;
151
+ }
152
+
153
+ async run ( context ) {
154
+ if ( this . label ) {
155
+ context . jumpParameter = this . label ;
156
+ return JumpLocation . Label ;
157
+ }
158
+ return JumpLocation . EndOfCycle ;
159
+ }
160
+
161
+ dump ( indent ) {
162
+ return `${ makeIndent ( indent ) } BRANCH:\n`
163
+ + this . addressRange . dump ( indent + 1 )
164
+ + `${ makeIndent ( indent + 1 ) } LABEL: ${ this . label ? `'${ this . label } '` : 'END' } \n` ;
165
+ }
166
+ }
167
+
124
168
// 'c' - Replace line with text
125
169
export class ReplaceCommand extends Command {
126
170
constructor ( addressRange , text ) {
@@ -345,34 +389,121 @@ export class PrintLineCommand extends Command {
345
389
}
346
390
347
391
// 'q' - Quit
392
+ // 'Q' - Quit, suppressing the default output
348
393
export class QuitCommand extends Command {
349
- constructor ( addressRange ) {
394
+ constructor ( addressRange , silent ) {
350
395
super ( addressRange ) ;
396
+ this . silent = silent ;
351
397
}
352
398
353
399
async run ( context ) {
354
- return JumpLocation . Quit ;
400
+ return this . silent ? JumpLocation . QuitSilent : JumpLocation . Quit ;
355
401
}
356
402
357
403
dump ( indent ) {
358
404
return `${ makeIndent ( indent ) } QUIT:\n`
359
- + this . addressRange . dump ( indent + 1 ) ;
405
+ + this . addressRange . dump ( indent + 1 )
406
+ + `${ makeIndent ( indent + 1 ) } SILENT = '${ this . silent } '\n` ;
360
407
}
361
408
}
362
409
363
- // 'Q' - Quit, suppressing the default output
364
- export class QuitSilentCommand extends Command {
365
- constructor ( addressRange ) {
410
+ // 's' - Substitute
411
+ export class SubstituteFlags {
412
+ constructor ( { global = false , nthOccurrence = null , print = false , writeToFile = null } = { } ) {
413
+ this . global = global ;
414
+ this . nthOccurrence = nthOccurrence ;
415
+ this . print = print ;
416
+ this . writeToFile = writeToFile ;
417
+ }
418
+ }
419
+ export class SubstituteCommand extends Command {
420
+ constructor ( addressRange , regex , replacement , flags = new SubstituteFlags ( ) ) {
421
+ if ( ! ( flags instanceof SubstituteFlags ) ) {
422
+ throw new Error ( 'flags provided to SubstituteCommand must be an instance of SubstituteFlags' ) ;
423
+ }
424
+ super ( addressRange ) ;
425
+ this . regex = regex ;
426
+ this . replacement = replacement ;
427
+ this . flags = flags ;
428
+ }
429
+
430
+ async run ( context ) {
431
+ if ( this . flags . global ) {
432
+ // replaceAll() requires that the regex have the g flag
433
+ const regex = new RegExp ( this . regex , 'g' ) ;
434
+ context . substitutionResult = regex . test ( context . patternSpace ) ;
435
+ context . patternSpace = context . patternSpace . replaceAll ( regex , this . replacement ) ;
436
+ } else if ( this . flags . nthOccurrence && this . flags . nthOccurrence !== 1 ) {
437
+ // Note: For n=1, it's easier to use the "replace first match" path below instead.
438
+
439
+ // matchAll() requires that the regex have the g flag
440
+ const matches = [ ...context . patternSpace . matchAll ( new RegExp ( this . regex , 'g' ) ) ] ;
441
+ const nthMatch = matches [ this . flags . nthOccurrence - 1 ] ; // n is 1-indexed
442
+ if ( nthMatch !== undefined ) {
443
+ // To only replace the Nth match:
444
+ // - Split the string in two, at the match position
445
+ // - Run the replacement on the second half
446
+ // - Combine that with the first half again
447
+ const firstHalf = context . patternSpace . substring ( 0 , nthMatch . index ) ;
448
+ const secondHalf = context . patternSpace . substring ( nthMatch . index ) ;
449
+ context . patternSpace = firstHalf + secondHalf . replace ( this . regex , this . replacement ) ;
450
+ context . substitutionResult = true ;
451
+ } else {
452
+ context . substitutionResult = false ;
453
+ }
454
+ } else {
455
+ context . substitutionResult = this . regex . test ( context . patternSpace ) ;
456
+ context . patternSpace = context . patternSpace . replace ( this . regex , this . replacement ) ;
457
+ }
458
+
459
+ if ( context . substitutionResult ) {
460
+ if ( this . flags . print ) {
461
+ await context . out . write ( context . patternSpace + '\n' ) ;
462
+ }
463
+
464
+ if ( this . flags . writeToFile ) {
465
+ // TODO: Implement this.
466
+ }
467
+ }
468
+
469
+ return JumpLocation . None ;
470
+ }
471
+
472
+ dump ( indent ) {
473
+ return `${ makeIndent ( indent ) } SUBSTITUTE:\n`
474
+ + this . addressRange . dump ( indent + 1 )
475
+ + `${ makeIndent ( indent + 1 ) } REGEX '${ this . regex } '\n`
476
+ + `${ makeIndent ( indent + 1 ) } REPLACEMENT '${ this . replacement } '\n`
477
+ + `${ makeIndent ( indent + 1 ) } FLAGS ${ JSON . stringify ( this . flags ) } \n` ;
478
+ }
479
+ }
480
+
481
+ // 't' - Branch if substitution successful
482
+ // 'T' - Branch if substitution unsuccessful
483
+ export class ConditionalBranchCommand extends Command {
484
+ constructor ( addressRange , label , substitutionCondition ) {
366
485
super ( addressRange ) ;
486
+ this . label = label ;
487
+ this . substitutionCondition = substitutionCondition ;
367
488
}
368
489
369
490
async run ( context ) {
370
- return JumpLocation . QuitSilent ;
491
+ if ( context . substitutionResult !== this . substitutionCondition ) {
492
+ return JumpLocation . None ;
493
+ }
494
+
495
+ if ( this . label ) {
496
+ context . jumpParameter = this . label ;
497
+ return JumpLocation . Label ;
498
+ }
499
+ return JumpLocation . EndOfCycle ;
371
500
}
372
501
373
502
dump ( indent ) {
374
- return `${ makeIndent ( indent ) } QUIT-SILENT:\n`
375
- + this . addressRange . dump ( indent + 1 ) ;
503
+ return `${ makeIndent ( indent ) } CONDITIONAL-BRANCH:\n`
504
+ + this . addressRange . dump ( indent + 1 )
505
+ + `${ makeIndent ( indent + 1 ) } LABEL: ${ this . label ? `'${ this . label } '` : 'END' } \n`
506
+ + `${ makeIndent ( indent + 1 ) } IF SUBSTITUTED = ${ this . substitutionCondition } \n` ;
376
507
}
377
508
}
378
509
0 commit comments