# -*- tcl -*-
##
# (c) 2017 Andreas Kupries http://wiki.tcl.tk/andreas%20kupries
# http://core.tcl.tk/akupries/
##
# This code is BSD-licensed.
# SLIF support. Transformation of precedenced priority rules into
# non-precedenced form.
##
# See doc/precedence-rewrite
# See doc.1/facts-precedence.txt
# # ## ### ##### ######## #############
## Administrivia
# @@ Meta Begin
# Package marpa::slif::precedence 0
# Meta author {Andreas Kupries}
# Meta category {Parser/Lexer Generator}
# Meta description Part of TclMarpa. Grammar transformer.
# Meta description Rewrites precedenced priority rules into sets where
# Meta description the precedence is directly encoded in the structure
# Meta description of the rules
# Meta location http:/core.tcl.tk/akupries/marpa
# Meta platform tcl
# Meta require {Tcl 8.5}
# Meta require TclOO
# Meta require debug
# Meta require debug::caller
# Meta require marpa::util
# Meta subject marpa transform precedence
# @@ Meta End
# Notes
# - Precedences are integers <= 0.
# - The tightest precedence is 0.
# - The loosest precedence is some K <= 0
#
# - tighter x --> x+1 for x < 0, 0 for x == 0
# - looser x --> x-1 for K < x, K for x == K
#
# Assumptions:
# - E's loosest precedence is K < 0
# - E is the LHS of the precedenced rules
# - E is recursive, i.e. appears in at least one RHS
#
# Rewrite rules
##
# 1. Replace E with
# (( E ::= E[K] ))
#
# 2. Add
# (( E[x] ::= E[tighter(x)] ))
# for all x in [K...1]
#
# 3. Add
# (( E[x] ::= ... E[x] ... E[tighter(x)] ... ))
# for (( E ::= ... E ... E ... ))
# [left-assoc, at precedence x]
#
# 4. Add
# (( E[x] ::= ... E[tighter(x)] ... E[x] ... ))
# for (( E ::= ... E ... E ... ))
# [right-assoc, at precedence x]
#
# 5. Add
# (( E[x] ::= ... E[K] ... )) /f.a E in the input
# for (( E ::= ... E ... ))
# [group-assoc, at precedence x]
#
# Notes on the priority rules in the result:
# - All are at precedence level 0, assoc "left".
# - In case of L0 the attributes action, mask and assoc are
# left out, otherwise see below.
# - All from (3,4,5) have the
# - action,
# - mask, and
# - name
# of their origin rule.
# - All from (1, 2) have
# - action {special hide}, -- A form of `first` which prevents the
# \ symbol from making its own AST node.
# - mask {0}, and
# - no name.
# In generated symbols, i.e. FOO((x)) for origin FOO at level x the
# levels are inverted (0-x), stting -K the loosest precedence.
# Precedence 0 keeps being the tightest. This is easier to read, while
# the negative precedence levels were easier to generate from within
# the semantics.
# Thinking about this now I am not sure what I was thinking at the
# time. Incrementing up to mean `looser precedence` should work just
# as well. <-- Consider FUTURE TODO to redo this part of the
# semantics, and here, of course.
# # ## ### ##### ######## #############
## Requisites
package require Tcl 8.5
package require TclOO ;# Implies Tcl 8.5 requirement.
package require debug
package require debug::caller
package require marpa::util
debug define marpa/slif/precedence
# # ## ### ##### ######## #############
namespace eval ::marpa::slif::precedence {
namespace export rewrite rewrite1 2container
namespace ensemble create
namespace import ::marpa::X
}
# # ## ### ##### ######## #############
## Public API
proc ::marpa::slif::precedence::rewrite {container} {
debug.marpa/slif/precedence {}
set commands {}
foreach layer {g1 l0} {
foreach sym [$container $layer symbols] {
# Without an actual range of precedences a rewrite is not
# worth it. This also automatically exludes the trivially
# non-recursive rules (atoms and quantified) from
# consideration.
set min_p [$container $layer min-precedence $sym]
if {$min_p == 0} continue
# Exclude priority rules which are not recursive. This is
# a more expensive check, iterating over all the
# alternates and their elements, thus why it is the second
# check after the easier test for a precedence range.
if {![$container $layer recursive $sym]} continue
lappend commands {*}[rewrite1 $layer $sym $min_p [$container $layer get $sym]]
}
}
return $commands
}
proc ::marpa::slif::precedence::2container {rewrite container} {
debug.marpa/slif/precedence {}
foreach command $rewrite {
$container {*}$command
}
return
}
proc ::marpa::slif::precedence::rewrite1 {grcode lhs min alternatives} {
debug.marpa/slif/precedence {}
# alternatives :: list(alter)
# alter :: tuple(rhs precedence ...)
# rhs :: list(symbol)
# ... :: {*}dict ( name action assoc mask )
# action, assoc, mask are G1 only
# assoc defaults to "left".
# result :: list(commands)
set result {}
R $lhs
# Note:
# - "attr" is used by "P" to fill in the attributes of the new
# priority rules.
set smin [S $min]
set attr [list action {special hide} mask {0}]
# Rewrite (1) - Ground
P $lhs [list $smin]
# Rewrite (2) - Spine
for {
set current $min
set tighter [expr {$min + 1}]
} {$current < 0} {
incr current
incr tighter} {
P [S $current] [list [S $tighter]]
}
# Rewrite (3-5) - Rules per assoc type
foreach alter $alternatives {
P {*}[MapAlter $lhs $alter $smin]
}
return $result
}
# # ## ### ##### ######## #############
## Internal helpers
proc ::marpa::slif::precedence::MapAlter {lhs alter smin} {
debug.marpa/slif/precedence {}
# Export to caller for import into the following P.
upvar 1 attr attr
set attr [lassign $alter __type__ rhs precedence]
if {[dict exists $attr assoc]} {
set assoc [dict get $attr assoc]
} else {
set assoc left
}
set scurrent [S $precedence]
set stighter [S [expr {min($precedence + 1,0)}]]
switch -exact -- $assoc {
left {
# Rewrite (3)
# E[x] ::= ... E[x] ... E[tighter(x)]...
set newrhs [MapFirst $rhs $lhs $scurrent $stighter]
}
right {
# Rewrite (4)
# E[x] ::= ... E[tighter(x)] ... E[x]...
# Like left, but in reverse.
set newrhs [lreverse [MapFirst [lreverse $rhs] $lhs $scurrent $stighter]]
}
group {
# Rewrite (5)
# E[x] ::= ... E[K] ..., for all E in rhs
set newrhs [MapAll $rhs $lhs $smin]
}
default {
X "Bad assocation $assoc" SLIF PRECEDENCE BAD ASSOC
}
}
return [list $scurrent $newrhs]
}
proc ::marpa::slif::precedence::MapFirst {rhs lhs newfirst newplus} {
debug.marpa/slif/precedence {}
set new $newfirst
return [lmap sym $rhs {
if {$sym eq $lhs} {
set res $new
set new $newplus
set res
} else {
set sym
}
}]
}
proc ::marpa::slif::precedence::MapAll {rhs lhs new} {
debug.marpa/slif/precedence {}
return [lmap sym $rhs {
if {$sym eq $lhs} {
set new
} else {
set sym
}
}]
}
proc ::marpa::slif::precedence::S {k} {
debug.marpa/slif/precedence {}
upvar 1 lhs lhs
return "${lhs}(([expr {-$k}]))"
}
proc ::marpa::slif::precedence::R {sym} {
debug.marpa/slif/precedence {}
upvar 1 grcode grcode result result
lappend result [list $grcode remove $sym]
return
}
proc ::marpa::slif::precedence::P {sym rhs} {
debug.marpa/slif/precedence {}
upvar 1 grcode grcode result result attr attr
set newattr $attr
dict set newattr assoc left
if {$grcode eq "l0"} {
dict unset newattr action
dict unset newattr mask
dict unset newattr assoc
}
lappend result [list $grcode priority-rule $sym $rhs 0 {*}$newattr]
return
}
# # ## ### ##### ######## #############
package provide marpa::slif::precedence 0
return
|