blob: 70444d3463f1d363ead7c23a6a378f39b7824d26 [file] [log] [blame]
# Copyright (C) 2021 Free Software Foundation, Inc.
#
# This file is part of DejaGnu.
#
# DejaGnu is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DejaGnu is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with DejaGnu. If not, see <http://www.gnu.org/licenses/>.
# This file was written by Jacob Bachmeyer.
# Procedures for handling specs strings similar to those used in GCC.
# These spec strings support substitutions introduced using "%":
#
# %% -- literal "%" character
# %{...} -- substitute data value with recursive evaluation
# %[...] -- evaluate Tcl code and substitute result literally
#
# All other uses of "%" in specs strings are reserved. Data item names
# containing colon (":") are generally reserved for future expansion; a few
# are currently used as shorthand for certain DejaGnu API calls.
# Convention for hierarchical name parts is separation using ".", while "/"
# is used for variations intended to be selected using another value.
# Specs are stored in a Tcl array, referred to as the "database" array.
# Spec strings are organized into layers, providing a hierarchical
# structure of fallback and default values by searching layers in the order
# given by the "_layers" option.
# The external data structures used by this module are mostly association
# lists, but they are internally referenced using Tcl arrays.
# All procedures in this module are currently internal to DejaGnu and
# subject to change without notice.
namespace eval ::dejagnu::specs {
namespace export eval_specs validate_specs
}
# Expand one data substitution token.
# internal procedure; uses SPECS and OPTION arrays in caller's context
proc ::dejagnu::specs::subst_token { key } {
upvar 1 specs specs option option
# check for an option first
if { [info exists option($key)] } {
return $option($key)
}
# check for a board configuration value
if { [regexp {^board_info\(([^)]+)\):(.*)$} $key -> machine info_key] } {
return [board_info $machine $info_key]
}
# search the specs database if a layer path was given
if { [info exists option(_layers)] } {
foreach layer $option(_layers) {
if { [info exists specs(layer,$layer,$key)] } {
return $specs(layer,$layer,$key)
}
}
}
# check for suitable default entry in the specs database
if { [info exists specs(base,$key)] } {
return $specs(base,$key)
}
error "unresolved specs token: $key"
}
# Evaluate excess open or close delimiters.
proc ::dejagnu::specs::delimiter_balance { text } {
# first, remove all backslashes that cannot quote delimiters
regsub -all {\\+[^][\\{}]} $text "" text
# strip backslash-quoted backslashes
regsub -all {(?:\\{2})+} $text "" text
# strip backslash-quoted delimiters
regsub -all {(^|[^\\])\\[][{}]} $text "\\1" text
# remove all unrelated characters
regsub -all {[^][{}]+} $text "" text
# separate the text into only-left and only-right subsets
regsub -all "\\\\*\[\]\}\]" $text "" left
regsub -all "\\\\*\[\[\{\]" $text "" right
return [expr { [string length $left] - [string length $right] }]
}
# Find the end of a token.
proc ::dejagnu::specs::token_end { text start end_pat } {
set balance 1
set point $start
while { $balance > 0 } {
regexp -indices -start [expr { 1 + $point }] -- $end_pat $text item
set point [lindex $item 0]
# optimization: if delimiter_balance returns N, we need at least N
# more closing delimiters, but that could be any combination of
# braces and brackets, not only the main endpoint delimiter
for {
set balance [delimiter_balance [string range $text $start $point]]
} { $balance > 1 } { incr balance -1 } {
regexp -indices -start [expr { 1 + $point }] -- \
"\[\\\}\\\]\]" $text item
set point [lindex $item 0]
}
}
return [lindex $item 1]
}
# Abstract parsing loop.
# internal procedure; sets TOKEN variable in caller's context
proc ::dejagnu::specs::scan_specs_string { text literal char data code } {
upvar 1 token token
for {
set mark -1
set point 0
} { [regexp -indices -start $point -- {%.} $text item] } {
set point [expr { 1 + $mark }]
} {
# extract literal from preceding range
set token [string range $text \
[expr { $mark + 1 }] \
[expr { [lindex $item 0] - 1 }]]
uplevel 1 $literal
# advance point
set point [lindex $item 1]
# extract first character of substitution
set enter [string index $text $point]
if { $enter eq "%" } {
# %% -- literal "%"
set mark $point
uplevel 1 $char
} elseif { $enter eq "\{" } {
# %{...} -- substitute data item
set mark [token_end $text $point "\\\}"]
set token [string range $text \
[expr { $point + 1 }] [expr { $mark - 1 }]]
uplevel 1 $data
} elseif { $enter eq "\[" } {
# %[...] -- substitute value from Tcl code fragment
set mark [token_end $text $point "\\\]"]
set token [string range $text \
[expr { $point + 1 }] [expr { $mark - 1 }]]
uplevel 1 $code
} else {
error "unrecognized sequence %$enter in spec string"
}
}
# leave the trailing literal in TOKEN
set token [string range $text [expr { $mark + 1 }] end]
}
# Generate parse report for specs string; for debugging
proc ::dejagnu::specs::parse_specs_string { text } {
set tokens [list]
scan_specs_string $text {
# intervening literal text
lappend tokens [list text $token]
} { # %% escape
lappend tokens [list text %]
} { # data item
lappend tokens [list data $token]
} { # code item
lappend tokens [list code $token]
}
lappend tokens [list text $token]
return $tokens
}
# Expand substitutions in specs string.
# internal procedure; uses SPECS and OPTION arrays and BASE_LEVEL variable
# in caller's context
proc ::dejagnu::specs::eval_specs_string { text } {
upvar 1 specs specs option option base_level base_level
set output ""
scan_specs_string $text {
# copy intervening literal text to output
append output $token
} {
# emit "%" where string contains "%%"
append output "%"
} {
# substitute data item
append output [eval_specs_string \
[subst_token [eval_specs_string $token]]]
} {
# evaluate Tcl code fragment
append output [uplevel "#$base_level" [eval_specs_string $token]]
}
# copy trailing literal
append output $token
return $output
}
# Check that the provided specs string can be evaluated; that is, that all
# substitutions have definitions.
# internal procedure; uses SPECS and OPTION arrays in caller's context
proc ::dejagnu::specs::validate_specs_string { text } {
upvar 1 specs specs option option
scan_specs_string $text {
# ignore literal text
} {
# ignore literal "%"
} {
# check substitution
} {
# check Tcl code fragment
}
# ignore trailing literal
# an error is thrown if validation fails
return 1
}
# Perform spec substitutions to evaluate %{GOAL}.
#
# The DATABASE_NAME is the name (in the caller's context) of the database
# array to use, while OPTIONS is a list of additional KEY VALUE pairs that
# should be available for substitution.
proc ::dejagnu::specs::eval_specs { database_name goal options } {
upvar 1 $database_name specs
array set option $options
set base_level [expr { [info level] - 1 }]
return [eval_specs_string "%{$goal}"]
}
# Load specs strings into DATABASE_NAME; as:
# load_specs DATABASE_NAME BASE_STRINGS (LAYER_NAME LAYER_STRINGS)...
# to load only into a layer:
# load_specs DATABASE_NAME {} LAYER_NAME LAYER_STRINGS
proc ::dejagnu::specs::load_specs { database_name base_strings args } {
upvar 1 $database_name specs
if { ([llength $args] & 1) != 0 } {
error "specs layer names and contents must be in pairs"
}
foreach {k v} $base_strings {
set specs(base,$k) $v
}
foreach {layer layer_strings} $args {
foreach {k v} $layer_strings {
set specs(layer,$layer,$k) $v
}
}
}
# Display contents of specs database array; for debugging
proc ::dejagnu::specs::dump_specs { database_name } {
upvar 1 $database_name specs
set keys [lsort -dictionary [array names specs]]
# all defaults (base,*) sort ahead of all layers (layer,*,*)
puts "Specs $database_name:\n"
for { set i 0 } { ($i < [llength $keys])
&& [regexp {^base,(.*)$} [lindex $keys $i] \
-> name] } \
{ incr i } {
puts "*$name:\n$specs([lindex $keys $i])\n"
}
for { set prev "" } { ($i < [llength $keys])
&& [regexp {^layer,([^,]+),(.*)$} [lindex $keys $i] \
-> layer name] } \
{ incr i } {
if { $prev ne $layer } {
puts "\[$layer\]"
set prev $layer
}
puts "*$name:\n$specs([lindex $keys $i])\n"
}
}
# Validate a specs database
proc ::dejagnu::specs::validate_specs { database_name } {
upvar 1 $database_name specs
# TODO
}