Обработка кода через интерактивное дерево для Mathematica

Этот вопрос заставлял меня обдумать интерактивный метод редактирования кода. Интересно, можно ли реализовать что-то подобное с учетом динамических возможностей Mathematica.

Рассмотрим выражение:

Text[Row[{PaddedForm[currentTime, {6, 3}, NumberSigns -> {"", ""}, NumberPadding -> {"0", "0"}]}]]

И его TreeForm:

enter image description here

Я хотел бы иметь возможность редактировать это дерево напрямую, а затем возвращать результат в код Mathematica. По крайней мере, нужно иметь возможность:

  • переименование узлов, замена символов
  • удалить узлы, возвращая их листья в node выше
  • переупорядочивает узлы и листья (порядок аргументов)

Я считаю, что существуют языки или среды, которые специализируются на таких манипуляциях, и я не считаю это привлекательным, но меня интересует такое интерактивное редактирование дерева для специальных целей.

Ответы

Ответ 1

Я предоставлю частичное решение, но это поможет вам начать. Я буду использовать измененную структуру данных дерева из этой почты, так как это кажется изменчивостью для этой проблемы. Повторите это для удобства здесь:

Module[{parent, children, value},
  children[_] := {};
  value[_] := Null;
  node /: new[node[]] := node[Unique[]];
  node /: node[tag_].getChildren[] := children[tag];
  node /: node[tag_].addChild[child_node, index_] := 
     children[tag] = Insert[children[tag], child, index];
  node /: node[tag_].removeChild[child_node, index_] := 
     children[tag] = Delete[children[tag], index];
  node /: node[tag_].getChild[index_] := children[tag][[index]];
  node /: node[tag_].getValue[] := value[tag];
  node /: node[tag_].setValue[val_] := value[tag] = val;
];

Вот код для создания изменяемого дерева из любого выражения Mathematica и чтения выражения из дерева:

Clear[makeExpressionTreeAux];
makeExpressionTreeAux[expr_?AtomQ] :=
  With[{nd = new[node[]], val = Hold[Evaluate[Unique[]]]},
    nd.setValue[val];
    Evaluate[val[[1]]] = expr;
    nd];
makeExpressionTreeAux[expr_] :=
  With[{nd = new[node[]], val = Hold[Evaluate[Unique[]]]},
   nd.setValue[val];
   Evaluate[val[[1]]] = Head[expr];
   Do[nd.addChild[makeExpressionTreeAux[expr[[i]]], i], {i, Length[expr]}];
   nd];

Clear[expressionFromTree];
expressionFromTree[nd_node] /; nd.getChildren[] == {} := (nd.getValue[])[[-1, 1]];
expressionFromTree[nd_node] := 
  Apply[(nd.getValue[])[[-1, 1]], Map[expressionFromTree, nd.getChildren[]]];

