Я не могу понять, как работает уровень - PullRequest
0 голосов
/ 12 марта 2020

Почему я получаю ошибку?

#!/usr/bin/tclsh
proc add {a} {
  uplevel 1 $a
   puts $a
}
set n 0
add $n

Я не могу понять, как работает уровень

Ответы [ 2 ]

0 голосов
/ 13 марта 2020

Если мы запустим ваш код, мы получим сообщение об ошибке: invalid command name "0".

Давайте исследуем, напечатав трассировку стека:

% puts $errorInfo
invalid command name "0"
    while executing
"0"
    ("uplevel" body line 1)
    invoked from within
"uplevel 1 $a"
    (procedure "add" line 2)
    invoked from within
"add $n"

Это выглядит любопытно, но хорошо потому что значение $a равно 0, поскольку аргумент add является значением $n, равным 0. Аргументы uplevel (после необязательного первого псевдо-числового аргумента c, который предоставляет ; хорошо!) Представляют собой сценарий (с объединением нескольких аргументов). Строка 0 может быть сценарием, если в области видимости есть команда с именем di git 0, но это довольно необычно.

Один раз у него есть скрипт для запуска, uplevel 1 запускает скрипт в контексте вызывающей стороны текущей процедуры. (Технически, он увеличивает 1 стековый фрейм и запускает его там. Фреймы Tcl-стека фактически формируют дерево, хотя обычно в дереве есть только одна ветвь за раз; uplevel образует другую ветвь.)

Две распространенные формы uplevel: uplevel 1 (для «запуска в вызывающей программе») и uplevel #0 (для «запуска в глобальной области»). Есть и другие, но они необычные. Не используйте uplevel 0; это похоже на eval и путаница царит, если вы так неясны.

Хорошей практикой обычно является сохранение ваших uplevel форм сценариев в одном из:

  1. Переданный аргумент в звонящего. (Это ваш сценарий.)
  2. Постоянный сценарий.
  3. Одиночный командный вызов , генерируемый путем вызова list. Вы можете использовать {*}expansion при создании списка; Это хороший способ обработки списка значений или префикса команды.

Все они, как было установлено, обычно не сбивают с толку и довольно легко использовать правильно (без каких-либо странных опасностей). Другие варианты, как правило, хитрые.


Я предполагаю, что ваш код должен иметь на самом деле написано так:

proc add {varName} {
    upvar 1 $varName v
    set v [expr {$v + 1}]
    # Or maybe: incr v
    puts $v
}
set n 0
add n

Команда upvar использует те же правила разрешения уровня, что и uplevel, но вместо этого псевдоним именованная переменная в этой области для другого локального имени в текущей области. Вызов upvar #0 foo foo функционально эквивалентен, по-видимому, более простому global foo.

0 голосов
/ 12 марта 2020

uplevel выполняет некоторый код в другом «фрейме стека» - каждый раз, когда вы вызываете процедуру (и некоторые другие способы), Tcl добавляет фрейм выполнения в стек вызовов.

Вот пример :

proc foo {} {
    set fooVar 42
    bar {expr {$fooVar + 21}}
}

proc bar {code} {
    puts "in bar, code is: [list $code]"
    puts "in bar, the fooVar variable [expr {[info exists fooVar] ? "does" : "does not"}] exist"
    uplevel 1 $code
}

Выполнение процедуры foo:

% foo
in bar, code is: {expr {$fooVar + 21}}
in bar, the fooVar variable does not exist
63

Это демонстрирует, что, поскольку мы выполняем код в одном кадре в стеке вызовов, фрагмент кода имеет ли доступ к локальной переменной в этом кадре.

Если мы попытаемся запустить bar с тем же блоком кода из глобальной области видимости, мы увидим ошибку:

% bar {expr {$fooVar + 21}}
in bar, code is: {expr {$fooVar + 21}}
in bar, the fooVar variable does not exist
can't read "fooVar": no such variable

Но если мы установим переменную в глобальной области видимости, она будет работать, как и ожидалось:

% set fooVar -1
-1
% bar {expr {$fooVar + 21}}
in bar, code is: {expr {$fooVar + 21}}
in bar, the fooVar variable does not exist
20

Вместе с upvar , uplevel Команда позволяет вам реализовать свои собственные управляющие структуры - Tcl действительно невероятно гибкий язык. Пример:

proc foreachWithIndex {variableNames aList code} {
    lassign $variableNames idxVar elemVar
    upvar 1 $idxVar idx
    upvar 1 $elemVar elem

    set idx 0
    foreach elem $aList {
        uplevel 1 $code
        incr idx
    }
}

foreachWithIndex {i e} {a b c d} {puts "$i -> $e"}
0 -> a
1 -> b
2 -> c
3 -> d
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...