Tcl Source Code

Check-in [f41248f057]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:TIP 712 - Add "positive" options to the subst command
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk | main
Files: files | file ages | folders
SHA3-256: f41248f05784051abfdeb81f88cfc3d92007a830a5f6643bf0c143ed58761cb6
User & Date: max 2025-06-17 12:27:48.701
Context
2025-06-17
13:35
Update macOS info in README.md and macosx/README check-in: 4361a247f8 user: culler tags: trunk, main
12:27
TIP 712 - Add "positive" options to the subst command check-in: f41248f057 user: max tags: trunk, main
2025-06-15
03:03
Merge 9.0 - tests for handle leaks for exec check-in: 166bfb6375 user: apnadkarni tags: trunk, main
2025-05-21
10:09
Rebase to latest trunk Closed-Leaf check-in: d265be08af user: jan.nijtmans tags: tip-712
Changes
Unified Diff Ignore Whitespace Patch
Changes to doc/subst.n.
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
.TH subst n 7.4 Tcl "Tcl Built-In Commands"
.so man.macros
.BS
'\" Note:  do not modify the .SH NAME line immediately below!
.SH NAME
subst \- Perform backslash, command, and variable substitutions
.SH SYNOPSIS

\fBsubst \fR?\fB\-nobackslashes\fR? ?\fB\-nocommands\fR? ?\fB\-novariables\fR? \fIstring\fR


.BE
.SH DESCRIPTION
.PP
This command performs variable substitutions, command substitutions,
and backslash substitutions on its \fIstring\fR argument and
returns the fully-substituted result.
The substitutions are performed in exactly the same way as for
Tcl commands.
As a result, the \fIstring\fR argument is actually substituted twice,
once by the Tcl parser in the usual fashion for Tcl commands, and
again by the \fIsubst\fR command.
.PP
If any of the \fB\-nobackslashes\fR, \fB\-nocommands\fR, or
\fB\-novariables\fR are specified, then the corresponding substitutions
are not performed.
For example, if \fB\-nocommands\fR is specified, command substitution
is not performed:  open and close brackets are treated as ordinary characters
with no special interpretation.
.PP












Note that the substitution of one kind can include substitution of
other kinds.  For example, even when the \fB\-novariables\fR option
is specified, command substitution is performed without restriction.
This means that any variable substitution necessary to complete the
command substitution will still take place.  Likewise, any command
substitution necessary to complete a variable substitution will
take place, even when \fB\-nocommands\fR is specified.  See the







>

>
>



















>
>
>
>
>
>
>
>
>
>
>
>







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
.TH subst n 7.4 Tcl "Tcl Built-In Commands"
.so man.macros
.BS
'\" Note:  do not modify the .SH NAME line immediately below!
.SH NAME
subst \- Perform backslash, command, and variable substitutions
.SH SYNOPSIS
.nf
\fBsubst \fR?\fB\-nobackslashes\fR? ?\fB\-nocommands\fR? ?\fB\-novariables\fR? \fIstring\fR
\fBsubst \fR?\fB\-backslashes\fR? ?\fB\-commands\fR? ?\fB\-variables\fR? \fIstring\fR
.fi
.BE
.SH DESCRIPTION
.PP
This command performs variable substitutions, command substitutions,
and backslash substitutions on its \fIstring\fR argument and
returns the fully-substituted result.
The substitutions are performed in exactly the same way as for
Tcl commands.
As a result, the \fIstring\fR argument is actually substituted twice,
once by the Tcl parser in the usual fashion for Tcl commands, and
again by the \fIsubst\fR command.
.PP
If any of the \fB\-nobackslashes\fR, \fB\-nocommands\fR, or
\fB\-novariables\fR are specified, then the corresponding substitutions
are not performed.
For example, if \fB\-nocommands\fR is specified, command substitution
is not performed:  open and close brackets are treated as ordinary characters
with no special interpretation.
.PP
If any of the \fB\-backslashes\fR, \fB\-commands\fR, or
\fB\-variables\fR are specified, then only the corresponding
substitutions are performed. This means that the following lines are
equivalent:
.PP
.CS
\fBsubst\fR -nobackslashes -nocommands $string
\fBsubst\fR -variables $string
.CE
.PP
It is not allowed to combine positive and negated options.
.PP
Note that the substitution of one kind can include substitution of
other kinds.  For example, even when the \fB\-novariables\fR option
is specified, command substitution is performed without restriction.
This means that any variable substitution necessary to complete the
command substitution will still take place.  Likewise, any command
substitution necessary to complete a variable substitution will
take place, even when \fB\-nocommands\fR is specified.  See the
Changes to generic/tclCmdMZ.c.
3341
3342
3343
3344
3345
3346
3347

3348
3349
3350
3351





3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362











