Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for a new 'leval' command #304

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 133 additions & 2 deletions jim.c
Original file line number Diff line number Diff line change
Expand Up @@ -11045,6 +11045,7 @@ static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * tok
Jim_Obj *sintv[JIM_EVAL_SINTV_LEN];
Jim_Obj *objPtr;
char *s;
const char *error_action = NULL;

if (tokens <= JIM_EVAL_SINTV_LEN)
intv = sintv;
Expand All @@ -11064,14 +11065,16 @@ static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * tok
tokens = i;
continue;
}
/* XXX: Should probably set an error about break outside loop */
error_action = "break";
/* fall through to error */
case JIM_CONTINUE:
if (flags & JIM_SUBST_FLAG) {
intv[i] = NULL;
continue;
}
/* XXX: Ditto continue outside loop */
if (!error_action) {
error_action = "continue";
}
/* fall through to error */
default:
while (i--) {
Expand All @@ -11080,6 +11083,9 @@ static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * tok
if (intv != sintv) {
Jim_Free(intv);
}
if (error_action) {
Jim_SetResultFormatted(interp, "invoked \"%s\" outside of a loop", error_action);
}
return NULL;
}
Jim_IncrRefCount(intv[i]);
Expand Down Expand Up @@ -11130,6 +11136,117 @@ static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * tok
return objPtr;
}

#define JIM_LSUBST_LINE 0x0001

/* Parse a string as an 'lsubst' argument and sets the interp result.
* Return JIM_OK if ok, or JIM_ERR on error.
*
* Modelled on Jim_EvalObj()
*
* If flags contains JIM_LSUBST_LINE, each "statement" is returned as list of {command arg...}
*/
static int JimListSubstObj(Jim_Interp *interp, struct Jim_Obj *objPtr, unsigned flags)
{
int i;
ScriptObj *script;
ScriptToken *token;
Jim_Obj *resultListObj;
int retcode = JIM_OK;

Jim_IncrRefCount(objPtr); /* Make sure it's shared. */
script = JimGetScript(interp, objPtr);
if (JimParseCheckMissing(interp, script->missing) == JIM_ERR) {
JimSetErrorStack(interp, script);
Jim_DecrRefCount(interp, objPtr);
return JIM_ERR;
}

token = script->token;

script->inUse++;

/* Build the result list here */
resultListObj = Jim_NewListObj(interp, NULL, 0);

/* Add every command, arg to the result list */
for (i = 0; i < script->len && retcode == JIM_OK; ) {
int argc;
int j;
Jim_Obj *lineListObj = resultListObj;

/* First token of the line is always JIM_TT_LINE */
argc = token[i].objPtr->internalRep.scriptLineValue.argc;
script->linenr = token[i].objPtr->internalRep.scriptLineValue.line;

/* Skip the JIM_TT_LINE token */
i++;

if (flags & JIM_LSUBST_LINE) {
lineListObj = Jim_NewListObj(interp, NULL, 0);
}

/* Extract the words from this line */
for (j = 0; j < argc; j++) {
long wordtokens = 1;
int expand = 0;
Jim_Obj *wordObjPtr = NULL;

if (token[i].type == JIM_TT_WORD) {
wordtokens = JimWideValue(token[i++].objPtr);
if (wordtokens < 0) {
expand = 1;
wordtokens = -wordtokens;
}
}

/* Note we don't worry about a fast path here */
wordObjPtr = JimInterpolateTokens(interp, token + i, wordtokens, JIM_NONE);

if (!wordObjPtr) {
if (retcode == JIM_OK) {
retcode = JIM_ERR;
}
break;
}

Jim_IncrRefCount(wordObjPtr);
i += wordtokens;

if (!expand) {
Jim_ListAppendElement(interp, lineListObj, wordObjPtr);
}
else {
int k;
/* Need to add each word of wordObjPtr list to the result list */
for (k = 0; k < Jim_ListLength(interp, wordObjPtr); k++) {
Jim_ListAppendElement(interp, lineListObj, Jim_ListGetIndex(interp, wordObjPtr, k));
}
}
Jim_DecrRefCount(interp, wordObjPtr);
}

if (flags & JIM_LSUBST_LINE) {
Jim_ListAppendElement(interp, resultListObj, lineListObj);
}
}

/* Note that we don't have to decrement inUse, because the
* following code transfers our use of the reference again to
* the script object. */
Jim_FreeIntRep(interp, objPtr);
objPtr->typePtr = &scriptObjType;
Jim_SetIntRepPtr(objPtr, script);
Jim_DecrRefCount(interp, objPtr);

if (retcode == JIM_OK) {
Jim_SetResult(interp, resultListObj);
}
else {
Jim_FreeNewObj(interp, resultListObj);
}

return retcode;
}

