2010-02-08 41 views
5

我有興趣在Ruby中構建用於解析微博更新的DSL。具體來說,我認爲我可以將文本翻譯成Ruby字符串,就像Rails gem允許「4.days.ago」一樣。我已經有正則表達式的代碼,將文本在Ruby中構建「半自然語言」DSL

@USER_A: give X points to @USER_B for accomplishing some task 
@USER_B: take Y points from @USER_A for not giving me enough points 

轉化爲類似

Scorekeeper.new.give(x).to("USER_B").for("accomplishing some task").giver("USER_A") 
Scorekeeper.new.take(x).from("USER_A").for("not giving me enough points").giver("USER_B") 

這是我接受正式更新的語法,使得只有標準化的文本提供和分析,讓我聰明地處理更新。因此,它似乎更多的是如何實現DSL類的問題。我有以下的stub類(刪除了所有的錯誤檢查和替換了一些與意見,以儘量減少糊):

class Scorekeeper 

    attr_accessor :score, :user, :reason, :sender 

    def give(num) 
    # Can 'give 4' or can 'give a -5'; ensure 'to' called 
    self.score = num 
    self 
    end 

    def take(num) 
    # ensure negative and 'from' called 
    self.score = num < 0 ? num : num * -1 
    self 
    end 

    def plus 
    self.score > 0 
    end 

    def to (str) 
    self.user = str 
    self 
    end 

    def from(str) 
    self.user = str 
    self 
    end 

    def for(str) 
    self.reason = str 
    self 
    end 

    def giver(str) 
    self.sender = str 
    self 
    end 

    def command 
    str = plus ? "giving @#{user} #{score} points" : "taking #{score * -1} points from @#{user}" 
    "@#{sender} is #{str} for #{reason}" 
    end 

end 

運行以下命令:

t = eval('Scorekeeper.new.take(4).from("USER_A").for("not giving me enough points").giver("USER_B")') 
p t.command 
p t.inspect 

產生預期的結果:

"@USER_B is taking 4 points from @USER_A for not giving me enough points" 
"#<Scorekeeper:0x100152010 @reason=\"not giving me enough points\", @user=\"USER_A\", @score=4, @sender=\"USER_B\">" 

所以我的問題主要是,我正在做什麼來通過建立在這個實現基礎上拍攝自己?有沒有人有任何改善DSL類本身的例子或對我的任何警告?

順便說一句,要獲得eval字符串,我主要使用sub/gsub和正則表達式,我認爲這是最簡單的方法,但我可能是錯的。

+0

