openlang

Фигурные скобки вокруг expr решают, что видит компилятор байткода

expr без фигурных скобок - единственная привычка в Tcl, которую хочется запретить! Выглядит как придирка к стилю, а на деле нет.

set qty {[puts "side effect"; list 3]}
puts [expr {$qty == 3}]
puts [expr $qty == 3]

Строка со скобками печатает 0. Компилятор байткода видит $qty == 3 как выражение, читает переменную один раз и сравнивает странную строку с 3. Строка без скобок печатает side effect, а потом 1. Tcl подставляет $qty, пока собирает слова команды, expr получает текст [puts "side effect"; list 3] == 3 и делает свой собственный круг подстановок, который и запускает команду в квадратных скобках. Две подстановки, и вторая выполняет всё, что лежало в переменной.

Это весёлая часть. Тихая часть касается скорости, потому что форму без скобок нельзя скомпилировать заранее, и она разбирается при каждом выполнении. Проверено здесь на 9.1, но так было очень давно.

Разговор продолжается 13

Следуя доводам
  1. Ссылка на ответ

    Строку со скобками стоит перечитать, потому что == там тоже кое-что делает. Он сравнивает числа, когда обе стороны похожи на числа, а иначе переходит к сравнению строк, поэтому странная строка даёт просто 0, а не ошибку. Этот переход кусается и сам по себе. expr {"1.0" == "1"} даёт 1, а expr {"1.0" eq "1"} даёт 0. Если задумано сравнение текста, eq так и говорит и никогда не пробует числовое прочтение.

  2. Ссылка на ответ

    А с if и while происходит то же самое? Их условия ведь тоже выражения, а if $done в старых скриптах попадается чаще, чем хотелось бы.

  3. Ссылка на ответ

    Посчитано через tcl::unsupported::disassemble на двух однострочных процедурах. expr {$a + $b} компилируется в четыре инструкции, два loadScalar, один add и done. expr $a + $b компилируется в восемь. Загружается a, пробелы и + кладутся как литералы, загружается b, все пять кусков склеиваются через strcat, и результат уходит в exprStk, который разбирает и выполняет выражение во время выполнения при каждом вызове. Для чисел ответ тот же, а второй вариант на каждом вызове собирает строку и разбирает её.

  4. Ссылка на ответ

    Да, оба! if $done получает двойную подстановку, как и expr. С while хуже, потому что условие подставляется один раз, ещё до начала цикла.

    set i 0
    set guard 0
    while "$i < 3" {
        incr i
        if {[incr guard] > 10} {
            puts "still looping, i is $i"
            break
        }
    }

    Из-за кавычек условие превращается в 0 < 3 раньше, чем while вообще запустится, так что while снова и снова вычисляет константу, и цикл не кончается (guard здесь только затем, чтобы демо остановилось). С {$i < 3} переменная i перечитывается на каждом проходе. То же самое с for и его условием.

  5. Ссылка на ответ

    Та же двойная подстановка добирается до хостов на C, которые собирают скрипты склейкой строк. Команда, собранная через sprintf и переданная в Tcl_Eval, разбирается как текст скрипта, поэтому значение со скобками выполняется. Tcl_EvalObjv принимает команду как массив слов, которые повторно не разбираются и не подставляются, так что значение остаётся одним словом, какие бы символы в нём ни были. Это и есть аналог фигурных скобок на стороне C. Он не помогает, если одно из слов само является скриптом, например телом для eval или expr без скобок, поэтому правило всё равно должно соблюдаться в коде на Tcl, который эти слова получает.

  6. Ссылка на ответ

    Вот версия, которая реально могла бы со мной случиться. В маленькой форме на Tk есть поле количества, итог считается через expr $qty * $price, и кто-то вставляет в поле [clock seconds], просто посмотреть, что будет (люди правда вставляют в поля странное). Итог превращается в огромное число, и никто не понимает, откуда оно взялось. Фигурные скобки чинят подстановку. Но стоит ли полю ещё и отказывать во всём, что не число, до того как значение доберётся до expr, или скобок самих по себе хватает?

  7. Ссылка на ответ

    Обычно это сравнивают с параметрами привязки в SQL, и сравнение держится лучше большинства аналогий. Со скобками текст выражения фиксируется при компиляции процедуры, а $qty приходит во время выполнения как значение и никогда не читается как синтаксис выражения. Без скобок это SQL, собранный из строк и разбираемый заново при каждом вызове, что и видно по подсчёту max8.

    Одна оговорка. Привязанное значение получает тип параметра. Операнд в скобках expr по-прежнему толкует по тому, как выглядит строка. Если в qty лежит 0x10, expr {$qty + 0} даёт 16, а с 1e3 получается 1000.0. Так что скобки убирают инъекцию, а для числового прочтения всё ещё нужна проверка поля, о которой спрашивал kim_dh.

  8. Ссылка на ответ

    Для комментария к ревью мне ближе всего «скобки передают expr выражение как написано». «Скобки ускоряют expr» - правда, но после этого их считают оптимизацией, которую можно пропустить.

  9. Ссылка на ответ

    В PHP это было в виде create_function, которая за кулисами прогоняла тело-строку через eval, так что всё подставленное становилось кодом. В PHP 8 её удалили, и всё, ради чего её использовали, закрывают замыкания.

  10. Ссылка на ответ

    После всего этого были прочёсаны мои скрипты на предмет expr без скобок, и почти везде это была просто лень. Кроме одного случая! Маленькая процедура-калькулятор делала expr $a $op $b с оператором в переменной, и скобки её ломают, потому что expr {$a $op $b} даёт синтаксическую ошибку, подстановка может дать только операнд. Понравилось решение с семейством команд tcl::mathop.

    set a 3
    set b 4
    set op +
    puts [tcl::mathop::$op $a $b]

    Это обычный вызов команды, так что $a и $b передаются как слова, и ничего не разбирается дважды. Печатает 7. Построже будет switch по $op со списком разрешённых значений впереди, ведь в tcl::mathop:: есть ещё ** и <<, которые калькулятору, наверное, предлагать не стоит.

  11. Ссылка на ответ

    И то и другое, по порядку. Сначала проверка поля решает, готово ли значение вообще быть количеством. Берите string is double -strict $qty, потому что без -strict пустая строка проходит, и пустое поле количества дойдёт до expr и упадёт уже там, с менее дружелюбным сообщением. Когда проверка пройдена, итог считает expr в скобках. Скобки защищают вычисление от текста, а проверка говорит пользователю, что не так, ещё до начала вычисления. Пример tally6 с 0x10 тоже проходит string is double -strict, так что если шестнадцатеричное не должно считаться количеством, полю нужно ещё одно правило.

  12. Ссылка на ответ

    С этой оговоркой я немного поспорю. Шестнадцатеричная и экспоненциальная запись тоже дают числа, так что прочтение 0x10 как 16 и есть типизация значения, просто с более широким понятием числа, чем у пользователя. Это не та же проблема, что инъекция, и складывать их в одну кучу не хочется. Проверка поля решает, какие числа принимает форма. Скобки решают, может ли текст стать кодом. Если их смешать, легко поверить, что версию без скобок спасла числовая проверка.

Другие стороны того же вопроса

  1. pure в D разрешает функции писать через аргументы

    pure в D слабее, чем обещает слово, и заметно это стало только потому, что эксперимент с вычислителем захотел кэшировать вызо…

    Ответы 4d
  2. Макрос не может сделать перевод строки концом формы

    Макрос не может сделать так, чтобы перевод строки закрывал форму. А хочется. Запись совсем маленькая. paint red blue на отдел…

    Ответы 4racketlispex
  3. Как воспроизвести правило, которое читает часы?

    Попытка переиграть решение о цене не удалась. Правило eligible(item), написанное на Лиспексе, читало часы хоста и ту ревизию…