-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcombinadores.pd
917 lines (787 loc) · 28.1 KB
/
combinadores.pd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
utilizar bepd/builtins
utilizar bepd/x/puerto como Puerto
utilizar bepd/x/puerto/conPosición como PuertoConPos
utilizar tokens como TokensNS
[DOCUMENTA
@brief{Lista de tokens.}
El @pd{Tokenizador}, como lo dice su documentación, es practicamente un puerto
de tokens. No es posible mover, desleer o saltar de un token a otro.
Durante el parsing sin embargo, es necesario hacer @italic{backtracking} para
poder crear el AST. Para esto está esta clase, @pd{Tokens}.
Esta clase actúa como un buffer de tokens. Tiene un "punto" que indica donde se
encuentra en el flujo de tokens. Leer un token en realidad devuelve el token
después del punto y avanza el punto, operaciones como retroceder y saltar a un
punto dado son relativamente eficientes.
Cabe resaltar que, por el hecho de que esta clase no puede saber que puntos
exísten para hacer "backtracking" todos los tokens del programa eventualmente
existiran en memoria. Una posible optimización a futuro sería cambiar la API
para permitir que la lista de tokens pueda ser "limpiada" si no hay puntos
existentes.
Los puntos son comparables tanto para igualdad como para menor que/mayor
que. Si dos puntos son iguales ambos representan la misma posición. Si un punto
es menor que otro eso significa que el menor está antes que el
mayor. Actualmente los puntos están implementados como números, pero no
deberías depender de esto.
DOCUMENTA]
clase Tokens
[DOCUMENTA
@brief{Crea unos tokens desde el tokenizador.}
Los tokens son extraídos desde el tokenizador en la medida necesaria.
DOCUMENTA]
metodo estatico desdeTokenizador: tokenizador
[DOCUMENTA
@brief{Lee un token.}
Lee un token y avanza el punto. Devuelve el token o @pd{Puerto#EOF}.
Si el punto esta al final del "flujo de tokens", lee un nuevo token del
tokenizador utilizando su método @code{#tokenizarSiguiente}. El punto no es
avanzado si @code{#tokenizarSiguiente} devuelve @pd{Puerto#EOF}.
DOCUMENTA]
metodo leerToken
[DOCUMENTA
@brief{Avanza el punto.}
Es como @code{#leerToken} pero no devuelve el token leído.
DOCUMENTA]
metodo avanzar
[DOCUMENTA
@brief{Retrocede el punto un token.}
No hace nada si el token ya estaba al principio.
DOCUMENTA]
metodo retroceder
[DOCUMENTA
@brief{Devuelve la posición actual del punto.}
El valor devuelto luego puede ser pasado a @code{#irAPunto} para volver a
esta misma posición.
DOCUMENTA]
metodo puntoActual
[DOCUMENTA
@brief{Mueve el punto a otra posición.}
DOCUMENTA]
metodo irAPunto: punto
finclase
[ `tokenizador` contiene al tokenizador, `tokens` al arreglo de tokens ya
leídos y `punto` al punto actual.
Si `tokens` está vacío entonces `punto` será 0 (en vez del `-1` que podrías
esperar). Este es un caso especial que hay que tener en cuenta. ]
atributos Tokens#tokenizador, Tokens#tokens, Tokens#punto
metodo estatico Tokens#desdeTokenizador: tokenizador
devolver clonar yo#_crear con
tokenizador: tokenizador
tokens: Arreglo#vacio
punto: 0
finclonar
finmetodo
metodo Tokens#estoyAlFinal
devolver yo#punto >= yo#tokens#longitud
finmetodo
metodo Tokens#leerToken
variable encontreEOF
fijar encontreEOF a FALSO
mientras (no encontreEOF) && yo#estoyAlFinal
variable nuevoTk
fijar nuevoTk a yo#tokenizador#tokenizarSiguiente
si nuevoTk = Puerto#EOF
fijar encontreEOF a VERDADERO
sino
yo#tokens#agregarAlFinal: nuevoTk
finsi
finmientras
si encontreEOF
fijar yo#punto a yo#tokens#longitud
devolver Puerto#EOF
sino
variable tk
fijar tk a yo#tokens#en: yo#punto
fijar yo#punto a yo#punto + 1
devolver tk
finsi
finmetodo
metodo Tokens#avanzar
yo#leerToken
finmetodo
metodo Tokens#retroceder
fijar yo#punto a yo#punto - 1
si yo#punto < 0
fijar yo#punto a 0
finsi
finmetodo
metodo Tokens#puntoActual
devolver yo#punto
finmetodo
metodo Tokens#irAPunto: punto
fijar yo#punto a punto
finmetodo
[DOCUMENTA
@brief{Aproxima la posición textual actual en el punto actual de los tokens.}
Obtiene los tokens justo antes y justo después del punto actual en @pd{tokens},
y usándolos trata de aproximar la posición textual. Esto no es posible en el
caso general (el espacio entre dos tokens no es una posición sino un área
textual). Pero este procedimiento trata de aproximarlo (dandole prioridad a la
posición @bold{después} del punto actual). Es utilizada para algunos mensajes
de error.
@devuelve{Una @pd{PuertoConPos#PosiciónTextual}, o @pd{NULO} si no se pudo
aproximar una posición textual.}
DOCUMENTA]
funcion AproximarPosiciónActual: tokens
variables p, tk, inicio
fijar p a tokens#puntoActual
tokens#retroceder
fijar tk a tokens#leerToken
fijar inicio a tokens#puntoActual > p
[ A simple vista, puede parecer redundante `tokens#irAPunto: p`, digo,
`leerToken` ya avanza el token y dado que habíamos retrocedido, ¿no
nos debería dejar esto justo donde empezamos? Pero hay que tener en
cuenta que `retroceder` no hace nada si ya está al principio del
programa. ]
tokens#irAPunto: p
si tk = Puerto#EOF
[ El programa está "vacío" ]
devolver NULO
sino
si inicio
devolver tk#areaDelToken#posiciónInicial
sino
devolver tk#areaDelToken#posiciónFinal
finsi
finsi
finfuncion
[DOCUMENTA
@brief{Un error de parseo/compilación.}
Estos errores consisten de un mensaje de error (que es un texto), un lugar (una
posición textual que indica en que parte del archivo sucedio el error) y una
causa opcional (otra instancia de @pd{Error} o @pd{NULO} si no tiene otro error
como causa).
DOCUMENTA]
clase Error
atributos mensaje, lugar, causa
[DOCUMENTA
@brief{Inicializa el error.}
No es recomendable crear instancias de @pd{Error} con @code{#crear}, en
cambio, prefiere los otros métodos estáticos.
DOCUMENTA]
metodo inicializar: mensaje, lugar, causa
[DOCUMENTA
@brief{Crea un error con un mensaje, sin lugar y sin causa.}
DOCUMENTA]
metodo estatico conMensaje: mensaje
[DOCUMENTA
@brief{Crea un error con un mensaje y lugar pero sin causa.}
DOCUMENTA]
metodo estatico conMensajeYLugar: mensaje, lugar
[DOCUMENTA
@brief{Crea un error con un mensaje, lugar y causa.}
DOCUMENTA]
metodo estatico conMensajeLugarYCausa: mensaje, lugar, causa
[DOCUMENTA
@brief{Crea un error con un mensaje y en la posición de un token.}
@pd{token} puede ser @pd{Puerto#EOF}, en cuyo caso el error no tiene
posición.
DOCUMENTA]
metodo estatico enToken: mensaje, token
[DOCUMENTA
@brief{Crea un error con un mensaje, en la posición de un token y con una
causa.}
@pd{token} puede ser @pd{Puerto#EOF}, en cuyo caso el error no tiene
posición.
DOCUMENTA]
metodo estatico enTokenConCausa: mensaje, token, causa
[DOCUMENTA
@brief{Crea un error con un mensaje y una causa, pero no un lugar.}
DOCUMENTA]
metodo estatico conCausa: mensaje, causa
[DOCUMENTA
@brief{Devuelve una representación textual simple del error.}
Para mejores resultados tendrás que recorrer manualmente el error y sus
causas generando un texto.
DOCUMENTA]
metodo comoTexto
finclase
metodo estatico Error#conMensaje: mensaje
devolver yo#crear: mensaje, (PuertoConPos#PosiciónTextual#crear: {<desconocido>}, 1, 0, 0), NULO
finmetodo
metodo estatico Error#conMensajeYLugar: mensaje, lugar
devolver yo#crear: mensaje, lugar, NULO
finmetodo
metodo estatico Error#conMensajeLugarYCausa: mensaje, lugar, causa
devolver yo#crear: mensaje, lugar, causa
finmetodo
metodo estatico Error#enToken: mensaje, token
variable pos
si token = Puerto#EOF
fijar pos a PuertoConPos#PosiciónTextual#crear: {<desconocido>}, 1, 0, 0
sino
fijar pos a token#areaDelToken#posiciónInicial
finsi
devolver yo#crear: mensaje, pos, NULO
finmetodo
metodo estatico Error#enTokenConCausa: mensaje, token, causa
variable pos
si token = Puerto#EOF
fijar pos a PuertoConPos#PosiciónTextual#crear: {<desconocido>}, 1, 0, 0
sino
fijar pos a token#areaDelToken#posiciónInicial
finsi
devolver yo#crear: mensaje, pos, causa
finmetodo
metodo estatico Error#conCausa: mensaje, causa
devolver yo#crear: mensaje, causa#lugar, causa
finmetodo
metodo Error#inicializar: mensaje, lugar, causa
fijar yo#mensaje a mensaje
fijar yo#lugar a lugar
fijar yo#causa a causa
finmetodo
metodo Error#comoTexto
devolver {(Error en ~t: ~t causado por ~t)}#formatear: yo#lugar, yo#mensaje, yo#causa
finmetodo
[DOCUMENTA
@brief{Convierte un token a un texto bonito.}
@devuelve{El resultado de llamar al método @code{#comoTextoBonito} del token, o
un texto indicando que es el fin del programa si @pd{tkOEOF} es
@pd{Puerto#EOF}.}
DOCUMENTA]
funcion ComoTextoBonito: tkOEOF
si tkOEOF = Puerto#EOF
devolver {el fin del programa}
sino
devolver tkOEOF#comoTextoBonito
finsi
finfuncion
[DOCUMENTA
@brief{Un combinador (@italic{parser combinator}).}
Un combinador acepta una instancia de @pd{Tokens} y devuelve un @pd{Resultado}
contieniendo un valor parseado (de cualquier tipo) o un error (de tipo
@pd{Error}). Los combinadores son implementados como clases que implementan a
esta.
La posición del punto en la instancia de @pd{Tokens} cuando el combinador
finalize afecta a los otros combinadores y es parte de su API pública, véa los
otros combinadores para más información.
DOCUMENTA]
clase Combinador
metodo parsear: tokens
finclase
metodo Combinador#parsear: tokens
MétodoAbstracto
finmetodo
[ Todos los combinadores son implementados como una clase que implementa
Combinador y una función que construye y devuelve una instancia del
mismo. Las clases no están documentadas pero las funciones si (debido a como
funcionan los combinadores, solo es necesario documentar la clase o la
función pero no ámbos). Las clases (que comienzan con "Comb" para indicar que
son combinadores) son, además, privadas, y no deberías utilizarlas fuera de
este módulo. En cambio, siempre usa las funciones. ]
clase CombToken hereda Combinador
atributos predicado, generarMensajeDeError
metodo parsear: tokens
finclase
metodo CombToken#parsear: tokens
variables posAct, tk
fijar posAct a tokens#puntoActual
fijar tk a tokens#leerToken
si no yo#predicado#\llamar\: tk
variable pos
si tk = Puerto#EOF
fijar pos a AproximarPosiciónActual: tokens
sino
fijar pos a tk#areaDelToken#posiciónInicial
finsi
devolver Resultado#error: (yo#generarMensajeDeError#\llamar\: tk, pos)
finsi
devolver Resultado#ok: tk
finmetodo
[DOCUMENTA
@brief{Parsea un token que pase un predicado.}
Este combinador parsea cualquier token que pase el predicado, que debe ser una
función que toma un valor (el token o @pd{Puerto#EOF}) y devuelve verdadero o
falso.
Si @pd{predicado} no acepta al token, @pd{generarMensajeDeError} es llamado con
el token (o EOF) y una posición textual y debe devolver una instancia de
@pd{Error} que contiene un mensaje, lugar y posiblemente causa apropiados.
Este combinador siempre avanza el punto por 1 (el token leído).
DOCUMENTA]
funcion Token: predicado, generarMensajeDeError
variable comb
fijar comb a CombToken#_crear
fijar comb#predicado a predicado
fijar comb#generarMensajeDeError a generarMensajeDeError
devolver comb
finfuncion
[DOCUMENTA
@brief{Devuelve un combinador que acepta la palabra clave @pd{pal}.}
Está implementado con @pd{Token} y comparte su mismo comportamiento con
respecto a la posición del punto.
DOCUMENTA]
funcion PalabraClave: pal
funcion pred: tk
si no EsInstancia: tk, TokensNS#TokenPalabraClave
devolver FALSO
sino
devolver tk#palabraClave = pal
finsi
finfuncion
funcion genMensjError: tk, pos
devolver Error#conMensajeYLugar:
({Se esperaba la palabra clave [~t] pero se obtuvo ~t}#formatear: pal, (ComoTextoBonito: tk)),
pos
finfuncion
devolver Token: &pred, &genMensjError
finfuncion
[DOCUMENTA
@brief{Devuelve un combinador que acepta un token identificador.}
Está implementado con @pd{Token} y comparte su mismo comportamiento con
respecto a la posición del punto.
DOCUMENTA]
funcion Identificador
funcion pred: tk
devolver EsInstancia: tk, TokensNS#TokenIdentificador
finfuncion
funcion genMensjError: tk, pos
devolver Error#conMensajeYLugar:
({Se esperaba un identificador pero se obtuvo ~t}#formatear: (ComoTextoBonito: tk)),
pos
finfuncion
devolver Token: &pred, &genMensjError
finfuncion
[DOCUMENTA
@brief{Devuelve un combinador que acepta un token númerico.}
Está implementado con @pd{Token} y comparte su mismo comportamiento con
respecto a la posición del punto.
DOCUMENTA]
funcion NumeroLiteral
funcion pred: tk
devolver EsInstancia: tk, TokensNS#TokenNumero
finfuncion
funcion genMensjError: tk, pos
devolver Error#conMensajeYLugar:
({Se esperaba un número pero se obtuvo ~t}#formatear: (ComoTextoBonito: tk)),
pos
finfuncion
devolver Token: &pred, &genMensjError
finfuncion
[DOCUMENTA
@brief{Devuelve un combinador que acepta un token operador.}
Está implementado con @pd{Token} y comparte su mismo comportamiento con
respecto a la posición del punto.
DOCUMENTA]
funcion Operador
funcion pred: tk
devolver EsInstancia: tk, TokensNS#TokenOperador
finfuncion
funcion genMensjError: tk, pos
devolver Error#conMensajeYLugar:
({Se esperaba un operador pero se obtuvo ~t}#formatear: (ComoTextoBonito: tk)),
pos
finfuncion
devolver Token: &pred, &genMensjError
finfuncion
[DOCUMENTA
@brief{Devuelve un combinador que acepta un token de texto literal.}
Está implementado con @pd{Token} y comparte su mismo comportamiento con
respecto a la posición del punto.
DOCUMENTA]
funcion TextoLiteral
funcion pred: tk
devolver EsInstancia: tk, TokensNS#TokenTexto
finfuncion
funcion genMensjError: tk, pos
devolver Error#conMensajeYLugar:
({Se esperaba un texto literal pero se obtuvo ~t}#formatear: (ComoTextoBonito: tk)),
pos
finfuncion
devolver Token: &pred, &genMensjError
finfuncion
clase CombComponer hereda Combinador
atributo combinadores
metodo parsear: tokens
finclase
metodo CombComponer#parsear: tokens
variable res
fijar res a Resultado#ok: Arreglo#vacio
ParaCadaElemento: yo#combinadores, procedimiento: comb
si res#esError devolver NULO finsi
variable r
fijar r a comb#parsear: tokens
si r#esError
fijar res#error a r#error
sino
res#valor#agregarAlFinal: r#valor
finsi
finprocedimiento
devolver res
finmetodo
[DOCUMENTA
@brief{Crea un combinador que compone varios otros.}
El combinador devuelto trata de parsear todos los combinadores de @pd{combs}
(un arreglo de combinadores) en order. Avanza el punto solo en la forma que los
combinadores de @pd{combs} lo hagan.
Por ejemplo:
@ejemplo|{
variables númeroYTexto, idYTexto
fijar númeroYTexto a Componer: (Arreglo#crearCon: NumeroLiteral, TextoLiteral)
fijar idYTexto a Componer: (Arreglo#crearCon: Identificador, TextoLiteral)
}|
DOCUMENTA]
funcion Componer: combs
variable comb
fijar comb a CombComponer#_crear
fijar comb#combinadores a combs
devolver comb
finfuncion
clase CombElegir hereda Combinador
atributo combinadores
metodo parsear: tokens
finclase
metodo CombElegir#mensajeDeError: tokens
variables p, tk, pos
fijar p a tokens#puntoActual
fijar tk a tokens#leerToken
tokens#irAPunto: p
si tk = Puerto#EOF
fijar pos a AproximarPosiciónActual: tokens
sino
fijar pos a tk#areaDelToken#posiciónInicial
finsi
devolver Error#conMensajeYLugar:
({No se esperaba ~t}#formatear: (ComoTextoBonito: tk)),
pos
finmetodo
metodo CombElegir#parsear: tokens
variables posIni, res, encajoCombinador
fijar posIni a tokens#puntoActual
fijar res a Resultado#error: (yo#mensajeDeError: tokens)
fijar encajoCombinador a FALSO
ParaCadaElemento: yo#combinadores, procedimiento: comb
si encajoCombinador devolver NULO finsi
variables posFinal, r
fijar r a comb#parsear: tokens
fijar posFinal a tokens#puntoActual
si r#esError && (posIni = posFinal)
[ Backtracking solo es utilizado si el combinador no consumio tokens ]
tokens#irAPunto: posIni
sino
fijar res a r
fijar encajoCombinador a VERDADERO
finsi
finprocedimiento
devolver res
finmetodo
[DOCUMENTA
@brief{Devuelve un combinador que devuelve el primero de @pd{combs} que sea
exitoso.}
Trata cada uno de los combinadores en @pd{combs) (un arreglo de combinadores)
hasta que uno tenga éxito. En especifico, si uno de los combinadores devuelve
un error pero no avanza el punto, entonces intenta con el siguiente. Si uno de
devuelve error y avanza el punto, este combinador devuelve el mismo error. Y
bueno, si un combinador no devuelve un error entonces su valor es devuelto
también de manera exitosa.
Por ejemplo, supongamos que existe este combinador @pd{NúmeroNA} que intenta
parsear un número, pero si falla no avanza el punto. Entonces podríamos crear
un nuevo combinador:
@ejemplo|{
Elegir: (Componer: NúmeroNA, TextoLiteral),
(Componer: Identificador TextoLiteral)
}|
Este combinador parsea un número seguido de un texto, o un identificador
seguido de un texto. Funciona de la siguiente manera:
@itemlist(
@item{Intenta parsear @pd{NúmeroNA}. Si @pd{NúmeroNA} logra parsear un número
entonces intenta parsear un texto.}
@item{Como @pd{NúmeroNA} ya parseo correctamente y avanzó el punto, si no se
logra parsear un texto después el error es propagado y la siguiente elección no
es intentada.}
@item{Ya que @pd{NúmeroNA} no avanza el punto si falla, de no haber un número
el punto de toda la primera elección no se mueve y @pd{Elegir} intenta la
siguiente elección.}
@item{Ámbos combinadores en la siguiente elección avanzan el punto en todo
caso, así que cualquier error allí es propagado. Si agregáramos otra elección
después de la segunda nunca sería elegida ya que @pd{(Componer: Identificador
TextoLiteral)} nunca fallará sin avanzar el punto.}
)
DOCUMENTA]
funcion Elegir: combs
variable comb
fijar comb a CombElegir#_crear
fijar comb#combinadores a combs
devolver comb
finfuncion
clase CombEfecto hereda Combinador
atributo efecto, combinador
metodo parsear: tokens
finclase
metodo CombEfecto#parsear: tokens
variable res
fijar res a yo#combinador#parsear: tokens
si res#esOk
devolver Resultado#ok: (yo#efecto#\llamar\: res#valor)
sino
devolver res
finsi
finmetodo
[DOCUMENTA
@brief{Devuelve un combinador que transforma un resultado.}
El combinador devuelto es equivalente a @pd{combinador}, pero el valor devuelto
por @pd{combinador} de manera exitosa es transformado con @pd{efecto} (una
función que toma un valor y devuelve un valor) para obtener el resultado de
este combinador de efecto.
Por ejemplo: @pd{Efecto: funcion: X devolver X + 1 finfuncion,
combinadorQueDevuelveUnNúmero} crea un combinador que parsea lo mismo que
@pd{combinadorQueDevuelveUnNúmero} pero cualquier resultado no-erróneo de dicho
se le suma 1. Un ejemplo más útil es el siguiente:
@ejemplo|{
funcion NúmeroLiteral'
devolver Efecto:
funcion: res
devolver res#valor
finfuncion,
NumeroLiteral
finfuncion
}|
Este combinador @pd{NúmeroLiteral'} parsea exactamente lo mismo y tiene el
mismo comportamiento que @pd{NumeroLiteral}, pero en vez de devolver en éxito
un token @pd{TokensNS#TokenNumero} devuelve directamente el @pd{Numero}
contenido por el token.
DOCUMENTA]
funcion Efecto: efecto, combinador
variable comb
fijar comb a CombEfecto#_crear
fijar comb#efecto a efecto
fijar comb#combinador a combinador
devolver comb
finfuncion
clase CombIntentar hereda Combinador
atributo combinador
metodo parsear: tokens
finclase
metodo CombIntentar#parsear: tokens
variables posIni, res
fijar posIni a tokens#puntoActual
fijar res a yo#combinador#parsear: tokens
si res#esError
tokens#irAPunto: posIni
finsi
devolver res
finmetodo
[DOCUMENTA
@brief{Un combinador que intenta parsear otro.}
Intenta parsear con @pd{comb}. Si devuelve un valor exitoso entonces este
combinador también devuelve el mismo valor. @pd{Intentar} solo es interesante
cuando @pd{comb} falla: Irrelevantemente de si @pd{comb} avanzó o no el punto,
@pd{Intenta} lo regresa a su posición antes de iniciar @pd{comb}.
Esto te permite convertir combinadores que, al fallar, avanzarían el punto en
unos que no (y los podrías utilizar, por ejemplo, junto a @pd{Elegir}).
DOCUMENTA]
funcion Intentar: comb
variable inst
fijar inst a CombIntentar#_crear
fijar inst#combinador a comb
devolver inst
finfuncion
clase CombMensajeDeError hereda Combinador
atributos mensaje, combinador
metodo parsear: tokens
finclase
metodo CombMensajeDeError#parsear: tokens
variables res, optTk, pos
fijar pos a tokens#puntoActual
fijar optTk a tokens#leerToken
tokens#irAPunto: pos
fijar res a yo#combinador#parsear: tokens
si res#esError
si optTk = Puerto#EOF
devolver Resultado#error: (Error#conCausa: yo#mensaje, res#error)
sino
devolver Resultado#error: (Error#enTokenConCausa: yo#mensaje, optTk, res#error)
finsi
sino
devolver res
finsi
finmetodo
[DOCUMENTA
@brief{Cambia el mensaje de error de un combinador.}
El combinador devuelto es igual a @pd{combinador}, excepto que cualquier error
producido por @pd{combinador} es transformado para que su causa y lugar sean
iguales, pero su mensaje sea @pd{mensaje}.
DOCUMENTA]
funcion MensajeDeError: mensaje, combinador
variable comb
fijar comb a CombMensajeDeError#_crear
fijar comb#mensaje a mensaje
fijar comb#combinador a combinador
devolver comb
finfuncion
clase CombRecursivo hereda Combinador
atributo obtenerCombinador
metodo parsear: tokens
finclase
metodo CombRecursivo#parsear: tokens
devolver yo#obtenerCombinador#\llamar\#parsear: tokens
finmetodo
[DOCUMENTA
@brief{Un combinador de función.}
@pd{obtenerCombinador} debe ser una función o procedimiento que, al llamarse
sin argumentos, debe devolver un combinador. El combinador devuelto por
@pd{obtenerCombinador} es el que será llamado para parsear los tokens.
El propósito de este combinador es permitir crear gramáticas recursivas, por
ejemplo:
@ejemplo|{
variables Valor, Paréntesis
fijar Valor a Componer:
(Intenta: NumeroLiteral),
(Intenta: TextoLiteral),
(Recursivo: funcion devolver Paréntesis finfuncion)
fijar Paréntesis a Componer:
(Intenta: (PalabraClave: {(})),
Valor,
(PalabraClave: {)})
}|
En la gramática anterior, @pd{Valor} depende de @pd{Lista} y @pd{Lista} de
@pd{Valor}. Esto es posible ya que @pd{Valor} puede utilizar combinadores que
aún no están definidos utilizando @pd{Recursivo}.
DOCUMENTA]
funcion Recursivo: obtenerCombinador
variable comb
fijar comb a CombRecursivo#_crear
fijar comb#obtenerCombinador a obtenerCombinador
devolver comb
finfuncion
clase CombRepetir hereda Combinador
atributo combinador
metodo parsear: tokens
finclase
metodo CombRepetir#parsear: tokens
variable resultados
fijar resultados a Arreglo#vacio
mientras VERDADERO
variables rescomb, posIni, posFinal
fijar posIni a tokens#puntoActual
fijar rescomb a yo#combinador#parsear: tokens
fijar posFinal a tokens#puntoActual
si rescomb#esOk
resultados#agregarAlFinal: rescomb#valor
sino
si posIni = posFinal
devolver Resultado#ok: resultados
sino
devolver rescomb
finsi
finsi
finmientras
finmetodo
[DOCUMENTA
@brief{Repite el combinador un número indefinído de veces.}
Intenta parsear el combinador @pd{combinador} hasta que falle sin avanzar el
punto. Cada valor exitoso es acumulado en un arreglo que es el valor de este
combinador. Si en algún momento @pd{combinador} falla y mueve el punto el error
es propagado.
DOCUMENTA]
funcion Repetir: combinador
variable comb
fijar comb a CombRepetir#_crear
fijar comb#combinador a combinador
devolver comb
finfuncion
clase CombNoSigue hereda Combinador
atributo combinador
metodo parsear: tokens
finclase
metodo CombNoSigue#parsear: tokens
variables posIni, res
fijar posIni a tokens#puntoActual
fijar res a yo#combinador#parsear: tokens
si res#esError
devolver Resultado#ok: NULO
sino
variable tk
tokens#irAPunto: posIni
fijar tk a tokens#leerToken
devolver Resultado#error:
(Error#enToken:
({El combinador de NoSigue la logrado parsear en ~t}#formatear: tk#areaDelToken),
tk)
finsi
finmetodo
[DOCUMENTA
@brief{Se asegura que @pd{combinador} no parsee.}
Es equivalente al operador "not" de muchas bibliotecas de combinadores.
Este combinador intenta parsear @pd{combinador}. Si @pd{combinador} tiene
éxito, el punto es avanzado @bold{solamente por 1} (irrelevantemente de cuanto
consumió @pd{combinador}). Si @pd{combinador} no tiene éxito, este combinador
devuelve el valor @pd{NULO} de manera exitosa y solo avanza el punto tanto como
lo avanzó @pd{combinador} antes de fallar.
DOCUMENTA]
funcion NoSigue: combinador
variable comb
fijar comb a CombNoSigue#_crear
fijar comb#combinador a combinador
devolver comb
finfuncion
clase CombSigue hereda Combinador
atributo combinador
metodo parsear: tokens
finclase
metodo CombSigue#parsear: tokens
variables posIni, res
fijar posIni a tokens#puntoActual
fijar res a yo#combinador#parsear: tokens
si res#esOk
tokens#irAPunto: posIni
finsi
devolver res
finmetodo
[DOCUMENTA
@brief{Este combinador hace que @pd{combinador} no avanze el punto.}
Intenta parsear @pd{combinador}. Cualquier valor o error devuelto por este será
también el valor o error devuelto por @pd{Sigue}. Si @pd{combinador} tiene
éxito, el punto es devuelto al lugar donde estaba antes de comenzar a parsear
@pd{combinador}.
DOCUMENTA]
funcion Sigue: combinador
variable comb
fijar comb a CombSigue#_crear
fijar comb#combinador a combinador
devolver comb
finfuncion
clase CombHasta hereda Combinador
atributos final, principal
metodo parsear: tokens
finclase
metodo CombHasta#parsear: tokens
variables resultados, terminó
fijar resultados a Arreglo#vacio
fijar terminó a FALSO
mientras no terminó
variables posIni, posFinal, resFinal, resPrincipal
fijar posIni a tokens#puntoActual
fijar resFinal a yo#final#parsear: tokens
fijar posFinal a tokens#puntoActual
si resFinal#esOk
fijar terminó a VERDADERO
sino
si no (posIni = posFinal)
devolver resFinal
finsi
tokens#irAPunto: posIni
fijar resPrincipal a yo#principal#parsear: tokens
si resPrincipal#esOk
resultados#agregarAlFinal: resPrincipal#valor
sino
devolver resPrincipal
finsi
finsi
finmientras
devolver Resultado#ok: resultados
finmetodo
[DOCUMENTA
@brief{Parsea un combinador hasta que otro parsee.}
Intenta parsear @pd{terminar}. Si tiene éxito entonces este combinador no
parsea nada más. Si no tiene éxito y no avanzó el punto, intenta parsear
@pd{comb}. Si @pd{comb} falla propaga el error. Los valores devueltos por
@pd{comb} son acumulados en un arreglo, que es el valor de este combinador. Si
@pd{terminar} falla pero avanzó el punto también propaga el error.
DOCUMENTA]
funcion Hasta: terminar, comb
variable inst
fijar inst a CombHasta#_crear
fijar inst#final a terminar
fijar inst#principal a comb
devolver inst
finfuncion