我要補充一點,我用鏈式方法做了這個,只是因爲我不知道如何使用像eval這樣的裸字符串來發送它(「由於我不知道如何獲得空間 - 字符串放入方法參數列表中。對此的想法是非常受歡迎的。 – JohnMetta 2010-02-08 19:17:08

回答

0

建立在@David_James的答案,我已經想出了一個只有正則表達式的解決方案,因爲我實際上沒有在其他地方使用DSL來創建分數,而只是向用戶解析分數。我有,我會用它來搜索兩種模式:

SEARCH_STRING = "@Scorekeeper give a healthy 4 to the great @USER_A for doing something 
really cool.Then give the friendly @USER_B a healthy five points for working on this. 
Then take seven points from the jerk @USER_C." 

PATTERN_A = /\b(give|take)[\s\w]*([+-]?[0-9]|one|two|three|four|five|six|seven|eight|nine|ten)[\s\w]*\b(to|from)[\s\w]*@([a-zA-Z0-9_]*)\b/i 

PATTERN_B = /\bgive[\s\w]*@([a-zA-Z0-9_]*)\b[\s\w]*([+-]?[0-9]|one|two|three|four|five|six|seven|eight|nine|ten)/i 

SEARCH_STRING.scan(PATTERN_A) # => [["give", "4", "to", "USER_A"], 
           #  ["take", "seven", "from", "USER_C"]] 
SEARCH_STRING.scan(PATTERN_B) # => [["USER_B", "five"]] 

正則表達式可能會被清理了一下,但是這讓我有語法,允許一些有趣的形容詞,而仍然使用拉動核心信息「名稱 - >點」和「點 - >名稱」語法。它不允許我抓住原因,但那太複雜了,現在我將只存儲整個更新,因爲整個更新將與每個分數的上下文相關,除了異常情況外。獲得「送禮者」用戶名也可以在別處完成。

我已經寫了a description of these expressions爲好,希望其他人可能會發現有用的(而且這樣我就可以回去吧,記住什麼官樣文章的那一長串意味着:)

5

我是否正確理解你:你想從用戶那裏拿一個字符串並引起它觸發一些行爲?

根據你列出的兩個例子,你可能會使用正則表達式。

例如,要分析這個例子:

@USER_A: give X points to @USER_B for accomplishing some task 

使用Ruby:

input = "@abe: give 2 points to @bob for writing clean code" 
PATTERN = /^@(.+?): give ([0-9]+) points to @(.+?) for (.+?)$/ 
input =~ PATTERN 
user_a = $~[1] # => "abe" 
x  = $~[2] # => "2" 
user_b = $~[3] # => "bob" 
why = $~[4] # => "writing clean code" 

但如果有更多的複雜性,在某些時候,你可能會發現它更容易和更易於維護使用真正的解析器。如果你想要一個與Ruby運行良好的解析器,我推薦Treetop:http://treetop.rubyforge.org/

把字符串轉換成代碼來逃避的想法讓我感到緊張。使用eval是一個很大的風險,應儘可能避免。還有其他方法可以實現您的目標。如果你願意,我會很樂意提供一些想法。

關於你建議的DSL的問題:你打算在你的應用程序的另一部分本地使用它嗎?或者只是計劃將它用作過程的一部分,以將字符串轉換爲您想要的行爲?我不知道什麼是最好的而不知道更多,但如果你只是解析字符串,你可能不需要DSL。

+0

我真的只是試圖想出一個強大的方法來巧妙地解析一個字符串,如「記分員,給@USER_A Y點,並從@USER_B X點作爲一個混蛋。」從這裏,字符串,我需要拉出「+ Y-> USER_A」和「-X-> USER_B'是一個混蛋'」使用正則表達式變得unweildy(即我給USER_A X點B/C我解析爲「用戶+點」之前「點+用戶」 - 我不是正則表達式大師,顯然)。我只是認爲探索DSL選項可能是明智的,但由於我沒有真正在其他地方使用它,所以強大的正則表達式可能是更好的選擇。會喜歡一些想法。謝謝。 – JohnMetta 2010-02-08 20:43:46

+0

我剛剛在上面的答案中發佈了一個示例正則表達式。 (我嘗試在這裏粘貼,但格式不是很好。) – 2010-02-08 22:54:58

+0

謝謝!這比我有更多的正則表達式。我試圖通過創建DSL來回答的問題是如何巧妙地解析這個和最後一個評論示例。例如,在用戶「give @ bob 4 points」之後允許點也應該是有效的,「for」是可選的,等等。但是我現在匹配的較小比特和匹配完整的語句可能會讓我沒有比較好地匹配多種可能的模式,比如說錯誤的個人得分是錯誤的。謝謝。 – JohnMetta 2010-02-08 23:16:34

1

這反映了我對一個正切項目(舊式文本MOO)的一些想法。

我不相信一個編譯器式的解析器將會是程序處理英文文本的廢話的最佳方式。我目前的想法讓我把對英語的理解分解爲單獨的對象 - 所以一個盒子理解「打開盒子」而不是「按下按鈕」等等 - 然後讓對象使用某種DSL來調用集中代碼實際上會讓事情發生。

我不確定您是否已經知道DSL如何實際幫助您。首先,您可能需要看看英文文本如何轉化爲DSL。我並不是說你不需要DSL;你可能是對的。

至於如何做到這一點的提示?好吧,我想如果我是你,我會尋找特定的動詞。每個動詞都會「知道」它應該從周圍的文本中得到什麼樣的東西。所以在你的例子中,「to」和「from」會讓用戶立即跟隨。

這與您在此發佈的代碼IMO沒有特別的區別。

你可能會看到my question的回答。一位評論者向我指出瞭解釋器模式,我發現它特別有啓發性:有一個很好的Ruby示例here

+0

Geez。我一讀到「解釋模式」,就想:「呃,呃!」現在我感到很傻。不知道爲什麼我沒有首先探索這個更簡單的解決方案。我甚至在閱讀「Ruby中的設計模式」。多可笑。這可能是我應該採取的路線,如果普通的正則表達式不夠強大。 – JohnMetta 2010-02-08 22:38:01

+0

讓你比我更聰明 - 我從來沒有聽說過它。 – Shadowfirebird 2010-02-09 09:25:25