วิธีการประกอบ Python abstract syntax tree (AST) ใน Mathematica

Sep 10 2020

ฉันต้องการที่จะรวบรวม Python ' ast.Module ' วัตถุภายใน Mathematica ExternalEvaluate["exec(astObject)"]แล้วส่งให้พวกเขาผ่านทางงูใหญ่เช่น

ฟังก์ชัน Python ast , parse , eval , execและcompileสามารถทำงานบนอ็อบเจ็กต์ ' ast.Module ' ไม่ว่าจะเป็นเอาต์พุตหรือรับเป็นอินพุต แต่ผมไม่ทราบว่าวิธีการประกอบนี้astObjectภายใน Mathematica / WL ExternalEvaluateแล้วส่งไปที่งูใหญ่ผ่าน

ฉันกำลังพยายามสร้างโค้ด Python ด้วยโปรแกรมใน MMA (สำหรับอัลกอริธึมทางพันธุกรรม) จากนั้นส่งไปยัง Python เพื่อทำการประเมิน ฉันสามารถรวบรวมสตริงโค้ด Python ได้ แต่แล้วฉันก็ต้องจัดการกับการเยื้องทั้งหมดซึ่งดูเหมือนจะเจ็บปวด

ตัวอย่างเช่นใน Python สามารถทำสิ่งต่อไปนี้ได้:

import ast

pythonString="X3=X1*X2"
astObject=ast.parse(pythonString)
X1=3
X2=4
exec(compile(astObject,"","exec"))
print(X3)

-> 12

และแน่นอนจากวีคนี้เป็นไปได้ที่จะทำ:

session=StartExternalSession["Python"]
ExternalEvaluate[session, {"import ast","X1=3","X2=4",
 "exec(compile(ast.parse(\"X3=X1*X2\"),\"\",\"exec\"))"} ]

เพื่อให้ได้ผลลัพธ์เดียวกัน (เช่น 12)

แต่ผมต้องการที่จะสร้างบิตของฉันรหัสหลาม ( "X1=3", "X2=4", "X3=X1*X2") ใน Mathematica บิตเหล่านี้ง่ายเพียงพอ แต่ฉันตั้งใจจะสร้างโปรแกรมที่สมบูรณ์เช่นคำสั่งและนิพจน์ metaprogrammatically (!) ในการทำเช่นนั้นฉันต้องหาวิธีแยกวิเคราะห์การเยื้องที่น่ารำคาญของ Python ซึ่งแน่นอนว่ามันแยกความแตกต่างของนิพจน์ชุดหนึ่งจากชุดถัดไปและการอ้างอิงของมันคืออะไร ฉันเกลียดที่จะทำเช่นนั้นและคิดว่ามันอาจจะง่ายกว่าที่จะใช้งานโครงสร้างast

เดิมทีฉันคิดว่าฉันอาจจะสามารถใช้รูปแบบสตริงกลางจากast.dump()ฟังก์ชันของ Python ซึ่งดูเหมือนว่า:

astString = ast.dump(pythonString)
-> "Module(Body=[Assign(targets=[Name(id='X3',ctx=Store())],value=BinOp(left=Name(id='X1',
ctx=Load()),op=Mult(),right=Name(id='X2',ctx=Load())))])"

และเนื่องจากastStringสิ่งนี้ทำให้เป็นอนุกรมเป็นหลักastObjectฉันจึงสามารถสร้างสิ่งนี้แทนได้ อย่างไรก็ตามฉันไม่พบวิธีที่จะให้ Python ทำอะไรกับสิ่งนี้astStringได้

เป็นไปได้ไหมที่จะสร้างวัตถุ Python ประเภทนี้เช่นastObjectด้านบนของฉันที่ด้าน Mathematica

PS: นี่คือคำอธิบายของอ็อบเจ็กต์ ' ast.Module ':https://greentreesnakes.readthedocs.io/en/latest/tofrom.html

PPS: ฉันโพสต์ข้ามสิ่งนี้ในชุมชน Wolfram: https://community.wolfram.com/groups/-/m/t/2070851

คำตอบ

7 AntonAntonov Sep 10 2020 at 03:34

ฉันไม่แน่ใจว่าคุณกำลังมองหาอะไรกันแน่ ฉันคิดว่าคำตอบของคำถามของคุณ:

ฉันกำลังพยายามสร้างโค้ด Python โดยใช้โปรแกรมใน MMA [... ] จากนั้นส่งไปยัง Python เพื่อทำการประเมิน ฉันสามารถรวบรวมสตริงโค้ด Python ได้ แต่แล้วฉันก็ต้องจัดการกับการเยื้องทั้งหมดซึ่งดูเหมือนจะเจ็บปวด

เป็นไปได้ไหมที่จะสร้างวัตถุ Python ประเภทนี้ทางด้าน Mathematica

ค่อนข้างตรงไปตรงมาโดยใช้ExternalEvaluateไลบรารี "ast" ของ Python

นี่คือตัวอย่าง:

code = "'[i**2 for i in range(10)]'";

astTemplate = 
  StringTemplate["import ast; eval(compile(ast.parse(`1`, mode='eval'), '', 'eval'))"];

astTemplate[code]

(* "import ast; eval(compile(ast.parse('[i**2 for i in range(10)]', mode='eval'), '', 'eval'))" *)

