summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/pt/pt_util.tcl
blob: ed39663c3079ec0bde05dbd782987dc31d1723d8 (plain)
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
# -*- tcl -*-
# Copyright (c) 2014 Andreas Kupries <andreas_kupries@sourceforge.net>

# Utility commands for parser syntax errors.

# # ## ### ##### ######## ############# #####################
## Requirements

package require Tcl 8.5 ; # Required runtime.
package require char

# # ## ### ##### ######## ############# #####################
##

namespace eval ::pt::util {
    namespace export error2readable error2position error2text
    namespace ensemble create

    namespace import ::char::quote
}

# # ## ### ##### ######## #############
## Public API

proc ::pt::util::error2readable {error text} {
    lassign $error _ location msgs
    lassign [Position $location $text] l c

    lappend map \n \\n
    lappend map \r \\r
    lappend map \t \\t

    # Get 10 chars before and after the failure point.  Depending on
    # the relative position of input beginning and end we may get less
    # back of either.  Special characters in the input (line endings,
    # tabs) are quoted to keep this on a single line.
    set prefix [string map $map [string range $text ${location}-10 $location]]
    set suffix [string map $map [string range $text ${location}+1 ${location}+10]]

    # Construct a line pointing to the failure position. By using the
    # transformed prefix as our source (length) no complex
    # calculations are required. It is implicit in the prefix/suffix
    # separation above.
    set  n [string length $prefix]
    incr n -1
    set point [string repeat - $n]
    append point ^

    # Print our results.
    lappend lines "Parse error at position $location (Line $l, column $c)."
    lappend lines "... ${prefix}${suffix} ..."
    lappend lines "    $point"
    lappend lines "Expected one of"
    lappend lines "* [join [Readables $msgs] "\n* "]"
    lappend lines ""

    return [join $lines \n]
}

proc ::pt::util::error2position {error text} {
    lassign $error _ location msgs
    return [Position $location $text]
}

proc ::pt::util::error2text {error} {
    lassign $error _ location msgs
    return [Readables $msgs]
}

# # ## ### ##### ######## #############
## Internals

proc ::pt::util::Position {location text} {
    incr location -1

    # Computing the line/col of a position is quite easy. Split the
    # part before the location into lines (at eol), count them, and
    # look at the length of the last line in that.

    set prefix [string range $text 0 $location]
    set lines  [split $prefix \n]
    set line   [llength $lines]
    set col    [string length [lindex $lines end]]

    return [list $line $col]
}

proc ::pt::util::Readables {msgs} {
    set cl {}
    set r {}
    foreach pe $msgs {
	switch -exact -- [lindex $pe 0] {
	    t {
		# Fuse to multiple 't'-tags into a single 'cl'-tag.
		lappend cl [lindex $pe 1]
	    }
	    cl {
		# Fuse multiple 'cl'-tags into one.
		foreach c [split [lindex $pe 1]] { lappend cl $c }
	    }
	    default {
		lappend r [Readable $pe]
	    }
	}
    }
    if {[set n [llength $cl]]} {
	if {$n > 1} {
	    lappend r [Readable [list cl [join [lsort -dict $cl] {}]]]
	} else {
	    lappend r [Readable [list t [lindex $cl 0]]]
	}
    }
    return [lsort -dict $r]
}

proc ::pt::util::Readable {pe} {
    set details [lassign $pe tag]
    switch -exact -- $tag {
	t        {
	    set details [quote string {*}$details]
	    set m "The character '$details'"
	}
	n        { set m "The symbol $details" }
	..       {
	    set details [quote string {*}$details]
	    set m "A character in range '[join $details '-']'"
	}
	str      {
	    set details [join [quote string {*}[split $details {}]] {}]
	    set m "A string \"$details\""
	}
	cl       {
	    set details [join [quote string {*}[split $details {}]] {}]
	    set m "A character in set \{$details\}"
	}
	alpha    { set m "A unicode alphabetical character" }
	alnum    { set m "A unicode alphanumerical character" }
	ascii    { set m "An ascii character" }
	digit    { set m "A unicode digit character" }
	graph    { set m "A unicode printing character, but not space" }
	lower    { set m "A unicode lower-case alphabetical character" }
	print    { set m "A unicode printing character, including space" }
	control  { set m "A unicode control character" }
	punct    { set m "A unicode punctuation character" }
	space    { set m "A unicode space character" }
	upper    { set m "A unicode upper-case alphabetical character" }
	wordchar { set m "A unicode word character (alphanumerics + connectors)" }
	xdigit   { set m "A hexadecimal digit" }
	ddigit   { set m "A decimal digit" }
	dot      { set m "Any character" }
	default  { set m [string totitle $tag] }
    }
    return $m
}

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

package provide pt::util 1.1
return