3363
3364
3365
3366
3367
3368
3369
TclSubstOptions(
    Tcl_Interp *interp,
    Tcl_Size numOpts,
    Tcl_Obj *const opts[],
    int *flagPtr)
{
    static const char *const substOptions[] = {

	"-nobackslashes", "-nocommands", "-novariables", NULL
    };
    static const int optionFlags[] = {
	TCL_SUBST_BACKSLASHES, TCL_SUBST_COMMANDS, TCL_SUBST_VARIABLES





    };
    int flags = TCL_SUBST_ALL;

    for (Tcl_Size i = 0; i < numOpts; i++) {
	int optionIndex;

	if (Tcl_GetIndexFromObj(interp, opts[i], substOptions, "option", 0,
		&optionIndex) != TCL_OK) {
	    return TCL_ERROR;
	}
	flags &= ~optionFlags[optionIndex];











    }
    *flagPtr = flags;
    return TCL_OK;
}

int
Tcl_SubstObjCmd(







>



|
>
>
>
>
>

|








|
>
>
>
>
>
>
>
>
>
>
>







3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
TclSubstOptions(
    Tcl_Interp *interp,
    Tcl_Size numOpts,
    Tcl_Obj *const opts[],
    int *flagPtr)
{
    static const char *const substOptions[] = {
	"-backslashes", "-commands", "-variables",
	"-nobackslashes", "-nocommands", "-novariables", NULL
    };
    static const int optionFlags[] = {
	TCL_SUBST_BACKSLASHES,		/* -backslashes */
	TCL_SUBST_COMMANDS,		/* -commands */
	TCL_SUBST_VARIABLES,		/* -variables */
	TCL_SUBST_BACKSLASHES << 16,	/* -nobackslashes */
	TCL_SUBST_COMMANDS    << 16,	/* -nocommands */
	TCL_SUBST_VARIABLES   << 16	/* -novariables */
    };
    int flags = numOpts ? 0 : TCL_SUBST_ALL;

    for (Tcl_Size i = 0; i < numOpts; i++) {
	int optionIndex;

	if (Tcl_GetIndexFromObj(interp, opts[i], substOptions, "option", 0,
		&optionIndex) != TCL_OK) {
	    return TCL_ERROR;
	}
	flags |= optionFlags[optionIndex];
    }
    if (flags >> 16) {			/* negative options specified */
	if (flags & 0xFFFF) {		/* positive options specified too */
	    if (interp) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "cannot combine positive and negative options", -1));
	    }
	    return TCL_ERROR;
	}
	/* mask default flags using negative options */
	flags = TCL_SUBST_ALL & ~(flags >> 16);
    }
    *flagPtr = flags;
    return TCL_OK;
}

int
Tcl_SubstObjCmd(
3382
3383
3384
3385
3386
3387
3388

3389
3390
3391
3392
3393
3394
3395
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    int flags;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv,

		"?-nobackslashes? ?-nocommands? ?-novariables? string");
	return TCL_ERROR;
    }

    if (TclSubstOptions(interp, objc-2, objv+1, &flags) != TCL_OK) {
	return TCL_ERROR;
    }







>







3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    int flags;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv,
		"?-backslashes? ?-commands? ?-variables? "
		"?-nobackslashes? ?-nocommands? ?-novariables? string");
	return TCL_ERROR;
    }

    if (TclSubstOptions(interp, objc-2, objv+1, &flags) != TCL_OK) {
	return TCL_ERROR;
    }
Changes to tests/subst.test.
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
::tcltest::loadTestedCommands
catch [list package require -exact tcl::test [info patchlevel]]

testConstraint testbytestring [llength [info commands testbytestring]]

