File: analysis_peg_emodes.tcl

package info (click to toggle)
tcllib 1.20%2Bdfsg-1
  • links: PTS
  • area: main
  • in suites: bullseye
  • size: 68,064 kB
  • sloc: tcl: 216,842; ansic: 14,250; sh: 2,846; xml: 1,766; yacc: 1,145; pascal: 881; makefile: 107; perl: 84; f90: 84; python: 33; ruby: 13; php: 11
file content (458 lines) | stat: -rw-r--r-- 11,903 bytes parent folder | download | duplicates (8)
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
# -*- tcl -*-
# ### ### ### ######### ######### #########

# Perform mode analysis (x) on the PE grammar delivered by the
# frontend. The grammar is in normalized form (*).
#
# (x) = See "doc_emodes.txt".
#       and "doc_emodes_alg.txt".
# (*) = See "doc_normalize.txt".

# This package assumes to be used from within a PAGE plugin. It uses
# the API commands listed below. These are identical across the major
# types of PAGE plugins, allowing this package to be used in reader,
# transform, and writer plugins. It cannot be used in a configuration
# plugin, and this makes no sense either.
#
# To ensure that our assumption is ok we require the relevant pseudo
# package setup by the PAGE plugin management code.
#
# -----------------+--
# page_info        | Reporting to the user.
# page_warning     |
# page_error       |
# -----------------+--
# page_log_error   | Reporting of internals.
# page_log_warning |
# page_log_info    |
# -----------------+--

# ### ### ### ######### ######### #########
## Requisites

# @mdgen NODEP: page::plugin

package require page::plugin     ; # S.a. pseudo-package.
package require page::util::flow ; # Dataflow walking.
package require page::util::peg  ; # General utilities.
package require treeql

namespace eval ::page::analysis::peg::emodes {
    namespace import ::page::util::peg::*
}

# ### ### ### ######### ######### #########
## API