/* listPtr *must* be a list.
* The contents of the list is evaluated with the first element as the command and
Expand Down Expand Up @@ -15584,6 +15701,19 @@ static int Jim_SubstCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar
return JIM_OK;
}

/* [lsubst] */
static int Jim_LsubstCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
if (argc == 2) {
return JimListSubstObj(interp, argv[1], 0);
}
if (argc == 3 && Jim_CompareStringImmediate(interp, argv[1], "-line")) {
return JimListSubstObj(interp, argv[2], JIM_LSUBST_LINE);
}
Jim_WrongNumArgs(interp, 1, argv, "?-line? string");
return JIM_ERR;
}

#ifdef jim_ext_namespace
static int JimIsGlobalNamespace(Jim_Obj *objPtr)
{
Expand Down Expand Up @@ -16490,6 +16620,7 @@ static const struct {
{"rename", Jim_RenameCoreCommand},
{"dict", Jim_DictCoreCommand},
{"subst", Jim_SubstCoreCommand},
{"lsubst", Jim_LsubstCoreCommand},
{"info", Jim_InfoCoreCommand},
{"exists", Jim_ExistsCoreCommand},
{"split", Jim_SplitCoreCommand},
Expand Down
59 changes: 59 additions & 0 deletions jim_tcl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Changes since 0.82
7. Add support for hinting with `history hints`
8. Support for `proc` statics by reference (lexical closure) rather than by value
9. `regsub` now supports '-command' (per Tcl 8.7)
10. New `lsubst` command to create lists using subst-style substitution

Changes between 0.81 and 0.82
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -3120,6 +3121,64 @@ than variables, a list of unassigned elements is returned.
a=1,b=2
----

lsubst
~~~~

+*lsubst ?-line?* 'string'+

This command is similar to `list` in that it creates a list, but uses
the same rules as scripts when constructing the elements of the list.
It is somewhat similar to `subst` except it produces a list instead of a string.

This means that variables are substituted, commands are evaluated, backslashes are
interpreted, the expansion operator is applied and comments are skipped.

Consider the following example.

---
set x 1
set y {2 3}
set z 3
lsubst {
# This is a list with interpolation
$x; # The x variable
{*}$y; # The y variable expanded
[string cat a b c]; # A command
{*}[list 4 5]; # A list expanded into multiple elements
"$z$z"; # A string with interpolation
}
---

The result of `lsubst` is the following list with 7 elements.

---
1 2 3 abc 4 5 33
---

This is particularly useful when constructing a list (or dict)
as a data structure as it easily allows for comments and variable and command
substitution.

Sometimes it is useful to return each "command" as a separate list rather than
simply running all the words together. This can be accomplished with `lsubst -line`.

Consider the following example.

---
lsubst -line {
# two "lines" because of the semicolon
one a; two b
# one line with three elements
{*}{a b c}
}
---

The result of `lsubst -line` is the following list with 3 elements, one for each "command".

---
{one a} {two b} {a b c}
---

local
~~~~~
+*local* 'cmd ?arg\...?'+
Expand Down
7 changes: 4 additions & 3 deletions tests/exec2.test
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ test exec2-3.2 "close pipeline return value" -constraints {pipe signal nomingw32
signal ignore SIGPIPE
# Write more than 64KB which is maximum size of the pipe buffers
# on all systems we have seen
set bigstring [string repeat a 100000]
set f [open [list |cat << $bigstring]]
set bigstring [string repeat a 100]
set f [open [list |dd bs=512 << $bigstring]]
puts pid=[$f pid]
set rc [catch {close $f} msg opts]
lassign [dict get $opts -errorcode] status pid exitcode
list $rc $msg $status $exitcode
Expand All @@ -81,7 +82,7 @@ test exec2-3.3 "close pipeline with SIGPIPE blocked" -constraints {pipe signal n
signal block SIGPIPE
# Write more than 64KB which is maximum size of the pipe buffers
# on all systems we have seen
set bigstring [string repeat a 100000]
set bigstring [string repeat a 100]
set f [open [list |cat << $bigstring 2>/dev/null]]
set rc [catch {close $f} msg opts]
lassign [dict get $opts -errorcode] status pid exitcode
Expand Down
Loading
Loading