source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/grammar_peg/peg.tcl @ 8597867d

1941-docker-image-to-testdevel
Last change on this file since 8597867d was 8597867d, checked in by Jorge Pérez <josp.jorge@…>, 16 years ago

closes #976, se incluye tcllib1.10, base64 debe eliminarse del raiz de lib ya que esta incluido en tcllib

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@2556 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 12.7 KB
Line 
1# -*- tcl -*-
2#
3# Copyright (c) 2005 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
4# Grammars / Parsing Expression Grammars / Container
5
6# ### ### ### ######### ######### #########
7## Package description
8
9# A class whose instances hold all the information describing a single
10# parsing expression grammar (terminal symbols, nonterminal symbols,
11# nonterminal rules, start expression, hints), and operations to
12# define, manipulate, and query this information.
13#
14# The container has only one functionality beyond the simple storage
15# of the aforementioned information. It keeps track if the provided
16# grammar is valid (*). The container provides no higher-level
17# operations on the grammar, like removal of unreachable nonterminals,
18# rule rewriting, etc.
19#
20# The set of terminal symbols is the set of characters (i.e.
21# implicitly defined). For Tcl this means that all the unicode
22# characters are supported.
23#
24# (*) A grammar is valid if and only if all its rules are valid.  A
25# rule is valid if and only if all nonterminals referenced by the RHS
26# of the rule are in the set of nonterminals, and if only the allowed
27# operators are used in the expression.
28
29# ### ### ### ######### ######### #########
30## Requisites
31
32package require snit         ; # Tcllib | OO system used
33
34# ### ### ### ######### ######### #########
35## Implementation
36
37snit::type ::grammar::peg {
38    # ### ### ### ######### ######### #########
39    ## Type API. Helpful methods for PEs.
40
41    proc ValidateSerial {e prefix} {}
42    proc Validate   {e} {}
43    proc References {e} {}
44    proc Rename     {e old new} {}
45
46    # ### ### ### ######### ######### #########
47    ## Instance API
48
49    constructor {args} {}
50
51    method clear {} {}
52
53    method =   {src} {}
54    method --> {dst} {}
55    method serialize {} {}
56    method deserialize {value} {}
57
58    method {is valid} {} {}
59    method start {args} {}
60
61    method nonterminals {} {}
62    method {nonterminal add}    {nts pae} {}
63    method {nonterminal delete} {nts pae} {}
64    method {nonterminal exists} {nts} {}
65    method {nonterminal rename} {ntsold ntsnew} {}
66    method {nonterminal mode}   {nts args} {}
67
68    method {unknown nonterminals} {} {}
69
70    method {nonterminal rule}   {nts} {}
71
72    # ### ### ### ######### ######### #########
73    ## Internal data structures.
74
75    ## - Set of nonterminal symbols, and
76    ## - Mapping from nonterminals to their defining parsing
77    ##   expressions, and
78    ## - Start parsing expression.
79    ## - And usage of nonterminals by others, required for tracking
80    ##   of validity.
81
82    ## se: expression               | Start expression
83    ## nt: nonterm -> expression    | Known Nt's, their rules
84    ## re: nonterm -> list(nonterm) | Known Nt's, what others they use.
85    ## ir: nonterm -> list(nonterm) | Nt's, possibly unknown, their users.
86    ## uk: nonterm -> use counter   | Nt's which are unknown.
87    ##
88    ## Both 're' and 'ir' can list a nonterminal A multiple times,
89    ## if it uses or is used multiple times.
90    ##
91    ## Grammar is invalid <=> '[array size uk] > 0'
92
93    variable se        epsilon
94    variable nt -array {}
95    variable re -array {}
96    variable ir -array {}
97    variable uk -array {}
98    variable mo -array {}
99
100    # ### ### ### ######### ######### #########
101    ## Instance API Implementation.
102
103    constructor {args} {
104        if {
105            (([llength $args] != 0) && ([llength $args] != 2)) ||
106            (([llength $args] == 2) && ([lsearch {= := <-- as deserialize} [lindex $args 0]]) < 0)
107        } {
108            return -code error "wrong#args: $self ?=|:=|<--|as|deserialize a'?"
109        }
110
111        # Serialization arguments.
112        # [llength args] in {0 2}
113        #
114        # =           src-obj
115        # :=          src-obj
116        # <--         src-obj
117        # as          src-obj
118        # deserialize src-value
119
120        if {[llength $args] == 2} {
121            foreach {op val} $args break
122            switch -exact -- $op {
123                = - := - <-- - as {
124                    $self deserialize [$val serialize]
125                }
126                deserialize {
127                    $self deserialize $val
128                }
129            }
130        }
131        return
132    }
133
134    #destructor {}
135
136    method clear {} {
137        array unset nt *
138        array unset re *
139        array unset ir *
140        array unset uk *
141        array unset mo *
142        set se epsilon
143        return
144    }
145
146    method = {src} {
147        $self dserialize [$src serialize]
148    }
149
150    method --> {dst} {
151        $dst deserialize [$self serialize]
152    }
153
154    method serialize {} {
155        return [::list \
156                grammar::pegc \
157                [array get nt] \
158                [array get mo] \
159                $se]
160    }
161
162    method deserialize {value} {
163        # Validate value, then clear and refill.
164
165        $self CheckSerialization $value ntv mov sev
166        $self clear
167
168        foreach {s e} $ntv {
169            $self NtAdd $s $e
170        }
171        array set mo $mov
172        $self start $sev
173        return
174    }
175
176    method {is valid} {} {
177        return [expr {[array size uk] == 0}]
178    }
179
180    method start {args} {
181        if {[llength $args] == 0} {
182            return $se
183        }
184        if {[llength $args] > 1} {
185            return -code error "wrong#args: $self start ?pe?"
186        }
187        set newse [lindex $args 0]
188        Validate $newse
189        set se   $newse
190        return
191    }
192
193    method nonterminals {} {
194        return [array names nt]
195    }
196
197    method {nonterminal add} {nts pae} {
198        $self CheckNtKnown $nts
199        Validate $pae
200        $self NtAdd $nts $pae
201        return
202    }
203
204    method {nonterminal mode} {nts args} {
205        $self CheckNt $nts
206        if {![llength $args]} {
207            return $mo($nts)
208        } elseif {[llength $args] == 1} {
209            set mo($nts) [lindex $args 0]
210            return
211        } else {
212            return -code error "wrong#args"
213        }
214        return
215    }
216
217    method {nonterminal delete} {nts args} {
218        set args [linsert $args 0 $nts]
219        foreach nts $args {
220            $self CheckNt $nts
221        }
222
223        foreach nts $args {
224            $self NtDelete $nts
225        }
226        return
227    }
228
229    method {nonterminal exists} {nts} {
230        return [info exists nt($nts)]
231    }
232
233    method {nonterminal rename} {ntsold ntsnew} {
234        $self CheckNt      $ntsold
235        $self CheckNtKnown $ntsnew
236
237        # Difficult. We have to go through all rules and rewrite their
238        # RHS to use the new name of the nonterminal. We can however
239        # restrict ourselves to the rules which actually use the
240        # changed nonterminal.
241
242        # We also have to update the used/user information. We know
243        # that the validity of the grammar is unchanged by this
244        # operation. The unknown information is unchanged as well, as
245        # we cannot rename an unknown nonterminal. IOW we know that
246        # 'ntsold' is not in 'uk', and so 'ntsnew' will not be in that
247        # array either after the rename.
248
249        set myusers $ir($ntsold)
250        set myused  $re($ntsold)
251
252        set nt($ntsnew) $nt($ntsold)
253        unset            nt($ntsold)
254
255        set mo($ntsnew) $mo($ntsold)
256        unset            mo($ntsold)
257
258        foreach x $myusers {
259            set nt($x) [Rename $nt($x) $ntsold $ntsnew]
260        }
261
262        # It is possible to use myself, and be used by myself.
263
264        while {[set pos [lsearch -exact $myusers $ntsold]] >= 0} {
265            set myusers [lreplace $myusers $pos $pos $ntsnew]
266        }
267        while {[set pos [lsearch -exact $myused $ntsold]] >= 0} {
268            set myused [lreplace $myused $pos $pos $ntsnew]
269        }
270
271        set re($ntsnew) $myusers
272        set ir($ntsnew) $myused
273
274        unset            re($ntsold)
275        unset            ir($ntsold)
276        return
277    }
278
279    method {unknown nonterminals} {} {
280        return [array names uk]
281    }
282
283    method {nonterminal rule} {nts} {
284        $self CheckNt $nts
285        return $nt($nts)
286    }
287
288    # ### ### ### ######### ######### #########
289    ## Internal helper methods
290
291    method NtAdd {nts pae} {
292        # None of the symbols is known. We can add them to the
293        # grammar. If however any of their PEs is known to the PE
294        # storage then we had expressions refering to unknown
295        # symbols. The grammar is most certainly invalid and may have
296        # become valid right now. We have to invalidate the validity
297        # cache.
298
299        set nt($nts) $pae
300        set mo($nts) value
301
302        # Track users, uses, and unknowns.
303
304        set references [References $pae]
305
306        # We use the refered symbols
307        set re($nts) $references
308
309        # We are a user for the refered symbols
310        # Record unknown symbols immediately.
311        foreach x $references {
312            lappend ir($x) $nts
313            if {[info exists nt($x)]} continue
314            if {[catch {incr uk($x)}]} {set uk($x) 1}
315        }
316
317        # We are definitely not unknown.
318        unset -nocomplain uk($nts)
319        return
320    }
321
322    method NtDelete {nts} {
323        set references $re($nt)
324
325        # We are gone. We are not using anything anymore.
326        unset    nt($nts)
327        unset    re($nts)
328        unset    mo($nts)
329
330        # Our references loose us as their user.
331        foreach x $references {
332            set pos [lsearch -exact $ir($x) $x]
333            if {$pos < 0} {error PANIC}
334            set ir($x) [lreplace $ir($x) $pos $pos]
335            if {[llength $ir($x)] == 0} {
336                unset ir($x)
337                # x is not referenced anywhere, cannot be unknown.
338                unset -nocomplain uk($x)
339            }
340            if {[info exists uk($x)]} {
341                incr uk($x) -1
342            }
343        }
344
345        # We might be used by others still, and therefore become
346        # unknown.
347
348        if {[info exists ir($nts]} {
349            set uk($nts) [llength $ir($nts)]
350        }
351        return
352    }
353
354    method CheckNt {nts} {
355        if {![info exists nt($nts)]} {
356            return -code error "Invalid nonterminal \"$nts\""
357        }
358        return
359    }
360
361    method CheckNtKnown {nts} {
362        if {[info exists nt($nts)]} {
363            return -code error "Nonterminal \"$nts\" is already known"
364        }
365        return
366    }
367
368    method CheckSerialization {value ntv mov sev} {
369        # value is list/3 ('grammar::pegc' nonterminals start)
370        # terminals is list of string.
371        # nonterminals is doct (key is string, value is expr)
372        # start is expr
373        # terminals * nonterminals == empty
374        # expr is parsing expression (Validate PE).
375
376        upvar 1 \
377            $ntv ntvs \
378            $mov movs \
379            $sev sevs
380
381        set prefix "error in serialization:"
382        if {[llength $value] != 4} {
383            return -code error "$prefix list length not 4"
384        }
385
386        struct::list assign $value type nonterminals hints start
387        if {$type ne "grammar::pegc"} {
388            return -code error "$prefix unknown type \"$type\""
389        }
390
391        ValidateSerial $start "$prefix invalid start expression"
392
393        if {[llength $nonterminals] % 2 == 1} {
394            return -code error "$prefix nonterminal data is not a dictionary"
395        }
396        array set _nt $nonterminals
397        if {[llength $nonterminals] != (2*[array size _nt])} {
398            return -code error "$prefix nonterminal data contains duplicate names, or misses some"
399        }
400
401        foreach {s e} $nonterminals {
402            ValidateSerial $start "$prefix nonterminal \"$s\", invalid parsing expression"
403        }
404
405
406        if {[llength $hints] % 2 == 1} {
407            return -code error "$prefix nonterminal modes is not a dictionary"
408        }
409        array set _mo $hints
410        if {[llength $hints] != (2*[array size _mo])} {
411            return -code error "$prefix nonterminal modes contains duplicate names, or misses some"
412        }
413        foreach {s _} $hints {
414            if {![info exists _nt($s)]} {
415                return -code error "$prefix nonterminal mode for unknown nonterminal \"$s\""
416            }
417        }
418
419        set ntvs $nonterminals
420        set sevs $start
421        set movs $hints
422        return
423    }
424
425    # ### ### ### ######### ######### #########
426
427    # ### ### ### ######### ######### #########
428    ## Type API implementation.
429
430    proc ValidateSerial {e prefix} {
431        if {![catch {Validate $e} msg]} return
432        return -code error "$prefix, $msg"
433    }
434
435    proc Validate {e} {
436        if {[llength $e] == 0} {
437            return -code error "invalid empty expression list"
438        }
439
440        set op [lindex $e 0]
441        set ar [lrange $e 1 end]
442
443        switch -exact -- $op {
444            epsilon - alpha - alnum - dot {
445                if {[llength $ar] > 0} {
446                    return -code error "wrong#args for \"$op\""
447                }
448            }
449            .. {
450                if {[llength $ar] != 2} {
451                    return -code error "wrong#args for \"$op\""
452                }
453                # Leaf, arguments are not expressions to validate.
454            }
455            n - t {
456                if {[llength $ar] != 1} {
457                    return -code error "wrong#args for \"$op\""
458                }
459                # Leaf, argument is not expression to validate.
460            }
461            & - ! - * - + - ? {
462                if {[llength $ar] != 1} {
463                    return -code error "wrong#args for \"$op\""
464                }
465                Validate [lindex $ar 0]
466            }
467            x - / {
468                if {![llength $ar]} {
469                    return -code error "wrong#args for \"$op\""
470                }
471                foreach e $ar {
472                    Validate $e
473                }
474            }
475            default {
476                return -code error "invalid operator \"$op\""
477            }
478        }
479    }
480
481    proc References {e} {
482        set references {}
483
484        set op [lindex $e 0]
485        set ar [lrange $e 1 end]
486
487        switch -exact -- $op {
488            epsilon - t - alpha - alnum - dot - .. {}
489            n {
490                # Remember referenced nonterminal
491                lappend references [lindex $ar 0]
492            }
493            & - ! - * - + - ? {
494                foreach r [References [lindex $ar 0]] {
495                    lappend references $r
496                }
497            }
498            x - / {
499                foreach e $ar {
500                    foreach r [References $e] {
501                        lappend references $r
502                    }
503                }
504            }
505        }
506        return $references
507    }
508
509    proc Rename {e old new} {
510        set op [lindex $e 0]
511        set ar [lrange $e 1 end]
512
513        switch -exact -- $op {
514            epsilon - t - alpha - alnum - dot - .. {return $e}
515            n {
516                if {[lindex $ar 0] ne $old} {return $e}
517                return [list n $new]
518            }
519            & - ! - * - + - ? {
520                return [list $op [Rename [lindex $ar 0] $old $new]]
521            }
522            x - / {
523                set res $op
524                foreach e $ar {
525                    lappend res [Rename $e $old $new]
526                }
527                return $res
528            }
529        }
530    }
531
532    # ### ### ### ######### ######### #########
533    ## Type Internals.
534
535    # ### ### ### ######### ######### #########
536}
537
538# ### ### ### ######### ######### #########
539## Package Management
540
541package provide grammar::peg 0.1
Note: See TracBrowser for help on using the repository browser.