как правильно разрешить пользователям предоставлять строку "mangler" в виде регулярного выражения / proc / expr / - PullRequest
0 голосов
/ 29 апреля 2020

В моем проекте Tcl / Tk мне нужно разрешить пользователям манипулировать строкой четко определенным способом.

Идея состоит в том, чтобы позволить людям объявлять "искаженную строку" proc / expr / function / ... в файле конфигурации, который затем применяется к рассматриваемым строкам.

Я немного беспокоюсь о том, как правильно реализовать это.

Возможности, которые я рассмотрел до сих пор:

  • регулярные выражения

    Это была моя первая мысль, но есть два предостережения:

    • поиск / замена регулярных выражений в Tcl кажется неловким. по крайней мере, с regsub мне нужно отдельно передать спички и запасные части (в отличие от того, как, например, sed позволяет мне передать одну сложную строку, которая делает все для меня); есть sed реализаций для Tcl, но они выглядят наивными и могут сломаться скорее раньше, чем позже
    • также регулярные выражения могут быть неудобными сами по себе; их использование для манипулирования сложными строками часто сложнее, чем должно быть
  • проц?

    Поскольку целевой платформой, в любом случае, является Tcl, почему бы не использовать питание из Tcl делать каландрирование? «Функция» должна иметь один вход и производить один выход, и в идеале пользователь должен подтолкнуть его сделать это правильно (например, не имея возможности определить pro c, требующий двух аргументов), и это должно быть (почти) невозможно создать побочные эффекты (например, изменить состояние приложения).

    В качестве упрощенного подхода c можно использовать proc mymangler s $body ($body - строка, определенная пользователем), но Есть так много вещей, которые могут go ошибаться:

    • $body, принимая другое имя аргумента (например, $x вместо $s)
    • $body ничего не возвращая
    • $body изменение переменных, ... в среде

    expr эссенции больше похожи на это (всегда возвращают вещи, не позволяя легко изменять среду ), но я не могу заставить их работать со строками, и нет способа передать переменную без согласования ее имени.

Итак, лучшее, что я придумал, это:

set userfun {return $s}      # user-defined string
proc mymangler s ${userfun}
set output [mymangler $input]

Есть ли лучшие способы достижения ve определяемые пользователем строковые менеджеры в Tcl?

Ответы [ 3 ]

1 голос
/ 29 апреля 2020

Подход "simpisti c" подобен foreach в том смысле, что он требует, чтобы пользователь указал имя переменной и скрипт для оценки, который использует эту переменную, и является хорошим подходом. Если вы не хотите, чтобы это влияло на остальную часть программы, запустите ее в отдельном интерпретаторе:

set x 0
proc mymangler {name body} {
   set i [interp create -safe]
   set s "some string to change"
   try {
       # Build the lambda used by apply here instead of making
       # the user do it.
       $i eval [list apply [list $name $body] $s]
   } on error e {
       return $e
   } finally {
      interp delete $i
   }
}
puts [mymangler s { set x 1; string toupper $s }]
puts $x

output

SOME STRING TO CHANGE
0

Если человек, вызывающий это, говорит использовать s в качестве переменной, а затем использует что-то еще в теле, это на них. То же самое с предоставлением скрипта, который ничего не возвращает.

1 голос
/ 30 апреля 2020

Как правило, я бы позволил пользователю указать префикс команды в качестве списка Tcl (для этого тривиально подходят самые простые имена команд), который вы затем применили бы к аргументу, выполнив:

set mangled [{*}$commandPrefix $valueToMangle]

Это позволяет людям предоставлять практически все, что они хотят, тем более что они могут использовать apply и лямбда-термин для манипулирования вещами по мере необходимости. Конечно, если вы находитесь в процедуре, то вам, вероятно, лучше сделать:

set mangled [uplevel 1 [list {*}$commandPrefix $valueToMangle]]

, чтобы вы работали в контексте вызывающего (измените 1 на #0, чтобы использовать вместо этого - глобальный контекст), который может помочь защитить вашу процедуру от случайных изменений и упростить использование upvar в mangler.

Если источник префикса искажения не заслуживает доверия (что это означает, зависит в значительной степени от вашего приложения и развертывания), тогда вы можете запустить искажающий код в отдельном интерпретаторе:

# Make the safe evaluation context; this is *expensive*
set context [interp create -safe]
# You might want to let them define extra procedures too
#     interp invokehidden $context source /the/users/file.tcl

# Use the context
try {
    set mangled [interp eval $context [list {*}$commandPrefix $valueToMangle]]
} on error {msg} {
    # User supplied something bad; error message in $msg
}

Существуют различные способы поддержки пользователей, определяющих преобразование, но если вы можете предоставить им тот факт, что вы работаете с Tcl, тогда это, вероятно, самый простой и самый гибкий.

1 голос
/ 29 апреля 2020

Вы можете использовать apply - пользователь предоставляет список из 2 элементов: второй элемент - это «pro c body», код, который выполняет искажение; первый элемент - это имя переменной для хранения строки, эта переменная используется в теле.

Например:

set userfun {{str} {string reverse $str}}
set input "some string"
set result [apply $userfun $input]    ;# => "gnirts emos"

Конечно, код, который вы получаете от пользователя, является произвольным Код Tcl Вы можете запустить его в безопасном интерпретаторе :

set userfun {{str} {exec some malicious code; return [string reverse $str]}}

try {
    set interp [safe::interpCreate]
    set result [$interp eval [list apply $userfun $input]]
    puts "mangled string is: $result"
    safe::interpDelete $interp
} on error e {
    error "Error: $e"
}

, в результате

Error: invalid command name "exec"

Примечания:

  • стандартной командой Tcl apply
  • пользователь должен указать имя переменной, используемой в теле.
  • эта схема защищает окружающую среду:

    set userfun {{str} {set ::env(SOME_VAR) "safe slave"; return $str$str}}
    set env(SOME_VAR) "main"
    
    puts $env(SOME_VAR)
    try {
        set interp [safe::interpCreate]
        set result [$interp eval [list apply $userfun $input]]
        puts "mangled string is: $result"
        safe::interpDelete $interp
    } on error e {
        error "Error: $e"
    }
    puts $env(SOME_VAR)
    

    выходы

    main
    mangled string is: some stringsome string
    main
    
  • если пользователь не возвращает значение, искаженная строка - это просто пустая строка.
...