ExternalEvaluate["Python", astTemplate[code]]

(* {0, 1, 4, 9, 16, 25, 36, 49, 64, 81} *)

(ฉันใช้evalแทนexecเพราะevalส่งคืนค่า)

2 C.E. Sep 11 2020 at 00:36

ฉันคิดว่าการรวบรวมโค้ด Python เป็นสตริงนั้นง่ายกว่าที่คุณเสนอ แต่ฉันก็รู้ด้วยว่าฉันแค่พูดมันจะไม่ทำให้ใครมั่นใจได้ดังนั้นนี่คือตัวอย่าง

เรากำหนดสัญลักษณ์สองหัวที่จะแทนโปรแกรม Python ของเราใน Mathematica และฟังก์ชันในการแสดงผลนิพจน์ด้วยหัวสัญลักษณ์เหล่านั้น:

ToPythonString[statements_List] := StringRiffle[ToPythonString /@ statements, "\n"]

ToPythonString[PyBlock[statement_, children_, indent_ : 0]] := StringJoin[
  StringRepeat["    ", indent],
  statement, ":\n",
  ToPythonString[PyIndent /@ children]
  ]

ToPythonString[PyStatement[statement_, indent_ : 0]] := StringJoin[
  StringRepeat["    ", indent],
  statement
  ]

PyIndent[PyBlock[statement_, children_, indent_ : 0]] := PyBlock[
  statement,
  PyIndent /@ children,
  indent + 1
  ]

PyIndent[PyStatement[statement_, indent_ : 0]] := PyStatement[
  statement,
  indent + 1
  ]

สิ่งที่ฟังก์ชันเหล่านี้ช่วยให้เราทำได้คือการเขียนโค้ด Python ใน Mathematica โดยไม่ต้องคิดถึงการเยื้องมันเหมือนกับการสร้างโค้ด Python ด้วยโมดูล ast

นี่คือตัวอย่างของการแสดงผลนิพจน์สัญลักษณ์เป็นสตริง:

prog = {
   PyStatement["a = 1"],
   PyStatement["b = 2"],
   PyBlock["If a > b", {
     PyStatement["Print('a is larger than b')"]
     }],
   PyBlock["def f(x)", {
     PyStatement["Print('executing f')"],
     PyBlock["if x > 0", {
       PyStatement["Print('x is larger than 0')"]
       }]
     }]
   };

ToPythonString[prog]

ออก:

a = 1
b = 2
If a > b:
    Print('a is larger than b')
def f(x):
    Print('executing f')
    if x > 0:
        Print('x is larger than 0')

เราสามารถสร้างสิ่งนี้ได้อย่างง่ายดายและทำให้การแสดงสัญลักษณ์ของโปรแกรม Python มีความหมายมากขึ้น

PyAssign[lhs_, rhs_] := PyStatement[lhs <> " = " <> rhs]
PyPrint[text_] := PyStatement["Print(" <> text <> ")"]

PyFunction[name_, args_, statements_] := PyBlock[
  "def " <> name <> "(" <> StringRiffle[args, ", "] <> ")",
  statements
  ]

PyIf[cond_, statements_] := PyBlock[
  "If " <> cond,
  statements
  ]

PyIf[cond_, statements_, elseStatements_] := {
  PyBlock[
   "If " <> cond,
   statements
   ],
  PyBlock[
   "else",
   elseStatements
   ]
  }

ด้วยคำจำกัดความตัวช่วยเหล่านี้ตอนนี้เราสามารถเขียนโปรแกรมต่อไปนี้ในรูปแบบที่อ่านง่ายมาก

prog = {
   PyAssign["a", "1"],
   PyAssign["b", "2"],
   PyIf[
    "a > b", {
     PyPrint["a is larger than b"]
     }],
   PyFunction["f", {"x"},
    PyIf[
     "x > 0",
     {PyPrint["x is larger than 0"]},
     {PyPrint["x is not larger than 0"]}
     ]
    ]
   };

ToPythonString[prog]

ออก:

a = 1
b = 2
If a > b:
    Print(a is larger than b)
def f(x):
    If x > 0:
        Print(x is larger than 0)
    else:
        Print(x is not larger than 0)

หากคุณยังไม่มีโปรดค้นหา "สัญลักษณ์ C" ในเอกสาร Mathematica โดยพื้นฐานแล้วเป็นวิธีการสร้าง AST สำหรับโปรแกรมภาษา C ใน Mathematica ซึ่งสามารถแปลงเป็นรหัส C ที่รันได้ นั่นเป็นจุดที่เรามุ่งหน้าไปพร้อมกับโค้ดนี้เช่นกันแม้ว่าฉันตั้งใจจะทำให้การใช้งานสมบูรณ์แบบนั้นมันจะไม่เป็นแบบนี้ (โมดูล ast นั้นคุ้มค่าที่จะศึกษาอย่างแน่นอนหากใครต้องการไปตามเส้นทาง)

กลับไปที่ประเด็น: สิ่งที่ฉันต้องการจะสื่อด้วยคำตอบนี้ก็คือคุณไม่จำเป็นต้องใช้เวลามากในการสร้างกรอบงานเล็ก ๆ ที่ช่วยแก้ปัญหาการเยื้องที่คุณพูดถึงในคำถามของคุณ