Clear[traverse];
traverse[root_node, f_] :=
  Module[{},
   f[root];
   Scan[traverse[#, f] &, root.getChildren[]]];

Clear[indexNodes];
indexNodes[root_node] :=
  Module[{i = 0},
     traverse[root, #.setValue[{i++, #.getValue[]}] &]];

Clear[makeExpressionTree];
makeExpressionTree[expr_] :=
  With[{root  = makeExpressionTreeAux[expr]},
   indexNodes[root];
   root];

Вы можете протестировать простые выражения типа a+b. Несколько комментариев о том, как это работает: создать мутируемое дерево выражений (построенное из node -s) из выражения, мы вызываем функцию makeExpressionTree, которая сначала создает дерево (вызов makeExpressionTreeAux), а затем индексирует узлы (вызов indexNodes). Функция makeExpressionTree рекурсивна, она рекурсивно обходит дерево выражений при копировании его структуры в структуру полученного изменяемого дерева. Один из тонких моментов заключается в том, почему нам нужны такие вещи, как val = Hold[Evaluate[Unique[]]], nd.setValue[val];, Evaluate[val[[1]]] = expr;, а не только nd.setValue[expr]. Это делается с учетом InputField[Dynamic[some-var]] - для этого нам нужна переменная для хранения значения (возможно, можно написать более пользовательский Dynamic, чтобы избежать этой проблемы, если вам нравится). Таким образом, после создания дерева каждый node содержит значение Hold[someSymbol], а someSymbol содержит значение атома или головы для неатомной подчасти. Процедура индексации изменяет значение каждого node от Hold[sym] до {index,Hold[symbol]}. Обратите внимание, что в нем используется функция traverse, которая реализует общий путь пересечения дерева с глубиной в глубину (аналогично Map[f,expr, Infinity], но для изменяемых деревьев). Поэтому индексы увеличиваются в порядке глубины. Наконец, функция expressionFromTree обходит дерево и строит выражение, которое хранит дерево.

Вот код для рендеринга изменяемого дерева:

Clear[getGraphRules];
getGraphRules[root_node] :=
 Flatten[
  Map[Thread,
   Rule @@@ 
     Reap[traverse[root, 
       Sow[{First[#.getValue[]], 
         Map[First[#.getValue[]] &, #.getChildren[]]}] &]][[2, 1]]]]

Clear[getNodeIndexRules];
getNodeIndexRules[root_node] :=
 [email protected] Reap[traverse[root, Sow[First[#.getValue[]] -> #] &]][[2, 1]];

Clear[makeSymbolRule];
makeSymbolRule[nd_node] :=
   With[{val = nd.getValue[]},
      RuleDelayed @@ Prepend[Last[val], First[val]]];

Clear[renderTree];
renderTree[root_node] :=
 With[{grules = getGraphRules[root],
    ndrules = getNodeIndexRules[root]},
     TreePlot[grules, VertexRenderingFunction ->
      (Inset[
        InputField[Dynamic[#2], FieldSize -> 10] /. 
          makeSymbolRule[#2 /. ndrules], #] &)]];

Эта часть работает следующим образом: функция getGraphRules обходит дерево и собирает родительские-родительские пары индексов node (в форме правил), результирующий набор правил - это то, что ожидает GraphPlot как первый аргумент. Функция getNodeIndexRules обходит дерево и строит хеш-таблицу, где ключи node индексы и значения являются самими узлами. Функция makeSymbolRule принимает node и возвращает правило с задержкой формы index:>node-var-symbol. Важно, чтобы правило было отложено, чтобы символы не оценивались. Это используется для вставки символа из дерева node в InputField[Dynamic[]].

Вот как вы можете его использовать: сначала создайте дерево:

root  = makeExpressionTree[(b + c)*d];

Затем выполните его:

renderTree[root]

Вы должны иметь возможность изменять данные в каждом поле ввода, хотя для отображения курсора требуется несколько щелчков мыши. Например, я редактировал c как c1 и b как b1. Затем вы получите модифицированное выражение:

In[102]:= expressionFromTree[root]

Out[102]= (b1 + c1) d

Это решение обрабатывает только модификации, но не удаление узлов и т.д. Однако это может быть отправной точкой и быть расширенным, чтобы покрыть это.

ИЗМЕНИТЬ

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

Clear[renderTreeAlt];
renderTreeAlt[expr_] :=
  Module[{newExpr, indRules, grules, assignments, i = 0, set},
    getExpression[] := newExpr;
    newExpr = expr /. x_Symbol :> set[i++, Unique[], x];
    grules = 
      Flatten[ Thread /@ Rule @@@ 
        Cases[newExpr, set[i_, __][args___] :> 
          {i, Map[If[MatchQ[#, _set], First[#], First[#[[0]]]] &, {args}]}, 
          {0, Infinity}]];
   indRules = [email protected] 
        Cases[newExpr, set[ind_, sym_, _] :> (ind :> sym), {0, Infinity}, Heads -> True];
   assignments = 
       Cases[newExpr, set[_, sym_, val_] :> set[sym , val], {0, Infinity},Heads -> True];
   newExpr = newExpr /. set[_, sym_, val_] :> sym;
   assignments /. set -> Set;
   TreePlot[grules, VertexRenderingFunction -> (Inset[
           InputField[Dynamic[#2], FieldSize -> 10] /. indRules, #] &)]
]

Вот как вы его используете:

renderTreeAlt[(a + b) c + d]

Вы можете вызвать getExpression[] в любое время, чтобы увидеть текущее значение выражения или присвоить его любой переменной, или вы можете использовать

Dynamic[getExpression[]]

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