proc ::page::analysis::peg::emodes::compute {t} {

    # Ignore call if already done before
    if {[$t keyexists root page::analysis::peg::emodes]} {return 1}

    # We do not actually compute per node a mode, but rather their
    # gen'erate and acc'eptance properties, as described in
    # "doc_emodes.txt".

    # Note: This implementation will not compute acc/gen information
    # for unreachable nodes.

    # --- --- --- --------- --------- ---------

    array set acc  {} ; # Per node X, acc(X), undefined if no element
    array set call {} ; # Per definition node, number of users
    array set cala {} ; # Per definition node, number of (non-)accepting users

    foreach {sym def} [$t get root definitions] {
	set call($def)   [llength [$t get $def users]]
	set cala(0,$def) 0
	set cala(1,$def) 0
    }

    set acc(root) 1 ; # Sentinel for root of start expression.

    # --- --- --- --------- --------- ---------

    #puts stderr ~~~~\t~~~\t~~~\t~~~\t~~~
    #puts stderr Node\tAcc\tNew\tWhat\tOp
    #puts stderr ~~~~\t~~~\t~~~\t~~~\t~~~

    # A node is visited if its value for acc() is either undefined or
    # may have changed. Basic flow is top down, from the start
    # expression and a definition a child of its invokers.

    set gstart [$t get root start]
    if {$gstart eq ""} {
	page_error "  No start expression, unable to compute accept/generate properties"
	return 0
    }

    page::util::flow [list $gstart] flow n {
	# Determine first or new value.

	#puts -nonewline stderr [string replace $n 1 3]

	if {![info exists acc($n)]} {
	    set a [Accepting $t $n acc call cala]
	    set acc($n) $a
	    set change 0

	    #puts -nonewline stderr \t-\t$a\t^
	} else {
	    set a   [Accepting $t $n acc call cala]
	    set old $acc($n)
	    if {$a == $old} {
		#puts stderr \t$old\t$a\t\ =
		continue
	    }
	    set change 1
	    set acc($n) $a

	    #puts -nonewline stderr \t$old\t$a\t\ \ *
	}

	# Update counters in definitions, if the node invokes them.
	# Also, schedule the children for their (re)definition.

	if {[$t keyexists $n symbol]} {
	    #puts -nonewline stderr \t\ DEF\t[$t get $n symbol]\t[$t get $n mode]
	} else {
	    #puts -nonewline stderr \t[$t get $n op]\t\t
	}

	if {[$t keyexists $n op] && ([$t get $n op] eq "n")} {
	    #puts -nonewline stderr ->\ [$t get $n sym]
	    set def [$t get $n def]
	    if {$def eq ""} continue

	    if {$change} {
		incr cala($old,$def) -1
	    }
	    incr cala($a,$def)
	    $flow visit $def

	    #puts -nonewline stderr @$def\t(0a$cala(0,$def),\ 1a$cala(1,$def),\ #$call($def))\tv($def)
	    #puts stderr ""
	    continue
	}

	#puts stderr \t\t\t\tv([$t children $n])
	$flow visitl [$t children $n]
    }

    # --- --- --- --------- --------- ---------

    array set gen {} ; # Per node X, gen(X), undefined if no element
    array set nc  {} ; # Per node, number of children
    array set ng  {} ; # Per node, number of (non-)generating children

    foreach n [$t nodes] {
	set nc($n)       [$t numchildren $n]
	set ng(0,$n)     0
	set ng(1,$n)     0
    }

    # --- --- --- --------- --------- ---------

    #puts stderr ~~~~\t~~~\t~~~\t~~~\t~~~
    #puts stderr Node\tGen\tNew\tWhat\tOp
    #puts stderr ~~~~\t~~~\t~~~\t~~~\t~~~

    # A node is visited if its value for gen() is either undefined or
    # may have changed. Basic flow is bottom up, from the all
    # leaves (and lookahead operators). Users of a definition are
    # considered as its parents.

    set start [$t leaves]
    set q [treeql q -tree $t]
    q query tree withatt op ! over n {lappend start $n}
    q query tree withatt op & over n {lappend start $n}
    q destroy

    page::util::flow $start flow n {
	# Ignore root.

	if {$n eq "root"} continue

	#puts -nonewline stderr [string replace $n 1 3]

	# Determine first or new value.

	if {![info exists gen($n)]} {
	    set g [Generating $t $n gen nc ng acc call cala]
	    set gen($n) $g

	    #puts -nonewline stderr \t-\t$g\t^

	} else {
	    set g   [Generating $t $n gen nc ng acc call cala]
	    set old $gen($n)
	    if {$g eq $old} {
		#puts stderr \t$old\t$g\t\ =
		continue
	    }
	    set gen($n) $g

	    #puts -nonewline stderr \t$old\t$g\t\ \ *
	}

	if {($g ne "maybe") && !$g && $acc($n)} {
	    # No generate here implies that none of our children will
	    # generate anything either. So the current acceptance of
	    # these non-existing values can be safely forced to
	    # non-acceptance.

	    set acc($n) 0
	    #puts -nonewline stderr "-a"
	}

	if {0} {
	    if {[$t keyexists $n symbol]} {
		#puts -nonewline stderr \t\ DEF\t[$t get $n symbol]\t[$t get $n mode]
	    } else {
		#puts -nonewline stderr \t[$t get $n op]\t\t
	    }
	}

	#puts -nonewline stderr \t(0g$ng(0,$n),1g$ng(1,$n),\ #$nc($n))

	# Update counters in the (virtual) parents, and schedule them
	# for a visit.

	if {[$t keyexists $n symbol]} {
	    # Users are virtual parents.

	    set users  [$t get $n users]
	    $flow visitl $users

	    if {$g ne "maybe"} {
		foreach u $users {incr ng($g,$u)}
	    }
	    #puts stderr \tv($users)
	    continue
	}

	set p [$t parent $n]
	$flow visit $p
	if {$g ne "maybe"} {
	    incr ng($g,$p)
	}

	#puts stderr \tv($p)
    }

    # --- --- --- --------- --------- ---------

    # Copy the calculated data over into the tree.
    # Note: There will be no data for unreachable nodes.

    foreach n [$t nodes] {
	if {$n eq "root"}           continue
	if {![info exists acc($n)]} continue
	$t set $n acc $acc($n)
	$t set $n gen $gen($n)
    }

    # Recompute the modes based on the current
    # acc/gen status of the definitions.

    #puts stderr ~~~~\t~~~\t~~~~\t~~~\t~~~\t~~~
    #puts stderr Node\tSym\tMode\tNew\tGen\tAcc
    #puts stderr ~~~~\t~~~\t~~~~\t~~~\t~~~\t~~~

    foreach {sym def} [$t get root definitions] {
	set m {}

	set old [$t get $def mode]

	if {[info exists acc($def)]} {
	    switch -exact -- $gen($def)/$acc($def) {
		0/0     {set m discard}
		0/1     {error "Bad gen/acc for $sym"}
		1/0     {# don't touch (match, leaf)}
		1/1     {set m value}
		maybe/0 {error "Bad gen/acc for $sym"}
		maybe/1 {set m value}
	    }
	    if {$m ne ""} {
		# Should check correctness of change, if any (We can drop
		# to discard, nothing else).
		$t set $def mode $m
	    }
	    #puts stderr [string replace $def 1 3]\t$sym\t$old\t[$t get $def mode]\t[$t get $def gen]\t[$t get $def acc]
	} else {
	    #puts stderr [string replace $def 1 3]\t$sym\t$old\t\t\t\tNOT_REACHED
	}
    }

    #puts stderr ~~~~\t~~~\t~~~~\t~~~\t~~~\t~~~

    # Wrap up the whole state and save it in the tree. No need to
    # throw this away, useful for other mode based transforms and
    # easier to get in this way than walking the tree again.

    $t set root page::analysis::peg::emodes [list \
	    [array get acc] \
	    [array get call] \
	    [array get cala] \
	    [array get gen] \
	    [array get nc] \
	    [array get ng]]
    return 1
}

proc ::page::analysis::peg::emodes::reset {t} {
    # Remove marker, allow recalculation of emodesness after changes.

    $t unset root page::analysis::peg::emodes
    return
}

# ### ### ### ######### ######### #########
## Internal

proc ::page::analysis::peg::emodes::Accepting {t n av cv cav} {
    upvar 1 $av acc $cv call $cav cala

    # Definitions accept based on how they are called first, and on
    # their mode if that is not possible.

    if {[$t keyexists $n symbol]} {
	# Call based acceptance.
	# !acc if all callers do not accept.

	if {$cala(0,$n) >= $call($n)} {
	    return 0
	}

	# Falling back to mode specific accptance
	return [expr {([$t get $n mode] eq "value") ? 1 : 0}]
    }

    set op [$t get $n op]

    # Lookahead operators will never accept.

    if {($op eq "!") || ($op eq "&")} {
	return 0
    }

    # All other operators inherit the acceptance
    # of their parent.

    return $acc([$t parent $n])
}

proc ::page::analysis::peg::emodes::Generating {t n gv ncv ngv av cv cav} {
    upvar 1 $gv gen $ncv nc $ngv ng $av acc $cv call $cav cala
    #           ~~~      ~~      ~~     ~~~     ~~~~      ~~~~

    # Definitions generate based on their mode, their defining
    # expression, and the acceptance of their callers.

    if {[$t keyexists $n symbol]} {

	# If no caller accepts a value, then this definition will not
	# generate one, even if its own mode asked it to do so.

	if {$cala(0,$n) >= $call($n)} {
	    return 0
	}

	# The definition has callers accepting values and callres not
	# doing so. It will generate as per its own mode and defining
	# expression.

	# The special modes know if they generate a value or not.
	# The pass through mode looks at the expression for the
	# information.

	switch -exact -- [$t get $n mode] {
	    value   {return $gen([lindex [$t children $n] 0])}
	    match   {return 1}
	    leaf    {return 1}
	    discard {return 0}
	}
	error PANIC
    }

    set op [$t get $n op]

    # Inner nodes generate based on operator and children.

    if {$nc($n)} {
	switch -exact -- $op {
	    ! - & {return 0}
	    ? - * {
		# No for all children --> no
		# Otherwise           --> maybe

		if {$ng(0,$n) >= $nc($n)} {
		    return 0
		} else {
		    return maybe
		}
	    }
	    + - / - | {
		# Yes for all children --> yes
		# No for all children  --> no
		# Otherwise            --> maybe

		if {$ng(1,$n) >= $nc($n)} {
		    return 1
		} elseif {$ng(0,$n) >= $nc($n)} {
		    return 0
		} else {
		    return maybe
		}
	    }
	    x {
		# Yes for some children --> yes
		# No for all children   --> no
		# Otherwise             --> maybe

		if {$ng(1,$n) > 0} {
		    return 1
		} elseif {$ng(0,$n) >= $nc($n)} {
		    return 0
		} else {
		    return maybe
		}
	    }
	}
	error PANIC
    }

    # Nonterminal leaves generate based on acceptance from their
    # parent and the referenced definition.

    # As acc(X) == acc(parent(X)) the test doesn't have to go to the
    # parent itself.

    if {$op eq "n"} {
	if {[info exists acc($n)] && !$acc($n)} {return 0}

	set def [$t get $n def]

	# Undefine symbols do not generate anything.
	if {$def eq ""} {return 0}

	# Inherit directly from the definition, if existing.
	if {![info exists gen($def)]} {
	    return maybe
	}

	return $gen($def)
    }

    # Terminal leaves generate values if and only if such values are
    # accepted by their parent. As acc(X) == acc(parent(X) the test
    # doesn't have to go to the parent itself.


    return $acc($n)
}

# ### ### ### ######### ######### #########
## Ready

package provide page::analysis::peg::emodes 0.1