Embedded Tcl: как определить оценку экспрессии? - PullRequest
0 голосов
/ 05 ноября 2018

У нас есть Tcl , встроенное в наше приложение C / C ++, в нашем коде есть место, где мы должны проверить, является ли команда вложенной , что означает, что результат команды установлен позже как: " :: Tcl_SetResult (...) ". Если нет, он печатается на консоль или перенаправляется в файл, используя « printMessage (...) ».

К сожалению, что-то вроде :: Tcl_GetCommandInfo , не дает много информации. (Я должен признать, что я невежествен в Tcl).

Пример: (Как выглядит функция, Tcl вызывает ее, затем мы обрабатываем данные и возвращаемся к Tcl, Tcl решает, является ли это нашей командой или нет, и продолжает):

void traceProc (ClientData clientData, Tcl_Interp pInterp, int nLevel, char * pszCommand, Tcl_CmdProc * pCmdProc, ClientData cmdCliendData, int argc, char * argv []) *

Проблема видна при выполнении our_command :

our_command; # -> nLevel == 1
if {[our_command] eq "sth"} {do_sth}; # -> also nLevel == 1

Сейчас я ожидаю, что nLevel будет равен 2, потому что он находится внутри оператора if, или первый из них будет 0, или какая-то дополнительная информация о текущей выполняемой команде. Я делаю что-то неправильно? Дело в том, что я не знаю, что делать позже, потому что «не должен» печатать результат команды, если он внутри [] скобок и т. Д.

Ответы [ 2 ]

0 голосов
/ 05 ноября 2018

Предварительный ответ (на актуальный вопрос)

Как указывалось ранее, нет способа надежно обнаружить вложенную оценку заданной команды (proc) путем интроспекции команд или фреймов вызова. Кроме того, для этого требуется доступ к внутренним компонентам Tcl (частным заголовкам и т. Д.), И он доступен только для Tcl 8.6+ (если это важно для вас). Случай if -едных и не if вызовов на вашу команду (myCommand) может быть обнаружен, по крайней мере, из того, что вы нам сообщили, с помощью команды sth:

CmdFrame *framePtr;
Interp *iPtr = ((Interp *)interp);
Tcl_Obj* resObj = Tcl_NewIntObj(1);

framePtr = iPtr->cmdFramePtr;
Tcl_ResetResult(interp);

 if (iPtr->cmdFramePtr->nextPtr &&
     iPtr->cmdFramePtr->nextPtr->framePtr ==
     iPtr->cmdFramePtr->framePtr &&
     iPtr->cmdFramePtr->framePtr == iPtr->varFramePtr) {
     Tcl_SetObjResult(interp, resObj);
 } else {
     fprintf(stderr, "The result is %s\n", Tcl_GetString(resObj));
 }
 return TCL_OK;

Затем скрипт выполняется следующим образом:

myCommand; # w/ print-out
set y [myCommand]; # w/ print-out
if {[myCommand]} { puts "then, here!"} else {puts "notok"}; # w/o print-out

Однако вы заметите, что нельзя различить следующие два if -иннеровских использования:

if {[myCommand]} { puts "then, here! [myCommand]" } else {puts "notok"}

Второй также не будет распечатан. Только (искусственно созданный) дополнительный кадр в стеке команд сделает это:

if {[myCommand]} { puts "then, here! [apply {{} {myCommand}}]"} else {puts "notok"};

Итак, этот подход не обобщает, как показано на примере.

Предложение по улучшению

"Я не должен печатать результат команды, если он внутри [] скобок и т. Д."

Я хотел бы вернуться к одному из предложений Донала и продемонстрировать, как можно легко распутать различные контексты использования вашей команды (myCommand), предоставив две отдельные команды, по одной для каждого контекста, сохраняя при этом внешний вид ' не чувствую наличия одного. Вы можете предоставить основное, которое вычисляет предполагаемое возвращаемое значение, и предоставить вспомогательную оболочку, которая «перенаправляет» возвращаемое значение в стандартный вывод (или любой другой).

(1) Основная команда: превратить существующую команду, реализованную в C, в команду, которая не печатает значение результата, но возвращает результат, используя только Tcl_SetObjResult. Поместите эту команду (или, под псевдонимом) в пространство имен ::tcl::mathfunc::*. Используя Critcl , это может выглядеть так:

critcl::ccommand ::tcl::mathfunc::myCommand {cd interp objc objv} {
    Tcl_Obj* resObj = Tcl_NewIntObj(1);
    Tcl_SetObjResult(interp, resObj);
    return TCL_OK;
}

(2) Создайте оболочку со сценарием в пространстве имен верхнего уровня (::) или в конкретном проекте, которое имеет такое же (неквалифицированное) имя myCommand:

proc ::myCommand {} {
    puts stdout [uplevel 1 ::tcl::mathfunc::myCommand]
    return
}