test subst-1.1 {basics} -returnCodes error -body {
    subst
} -result {wrong # args: should be "subst ?-nobackslashes? ?-nocommands? ?-novariables? string"}
test subst-1.2 {basics} -returnCodes error -body {
    subst a b c
} -result {bad option "a": must be -nobackslashes, -nocommands, or -novariables}

test subst-2.1 {simple strings} {
    subst {}
} {}
test subst-2.2 {simple strings} {
    subst a
} a







|


|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
::tcltest::loadTestedCommands
catch [list package require -exact tcl::test [info patchlevel]]

testConstraint testbytestring [llength [info commands testbytestring]]

test subst-1.1 {basics} -returnCodes error -body {
    subst
} -result {wrong # args: should be "subst ?-backslashes? ?-commands? ?-variables? ?-nobackslashes? ?-nocommands? ?-novariables? string"}
test subst-1.2 {basics} -returnCodes error -body {
    subst a b c
} -result {bad option "a": must be -backslashes, -commands, -variables, -nobackslashes, -nocommands, or -novariables}

test subst-2.1 {simple strings} {
    subst {}
} {}
test subst-2.2 {simple strings} {
    subst a
} a
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
test subst-6.1 {clear the result after command substitution} -body {
    catch {unset a}
    subst {[concat foo] $a}
} -returnCodes error -result {can't read "a": no such variable}

test subst-7.1 {switches} -returnCodes error -body {
    subst foo bar
} -result {bad option "foo": must be -nobackslashes, -nocommands, or -novariables}
test subst-7.2 {switches} -returnCodes error -body {
    subst -no bar
} -result {ambiguous option "-no": must be -nobackslashes, -nocommands, or -novariables}
test subst-7.3 {switches} -returnCodes error -body {
    subst -bogus bar
} -result {bad option "-bogus": must be -nobackslashes, -nocommands, or -novariables}
test subst-7.4 {switches} {
    set x 123
    subst -nobackslashes {abc $x [expr {1 + 2}] \\\x41}
} {abc 123 3 \\\x41}
test subst-7.5 {switches} {
    set x 123
    subst -nocommands {abc $x [expr {1 + 2}] \\\x41}
} {abc 123 [expr {1 + 2}] \A}
test subst-7.6 {switches} {
    set x 123
    subst -novariables {abc $x [expr {1 + 2}] \\\x41}
} {abc $x 3 \A}
test subst-7.7 {switches} {
    set x 123
    subst -nov -nob -noc {abc $x [expr {1 + 2}] \\\x41}
} {abc $x [expr {1 + 2}] \\\x41}
































test subst-8.1 {return in a subst} {
    subst {foo [return {x}; bogus code] bar}
} {foo x bar}
test subst-8.2 {return in a subst} {
    subst {foo [return x ; bogus code] bar}
} {foo x bar}
test subst-8.3 {return in a subst} {







|


|


|
















|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
test subst-6.1 {clear the result after command substitution} -body {
    catch {unset a}
    subst {[concat foo] $a}
} -returnCodes error -result {can't read "a": no such variable}

test subst-7.1 {switches} -returnCodes error -body {
    subst foo bar
} -result {bad option "foo": must be -backslashes, -commands, -variables, -nobackslashes, -nocommands, or -novariables}
test subst-7.2 {switches} -returnCodes error -body {
    subst -no bar
} -result {ambiguous option "-no": must be -backslashes, -commands, -variables, -nobackslashes, -nocommands, or -novariables}
test subst-7.3 {switches} -returnCodes error -body {
    subst -bogus bar
} -result {bad option "-bogus": must be -backslashes, -commands, -variables, -nobackslashes, -nocommands, or -novariables}
test subst-7.4 {switches} {
    set x 123
    subst -nobackslashes {abc $x [expr {1 + 2}] \\\x41}
} {abc 123 3 \\\x41}
test subst-7.5 {switches} {
    set x 123
    subst -nocommands {abc $x [expr {1 + 2}] \\\x41}
} {abc 123 [expr {1 + 2}] \A}
test subst-7.6 {switches} {
    set x 123
    subst -novariables {abc $x [expr {1 + 2}] \\\x41}
} {abc $x 3 \A}
test subst-7.7 {switches} {
    set x 123
    subst -nov -nob -noc {abc $x [expr {1 + 2}] \\\x41}
} {abc $x [expr {1 + 2}] \\\x41}
test subst-7.8 {positive switches} {
    set x 123
    subst -backslashes {abc $x [expr {1 + 2}] \\\x41}
} {abc $x [expr {1 + 2}] \A}
test subst-7.9 {positive switches} {
    set x 123
    subst -commands {abc $x [expr {1 + 2}] \\\x41}
} {abc $x 3 \\\x41}
test subst-7.10 {positive switches} {
    set x 123
    subst -variables {abc $x [expr {1 + 2}] \\\x41}
} {abc 123 [expr {1 + 2}] \\\x41}
test subst-7.4.11 {positive switches} {
    set x 123
    subst -commands -variables {abc $x [expr {1 + 2}] \\\x41}
} {abc 123 3 \\\x41}
test subst-7.12 {positive switches} {
    set x 123
    subst -backslashes -variables {abc $x [expr {1 + 2}] \\\x41}
} {abc 123 [expr {1 + 2}] \A}
test subst-7.13 {positive switches} {
    set x 123
    subst -backslashes -commands {abc $x [expr {1 + 2}] \\\x41}
} {abc $x 3 \A}
test subst-7.14 {positive switches} {
    set x 123
    subst -ba -co -va {abc $x [expr {1 + 2}] \\\x41}
} {abc 123 3 \A}
test subst-7.15 {mixed switches} -returnCodes error -body {
    set x 123
    subst -backslashes -novariables {abc $x [expr {1 + 2}] \\\x41}
} -result {cannot combine positive and negative options}
test subst-8.1 {return in a subst} {
    subst {foo [return {x}; bogus code] bar}
} {foo x bar}
test subst-8.2 {return in a subst} {
    subst {foo [return x ; bogus code] bar}
} {foo x bar}
test subst-8.3 {return in a subst} {