การเขียน python เชื่อมต่อกับ understand API
หลังจากที่เคยเขียนการใช้งาน understand API ด้วย python มาบ้างแล้ว แต่นานมาแล้ว
บล็อกตอนนี้จะมาเขียนละเอียดขึ้นมาหน่อย เป็นหลักเป็นการ อ่านง่ายขึ้น มี censor เยอะหน่อย เพราะเป็นงานภายในบริษัทเนอะ
ทำไมถึงใช้ understand เพราะว่าตัว source code ของโปรเจกมันเยอะมาก ถึงเราจะทำแค่บางตัวที่เขาสั่ง แต่เวลาเอาไปรันจริง มันใช้ทั้งหมดแหละ ดังนั้นเราจึงตรวจสอบไฟล์ที่เกี่ยวข้องกับไฟล์ที่เราเทสด้วย และการใช้โปรแกรมนี้สร้าง database ทำให้คนทำ in-house tool ทำงานง่ายขึ้น เพราะลำพังเขียน python ดักชื่อฟังก์ชั่น ชื่อตัวแปร คงจะลำบากน่าดู อีกทั้ง เรานะรู้ได้ยังไงว่านี่คือตัวแปร นี่คือฟังก์ชั่น
understand API รองรับให้ developer เอาไปพัฒนาต่อได้ตามใจ ในภาษา perl python และ C
ในที่นี้เป็น python เนอะ และเป็น python 3.0 ขึ้นไปด้วยนะ
การเปิดไฟล์ understand project ขึ้นมาอ่าน
import understand
db = understand.open("c:\\projects\\test.udb")
for file in db.ents("file"):
print(file.longname())
เรา import understand เพื่อเรียกใช้งาน library
จากนั้นเราเปิดไฟล์ udb อันเป็น understand project ที่เราสร้างขึ้น
ส่วนสองบรรทัดหลัง คือ ให้ปริ๊น path ของไฟล์ทั้งหมดออกมาแสดง
ข้อสังเกต เวลาเราจะอ่านไฟล์ เขียนไฟล์ path ต้องเขียน \\ คั่นเสมอ
เนื่องจากการเขียน \ ตามด้วย char สักตัว python มันจะมองเป็นอะไรสักตัวที่ผ่านการรวมร่างแล้ว (เหมือนมองเป็น trigraph มั้ง)
มาทำความรู้จัก Entity vs. Reference
Entity คือ สิ่งที่อยู่ในโค้ดของเรา ที่เราสามารถเอาไปใช้ได้ เช่น file, class, function, variable โดยเมื่อข้างบน คือการดูว่า มีไฟล์ไหนบ้างในโปรเจกของเรา
Reference คือ ตัวอ้างอิง ว่าอันนี้ อยู่ตรงไหนของโค้ด ซึ่งมีความสัมพันธ์ระหว่าง 2 entity
ใช้ตัวอย่างของเขาอธิบายดีกว่า
//myFile.c
void main(){
char myString[];
myString = "Hello World!";
}
อธิบายคร่าวๆ กล่องแรก บอกว่าไฟล์ชื่ออะไร อยู่ที่ไหน ใช้ภาษาอะไรเขียนต่อมา บอกว่า มีชื่อฟังก์ชั่นอะไร อยู่ในไฟล์ไหน บรรทัดเท่าไหร่
ตัวแปร myString มีการใช้งาน 2 ที่ คือ ใช้ประกาศตัวแปรด้านบน และเขียนค่าดังนั้น จึงมี referent 2 ที่ และเชื่อมกันระหว่าง function main และตัวมันเอง
การ filter entity ชนิดต่างๆ
ในตอนแรกที่เราให้ปริ๊นชื่อไฟล์ออกมา เป็นการ filter ว่าเราเอาเฉพาะชื่อไฟล์มาใช้งานนะ
ดังนั้น การใช้งาน เป็นแบบนี้
ents("<kind_filter>")
ใส่ entity ที่เราต้องการหา คือ File, Class, Function, Variable, Attribute, LambdaParameter, Module, Package ใส่ไว้ในของฟันหนู
เราสามารถใช้ logic มาช่วยในการกรองข้อมูลที่เราต้องการได้ด้วย
- NOT ใช้ตัว ~
- AND ใช้ space
- OR ใช้ ,
เช่น
ents("class, function")) //กรองชื่อ class และ function
ents("Global Object ~Static") //หา object ที่เป็น global ไม่เอา static
ents(function,method,procedure) //หาชื่อฟังก์ชั่น
การ sort ข้อมูล
ใส่ function sorted ไว้ด้านหน้าสิ่งที่เราจะเรียงข้อมูล
for ent in sorted(db.ents(), key = lambda ent: ent.name()):
print(ent.name(), "[", ent.kindname(), "]", sep = "", end = "\n")
A1 [Parameter]
A2 [Parameter]
F1 [Static Function]
B1 [Parameter]
การใช้ referent หาว่า entity ที่เราต้องการว่าถูกเรียกใช้ที่ไหน
for ent in db.ents("Global Object ~Static"):
print(ent, ":", sep="")
for ref in ent.refs():
print(ref.kindname(), ref.file(), "(", ref.line(), ",", ref.column(), ")")
print("\n", end="")
ในที่นี่เราอยากรู้ว่าตัวแปรที่เป็น global มีตัวแปรชื่ออะไรบ้าง
(logic คือ Global & Object & !Static)
ถูกทำอะไรที่ไหน ที่ไฟล์ไหน บรรทัดและคอลัมน์ที่เท่าไหร่
การใช้ regular expression
ก่อนอื่น import re ก่อนเพื่อเรียกใช้งาน regular expression
ถ้าเราหาไฟล์ชื่อที่เราต้องการ เช่น เราต้องการหาไฟล์ที่มีคำว่า test อยู่ เป็นไฟล์ .c
re.compile เป็นการหาคำตาม pattern ที่เราสนใจ
re.I คือ ignore case ไม่สนใจตัวใหญ่เล็ก สนใจว่าคำตรงกันไหม
searchstr = re.compile("test*.c", re.I)
for file in db.lookup(searchstr, "File"):
print(file)
แสดงฟังก์ชั่นพร้อมด้วย parameter
ในขั้นแรกเราทำ function key ก่อน
import string
def sortKeyFunc(ent):
return str.lower(ent.longname())
จากนั้นเรามา sorted ชื่อของ function พร้อมทั้ง parameter ของ function
ents = db.ents("function, method, procedure")
for func in sorted(ents, key = sortKeyFunc):
print (func.longname(), " (", sep="", end="")
first = True
for param in func.ents("Define", "Parameter"):
if not first:
print (", ", end="")
print (param.type(), param, end="")
first = False
print (")")
การตรวจสอบว่าแต่ละ function ถูกเรียกใช้ที่ function ใดบ้าง
for func in db.ents("function, method, procedure"):
for line in func.ib():
print (line, end="")
เราจะเลือกตัวที่เป็น function และบอกรายละเอียดว่า ถูก defined ที่ไหน return ค่าเป็นอะไร มี parameter ขาเข้าเป็นอะไร ชื่อ local variable ที่ใช้งานภายใน เรียกใช้ function อะไร ถูกใครเรียกต่อ มีมาโครอะไรให้ใช้ เจอที่ไหน ประมาณนี้
ถ้าเราจะหาตัวแปรหล่ะ เปลี่ยนด้านบนนิดหน่อย ดังนี้
for func in db.ents("variable, define, parameter"):
for line in func.ib():
print (line, end="")
หา comment ใน function
for func in db.ents("function ~unresloved ~unknown")
comments = func.comment("after")
if comments:
print (func.longname(), ":\n ", comments, ":\n", sep="")
Lexers and Lexemes
Lexers คือ ก้อนของข้อความที่มีความหมาย เช่น string, comment, variable
Lexemes คือ สตรีม (เข้าใจว่าน่าจะก้อนใหญ่ที่ใหญ่กว่า lexers)
เช่น int a=5;//radius
มันจะแยกคำในโค้ดออกมาเป็นดังนี้
อันนี้จะละเอียดกว่า entity กับ referent ตรงที่ มันบอกว่า อันนี้เป็น keyword นะ อันนี้เป็น operator ตรงนี้ comment นะเออ
ตัวอย่างโค้ด
def fileCleanText(file):
returnString = "";
# Open the file lexer with macros expanded and
# inactive code removed
for lexeme in file.lexer(False,8,False,True):
if(lexeme.token() != "Comment"):
# Go through lexemes in the file and append
# the text of non-comments to returnText
returnString += lexeme.text();
return returnString;
# Search for the first file named 'test' and print
# the file name and the cleaned text
file = db.lookup(".*test.*","file")[0];
print (file.longname());
print(fileCleanText(file));
อันนี้ทำ function เพื่อพิมพ์ค่าโค้ดทั้งหมดออกมา โดยไม่มี comment อยู่ด้วย
คำถาม : ถ้าเราลงลึกไปว่า เราสนใจ file นี้ variable นี้ ถูกเรียกใช้ที่ไหนหล่ะ
เนื่องจากตัวอย่างที่เราให้มา เอะอะก็วนลูป
เราอาจจะหาตัวแปรภายใน file นั้นๆ ออกมาแสดง จากนั้นก็เอาแต่ละอย่างมาทำอะไรต่อก็แล้วแต่เราแล้ว ในที่นี้ คือ ให้แสดงว่า ตัวแปรนี้ มีการประกาศตัวแปร กำหนดค่า ที่ไหน
ref.kindname จะมี Define, Type, Init, Set, Use, Addr Use
ent.ref().scope() อันนี้บอกชื่อตัวแปร ถ้าหาชื่อฟังก์ชั่นก็จะบอกชื่อฟังก์ชั่นแทน
ref.line() บอกบรรทัดที่เกิดเหตุการณ์ขึ้น
for func in db.ents("global object ~function !static ~local")
for ref in ent.refs():
if (str(ref.file()) == file_name):
print (ref.kindname(), ent.ref().scope(), ref.line())
คำถาม : อยากหาบรรทัดที่มีการเรียกใช้ header file ว่าเรียกใช้ไฟล์อะไรมาบ้าง
แต่ถ้ามีชื่อไฟล์คล้ายๆกัน เช่น aaa.c, aaa15.c แล้วเราอยากได้ aaa30.c หล่ะ
เราจะต้องกรอง db.lookup ให้ได้ชื่อไฟล์ที่เราต้องการก่อน จึงจะนำไปใช้ต่อได้
cnt = 0
file = udb.lookup(file_name,"file")
for i in file:
cnt+=1
if (str(i) == file_name + ".c"):
break
file = udb.lookup(file_name,"file")[cnt-1]
จากนั้นมาลุยกันต่อเลย ใช้ lexemes ในการหานะ lexeme.token() หลักๆ มีดังนี้
- Comment อันนี้ซื่อๆ พวกคอมเมนต์
- Identifier ชื่อตัวแปร ชื่อค่าคงที่ ชื่อฟังก์ชั่น
- Keyword เช่น const static struct void if else break case default return for while
- Literal ค่าของตัวแปรที่เรา assign ออกแนว magic number ในบางที่
- Newline ขึ้นบรรทัดใหม่
- Operator เช่น + - * / = ! || && อะไรพวกนี้
- Preprocessor พวกที่มี # เช่น #include #define
- Punctuation เช่น ( ) { } ;
- String ก็สตริงนั่นหล่ะ
- Whitespace สเปคปกตินั่นแหละ
ดังนั้นตามโจทย์ประมาณนี้นะ
for lexeme in file.lexer():
if (lexeme.token() == "Preprocessor"):
print (lexeme.line_begin(), lexeme.text())
สำหรับตอนนี้คงจะได้ความรู้เกี่ยวกับการใช้ understand API ใน python มากขึ้น
บอกก่อนเลยว่าบล็อกนี้ดองนานมาก เพราะต้องทำความเข้าใจ API ตัวนี้ บวกกับมีงานเทสที่เข้ามาด้วย เลยทำๆหยุดๆ และ understand database นี้ น่าจะเป็น NoSQL นะ เพราะไม่ได้เก็บค่า fixed แบบตาราง และข้อมูลบางตัว ก็เป็น one-to-many ด้วย เช่น ตัวแปรนี้ถูกเรียกที่ไหน โปรเจกนึงโค้ดก็เยอะแยะ พวก global variable ก็ใช้หลายที่อยู่
สำหรับผู้ที่สนใจเพิ่มเติม ตามด้านล่างนี้จ้า บอกเลยอากู๋ไม่ค่อยมีข้อมูลพวกนี้เท่าไหร่ ติดปัญหาทีก็หายากจัง
- API Tutorial 1: Writing Your First API Script
- API Tutorial 2: Entities, References and Filters
- API Tutorial 3: Lexers and Lexemes- Python Entity Kinds
- Understand::Ref class
- python interface to Understand databases
- 6.2. re — Regular expression operations — Python 3.5.2 documentation