Оболочка вызывает основную команду и puts результат, где хочет. return сбросит результат интерпретатора. В качестве альтернативы, вы также можете вернуть его, поэтому оболочка становится эквивалентной контракту с основной командой.

Теперь вы можете использовать myCommand по-другому, в [expr] условиях, таких как [if] и не expr:

myCommand; # main w/ print-out
if {myCommand()} { puts "then, here" }; # wrapper w/o print-out

set x [myCommand]; # main w/ print-out, x is set to ""
set y [expr {myCommand()}]; # wrapper w/o print-out, y is set to result

Все это зависит от пространства имен специального назначения ::tcl::mathfunc и от того, как [expr] обрабатываются в нем команды / процедуры. Преимущества для меня:

  • Позволяет выполнить простой рефакторинг (ваша основная команда упрощается), а оболочка написана исключительно по сценарию.
  • Четкое разделение контекстов использования, даже синтаксически.
  • Нет необходимости анализировать контекст CmdFrame / CallFrame вашей команды, который в любом случае будет ограничен и потребует доступа к внутренним компонентам Tcl.
  • Никакой штраф за самоанализ не возникает при каждом вызове вашей команды.

Оригинальные предложения

Ты после чего? по сценариям:

proc myCommand {} {
    for {set i 1} {$i<=[info frame]} {incr i} {
        set frameInfo [info frame $i]
        set frameType [dict get $frameInfo type]
        set cmd [dict get $frameInfo cmd]
        if {$frameType eq "source" && [lindex $cmd 0] eq "if"} {
           puts stderr {Called from (anywhere) within [if]}
           break;
        }
    }
    return 1
}

myCommand;
# some ancestor stackframe might reveal some [if] context
if {[myCommand]} { puts "then, here!"};

Вы можете достичь чего-л. аналогично на стороне C, используя TclGetFrame, для получения текущего стекового кадра и затем поднимаясь вниз по стеку. Однако, как ясно говорит Донал, это имеет очень сильный запах и может вызвать много ложных срабатываний (например, [myCommand] в любом месте структуры if, if не обязательно означает так называемую структуру потока управления, в случае, если кто-то решил добавить к этому имени команды и т. д.)

Лучше, во-первых, пересмотреть, почему вы чувствуете необходимость в этом.

Дело в том, что я не знаю, что делать позже, потому что не могу напечатать команду результат, если он внутри [] скобок и т. д.

Например, мне остается неясным, в чем разница для клиентов вашей команды, вызывается ли она в if -контексте или нет. Это не должно иметь значения, как пишет Донал.

Если вы хотите распечатать возвращаемое значение myCommand в условии if, не изменяя поведения, напишите sth. как в вашем скрипте (вместо манипулирования самой командой):

if {[set tmp [myCommand]; puts $tmp; set tmp]} { puts "then, here!"};

Обновление

Если вы не хотите манипулировать сценарием (на сайте вызовов myCommand), тогда используйте трассировку выполнения , чтобы наблюдать за вашими myCommand выполнениями:

proc logMyCommand {call code result op} {
    # puts ...
}

trace add execution myCommand leave logMyCommand
0 голосов
/ 05 ноября 2018

Функция Tcl_SetResult используется для установки в поле результата интерпретатора заданного строкового значения. (Поле является полностью внутренним во всех последних версиях Tcl и фактически является множеством реальных полей для управления памятью и т. Д. Это детали, которые вы можете игнорировать.) Если команда не устанавливает результат, ее результат с точки зрения Tcl будет пустой строкой. Вам не нужно устанавливать результат по любой другой причине, хотя интерпретация поля результата будет отличаться: если команда выдает ошибку (из-за функции реализации, возвращающей TCL_ERROR вместо TCL_OK), тогда поле результата содержит ошибка сообщение . Когда вызывается команда, значение поля результата (обсервационно эквивалентно) пустой строке.

Нет общего способа узнать из API, что производит команда: она просто выдает значение (которое является логически строкой, поскольку это логический супертип всех других типов значений, понятных Tcl). Документация команды, где она существует, может быть более конкретной. Все встроенные команды Tcl должны иметь четкое представление о том, что они производят, и должны предсказуемо выдавать сообщения об ошибках, если они не документированы для получения успешного результата. Команды, не предоставляемые самим Tcl, не так ограничены; мы не можем заставить вас правильно использовать API!


Считается крайне плохим стилем для изменения поведения команды в зависимости от ее вызывающего контекста, кроме грубых способов (например, потому что вы находитесь в другом пространстве имен или в процедуре). В частности, вы не можете определить, происходит ли вызов внутри if, либо внутри части выражения, либо внутри одного из сценариев тела. Если вам необходимо это различить, либо сообщите команду с помощью явного аргумента, либо разделите команду на две части с разными именами. Дополнительным преимуществом этого является то, что он значительно облегчает тестирование вашего кода.

...