1 # Copyright
(C
) 2021 Free Software Foundation
, Inc.
3 # This file is part of DejaGnu.
5 # DejaGnu is free software
; you can redistribute it and
/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation
; either version
3 of the License
, or
8 #
(at your option
) any later version.
10 # DejaGnu is distributed in the hope that it will be useful
, but
11 # WITHOUT
ANY WARRANTY
; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License
for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with DejaGnu
; if not
, write to the Free Software Foundation
,
17 # Inc.
, 51 Franklin Street
- Fifth Floor
, Boston
, MA
02110-1301, USA.
19 # This file was written by Jacob Bachmeyer.
21 # Procedures
for handling specs strings similar to those used in GCC.
23 # These spec strings support substitutions introduced using
"%":
25 #
%% -- literal
"%" character
26 #
%{...
} -- substitute data value with
recursive evaluation
27 #
%[...
] -- evaluate Tcl code and substitute result literally
29 # All other uses of
"%" in specs strings are reserved. Data item names
30 # containing colon
(":") are generally reserved for future expansion; a few
31 # are currently used as shorthand
for certain DejaGnu API calls.
32 # Convention
for hierarchical
name parts is separation using
".", while "/"
33 # is used
for variations intended to be selected using another value.
35 # Specs are stored in a Tcl array
, referred to as the
"database" array.
36 # Spec strings are organized into layers
, providing a hierarchical
37 # structure of fallback and default
values by searching layers in the order
38 # given by the
"_layers" option.
40 # The external data structures used by this module are mostly association
41 # lists
, but they are internally referenced using Tcl arrays.
43 # All procedures in this module are currently internal to DejaGnu and
44 # subject to change without notice.
45 namespace eval
::dejagnu
::specs
{
46 namespace
export eval_specs validate_specs
49 # Expand one data substitution token.
50 # internal procedure
; uses SPECS and OPTION arrays in caller
's context
51 proc ::dejagnu::specs::subst_token { key } {
52 upvar 1 specs specs option option
54 # check for an option first
55 if { [info exists option($key)] } {
59 # check for a board configuration value
60 if { [regexp {^board_info\(([^)]+)\):(.*)$} $key -> machine info_key] } {
61 return [board_info $machine $info_key]
64 # search the specs database if a layer path was given
65 if { [info exists option(_layers)] } {
66 foreach layer $option(_layers) {
67 if { [info exists specs(layer,$layer,$key)] } {
68 return $specs(layer,$layer,$key)
73 # check for suitable default entry in the specs database
74 if { [info exists specs(base,$key)] } {
75 return $specs(base,$key)
78 error "unresolved specs token: $key"
81 # Evaluate excess open or close delimiters.
82 proc ::dejagnu::specs::delimiter_balance { text } {
83 # first, remove all backslashes that cannot quote delimiters
84 regsub -all {\\+[^][\\{}]} $text "" text
85 # strip backslash-quoted backslashes
86 regsub -all {(?:\\{2})+} $text "" text
87 # strip backslash-quoted delimiters
88 regsub -all {(^|[^\\])\\[][{}]} $text "\\1" text
89 # remove all unrelated characters
90 regsub -all {[^][{}]+} $text "" text
92 # separate the text into only-left and only-right subsets
93 regsub -all "\\\\*\[\]\}\]" $text "" left
94 regsub -all "\\\\*\[\[\{\]" $text "" right
96 return [expr { [string length $left] - [string length $right] }]
99 # Find the end of a token.
100 proc ::dejagnu::specs::token_end { text start end_pat } {
103 while { $balance > 0 } {
104 regexp -indices -start [expr { 1 + $point }] -- $end_pat $text item
105 set point [lindex $item 0]
106 # optimization: if delimiter_balance returns N, we need at least N
107 # more closing delimiters, but that could be any combination of
108 # braces and brackets, not only the main endpoint delimiter
110 set balance [delimiter_balance [string range $text $start $point]]
111 } { $balance > 1 } { incr balance -1 } {
112 regexp -indices -start [expr { 1 + $point }] -- \
113 "\[\\\}\\\]\]" $text item
114 set point [lindex $item 0]
117 return [lindex $item 1]
120 # Abstract parsing loop.
121 # internal procedure; sets TOKEN variable in caller's
context
122 proc
::dejagnu
::specs
::scan_specs_string
{ text literal char data code
} {
128 } { [regexp
-indices
-start $point
-- {%.
} $
text item
] } {
129 set point
[expr
{ 1 + $mark
}]
131 # extract literal from preceding range
132 set token
[string range $
text \
133 [expr
{ $mark
+ 1 }] \
134 [expr
{ [lindex $item
0] - 1 }]]
137 set point
[lindex $item
1]
138 # extract first character of substitution
139 set enter
[string index $
text $point
]
140 if { $enter eq
"%" } {
144 } elseif
{ $enter eq
"\{" } {
145 #
%{...
} -- substitute data item
146 set mark
[token_end $
text $point
"\\\}"]
147 set token
[string range $
text \
148 [expr
{ $point
+ 1 }] [expr
{ $mark
- 1 }]]
150 } elseif
{ $enter eq
"\[" } {
151 #
%[...
] -- substitute value from Tcl code fragment
152 set mark
[token_end $
text $point
"\\\]"]
153 set token
[string range $
text \
154 [expr
{ $point
+ 1 }] [expr
{ $mark
- 1 }]]
157 error
"unrecognized sequence %$enter in spec string"
160 # leave the trailing literal in TOKEN
161 set token
[string range $
text [expr
{ $mark
+ 1 }] end
]
164 # Generate
parse report for specs string
; for debugging
165 proc
::dejagnu
::specs
::parse_specs_string
{ text } {
167 scan_specs_string $
text {
168 # intervening literal
text
169 lappend tokens
[list
text $token
]
171 lappend tokens
[list
text %]
173 lappend tokens
[list data $token
]
175 lappend tokens
[list code $token
]
177 lappend tokens
[list
text $token
]
181 # Expand substitutions in specs string.
182 # internal procedure
; uses SPECS and OPTION arrays and BASE_LEVEL
variable
183 # in caller
's context
184 proc ::dejagnu::specs::eval_specs_string { text } {
185 upvar 1 specs specs option option base_level base_level
188 scan_specs_string $text {
189 # copy intervening literal text to output
192 # emit "%" where string contains "%%"
195 # substitute data item
196 append output [eval_specs_string \
197 [subst_token [eval_specs_string $token]]]
199 # evaluate Tcl code fragment
200 append output [uplevel "#$base_level" [eval_specs_string $token]]
202 # copy trailing literal
208 # Check that the provided specs string can be evaluated; that is, that all
209 # substitutions have definitions.
210 # internal procedure; uses SPECS and OPTION arrays in caller's
context
211 proc
::dejagnu
::specs
::validate_specs_string
{ text } {
212 upvar
1 specs specs option option
214 scan_specs_string $
text {
215 # ignore literal
text
221 # check Tcl code fragment
223 # ignore trailing literal
225 # an error is thrown
if validation fails
229 # Perform spec substitutions to evaluate
%{GOAL
}.
231 # The DATABASE_NAME is the
name (in the caller
's context) of the database
232 # array to use, while OPTIONS is a list of additional KEY VALUE pairs that
233 # should be available for substitution.
234 proc ::dejagnu::specs::eval_specs { database_name goal options } {
235 upvar 1 $database_name specs
236 array set option $options
237 set base_level [expr { [info level] - 1 }]
239 return [eval_specs_string "%{$goal}"]
242 # Load specs strings into DATABASE_NAME; as:
243 # load_specs DATABASE_NAME BASE_STRINGS (LAYER_NAME LAYER_STRINGS)...
244 # to load only into a layer:
245 # load_specs DATABASE_NAME {} LAYER_NAME LAYER_STRINGS
246 proc ::dejagnu::specs::load_specs { database_name base_strings args } {
247 upvar 1 $database_name specs
249 if { ([llength $args] & 1) != 0 } {
250 error "specs layer names and contents must be in pairs"
252 foreach {k v} $base_strings {
253 set specs(base,$k) $v
255 foreach {layer layer_strings} $args {
256 foreach {k v} $layer_strings {
257 set specs(layer,$layer,$k) $v
262 # Display contents of specs database array; for debugging
263 proc ::dejagnu::specs::dump_specs { database_name } {
264 upvar 1 $database_name specs
266 set keys [lsort -dictionary [array names specs]]
267 # all defaults (base,*) sort ahead of all layers (layer,*,*)
269 puts "Specs $database_name:\n"
270 for { set i 0 } { ($i < [llength $keys])
271 && [regexp {^base,(.*)$} [lindex $keys $i] \
274 puts "*$name:\n$specs([lindex $keys $i])\n"
277 for { set prev "" } { ($i < [llength $keys])
278 && [regexp {^layer,([^,]+),(.*)$} [lindex $keys $i] \
281 if { $prev ne $layer } {
285 puts "*$name:\n$specs([lindex $keys $i])\n"
289 # Validate a specs database
290 proc ::dejagnu::specs::validate_specs { database_name } {
291 upvar 1 $database_name specs