สมมติอยาก parse source code หา methods ทั้งหมด define ที่ไหน และรับ arguments อะไร ทำไง?
ความคิดแรกอาจเป็น regexp... แต่มีวิธีดีกว่าไหม?

มี! Static analysis คือเทคนิคใช้ตอนต้องดึงข้อมูลจาก source code เอง ทำโดยแปลง source code เป็น tokens (parsing)
มาเริ่มเลย!
Using the Parser Gem
Ruby มี parser ใน standard library ชื่อ Ripper แต่ output ยากต่อการใช้งาน ผมชอบ parser gem รูบิคอปใช้ gem นี้ทำ magic
Gem นี้มี binary ใช้ parse code โดยตรงดู parse tree ได้
ตัวอย่าง:
ruby-parse -e '%w(hello world).map { |c| c.upcase }'
Output 长得像这样:
(block (send (array (str "hello") (str "world")) :map) (args (arg :c)) (send (lvar :c) :upcase))
มีประโยชน์ตอนพยายามเข้าใจ Ruby parse code บางอย่าง แต่ถ้าอยากสร้าง analysis tools เอง ต้อง read source file, parse, แล้ว traverse generated tree
ตัวอย่าง:
require 'parser/current'
code = File.read('app.rb')
parsed_code = Parser::CurrentRuby.parse(code)
Parser คืน AST (Abstract Syntax Tree) ของโค้ด อย่าตื่นตระหนกชื่อ มันง่ายกว่าที่คิด 🙂
Traversing The AST
ตอนมี parsed code ด้วย parser gem ต้อง traverse AST ที่ได้ สร้าง class inherit จาก AST::Processor
ตัวอย่าง:
class Processor < AST::Processor end
Instantiate class เรียก .process method:
ast = Processor.new ast.process(parsed_code)
Define on_ methods ตรงกับ node names ใน AST หา methods ที่ต้อง define 加上 handler_missing method ใน Processor class ต้องมี on_begin method ด้วย
class Processor < AST::Processor
def on_begin(node)
node.children.each { |c| process(c) }
end
def handler_missing(node)
puts "missing #{node.type}"
end
end
ตอนนี้เราที่ไหน: มี Ruby AST และ basic processor รันโค้ดจะเห็น node types ของ AST
ตอนนี้: Implement on_ methods ที่อยากใช้ เช่นอยากได้ instance method names กับ line numbers:
def on_def(node)
line_num = node.loc.line
method_name = node.children[0]
puts "Found #{method_name} at line #{line_num}"
end
รันโปรแกรมตอนนี้ควรพิมพ์ method names ทั้งหมดที่เจอ
สรุป
สร้าง Ruby static analysis tool ไมายากเท่าที่คิด ถ้าอยากตัวอย่างสมบูรณ์กว่านี้ดู class_indexer gem ของผม ตอนนี้ถึงคุณสร้าง tools ของตัวเอง!
แชร์โพสต์นี้ ถ้าชอบ! 🙂