Я пытаюсь использовать CPS для упрощения реализации потока управления в моем интерпретаторе Python. В частности, при реализации return
/break
/continue
я должен сохранять состояние и разматывать вручную, что является утомительным. Я читал, что это чрезвычайно сложно реализовать обработку исключений. Я хочу, чтобы каждая функция eval
имела возможность направлять поток управления либо на следующую команду, либо на другую команду целиком.
Некоторые люди с большим опытом, чем я, предложили изучить CPS как способ справиться с этим должным образом. Мне очень нравится, как это упрощает поток управления в интерпретаторе, но я не уверен, сколько мне нужно сделать для этого.
-
Нужно ли мне запускать CPS-преобразование в AST? Должен ли я понизить этот АСТ до более низкого уровня IR, который меньше, а затем преобразовать его?
-
Нужно ли обновлять оценщика, чтобы принять продолжение успеха повсюду? (Я так полагаю).
Я думаю, что я вообще понимаю трансформацию CPS: цель состоит в том, чтобы пронизывать продолжение через весь АСТ, включая все выражения.
Я также немного смущен, где монада Cont
подходит здесь, так как язык хоста - Haskell.
Изменить: здесь приведена сжатая версия AST. Это 1-1-отображение операторов, выражений и встроенных значений Python.
data Statement
= Assignment Expression Expression
| Expression Expression
| Break
| While Expression [Statement]
data Expression
| Attribute Expression String
| Constant Value
data Value
= String String
| Int Integer
| None
Чтобы оценить выражения, я использую eval
:
eval (Assignment (Variable var) expr) = do
value <- evalExpr expr
updateSymbol var value
eval (Expression e) = do
_ <- evalExpr e
return ()
Чтобы оценить выражения, я использую evalExpr
:
evalExpr (Attribute target name) = do
receiver <- evalExpr target
attribute <- getAttr name receiver
case attribute of
Just v -> return v
Nothing -> fail $ "No attribute " ++ name
evalExpr (Constant c) = return c
Все, что мотивировало все это, - это махинации, необходимые для осуществления перерыва. Определение разрыва разумно, но то, что он делает с определением while, немного:
eval (Break) = do
env <- get
when (loopLevel env <= 0) (fail "Can only break in a loop!")
put env { flow = Breaking }
eval (While condition block) = do
setup
loop
cleanup
where
setup = do
env <- get
let level = loopLevel env
put env { loopLevel = level + 1 }
loop = do
env <- get
result <- evalExpr condition
when (isTruthy result && flow env == Next) $ do
evalBlock block
-- Pretty ugly! Eat continue.
updatedEnv <- get
when (flow updatedEnv == Continuing) $ put updatedEnv { flow = Next }
loop
cleanup = do
env <- get
let level = loopLevel env
put env { loopLevel = level - 1 }
case flow env of
Breaking -> put env { flow = Next }
Continuing -> put env { flow = Next }
_ -> return ()
Я уверен, что здесь можно сделать больше упрощений, но основная проблема заключается в том, что где-то заполняется, и вручную завершается. Я надеюсь, что CPS позволит мне вести бухгалтерский учет (например, точки выхода цикла) в состояние и просто использовать те, когда они мне нужны.
Мне не нравится разделение между выражениями и выражениями и беспокоиться, что это может сделать преобразование CPS более эффективным.