2009-10-03 9 views
6

私はHaskellとAlexの小さな言語のためのレクサーを書いています。alex/haskellでpythonスタイルのインデント/デントントトークンを作成するにはどうすればよいですか?

インデントレベルが変更されるたびにINDENTトークンまたはDEDENTトークンが出力されるように、この言語には重要なインデントが設定されています。

Cのような伝統的な命令型言語では、グローバルをレクサーに保持し、各行のインデントレベルでそれを更新します。

アラスカ/ハスケルではこれはうまくいきません。なぜなら私はハスケルのどこにでもグローバルデータを保存することができず、すべてのレクシングルールをモナドの中に入れることができないからです。

どうすればいいですか?それも可能ですか?または私は自分のレクサーを書いて、アレックスを使わないでください。

答えて

11

Haskellのような他の空白に敏感な言語では、レイアウト処理は実際にレクサーで行われることに注意してください。実際、GHCはAlexのレイアウト処理を実装しています。ここでは、ソースです:

https://github.com/ghc/ghc/blob/master/compiler/parser/Lexer.x

jrockwayが指摘するように、道に迷ってあなたを導くあなたの問題のいくつかの重大なエラーがあります。 「私はハスケルを持つどこのグローバルデータも間違ったトラックに保存することはできません。まず、にグローバルな状態があり、第2に、アレックスが安全にルール内の状態遷移を完全にサポートしているときに、ここでグローバルな状態を使用すべきではありません。

Alexが提供するAlexState構造を見て、あなたのレクサーをスレッド状態にすることができます。次に、レイアウトルールのインデント/インデントを実装するために、GHCのレイアウト実装でどのように状態が使用されるかを見てください。 (GHCのレクサーで " - レイアウト処理"を検索して、状態がプッシュおよびポップされた状態を確認してください)。

+0

ニース。私はAlexとちょっと遊んでしまい、PArrows(これは通常私が手に入れているもの)よりもいくつかの点でよりクリーンであることがわかりました。情報ありがとうございました:) – jrockway

+0

ああ、ありがとう。私は#haskellでも尋ねましたが、alexのUserStateラッパーを発見しました。しかしそれほど多くのドキュメントはありませんでしたが、いくつかのソースハンティングをしなければなりませんでした。 私は国家のモナドについて知っていますが、私はアレックスのレクサーを通してスレッドの状態について不明でした。 ありがとうございます。 – kamatsu

5

私は、これは真実ではありませんHaskellの

でどこでも任意のグローバルデータを保存することはできません。ほとんどの場合、State monadのようなもので十分ですが、ST monadもあります。

ただし、このタスクではグローバルな状態は必要ありません。パーサーの記述は2つの部分で構成されています。字句解析と構文解析が含まれます。字句解析は、文字ストリームを意味のあるトークンストリームに変換するだけです。構文解析により、トークンがASTに変換されます。これはインデントに対処すべき場所です。

インデントを解釈するときに、インデントレベルの変更に応じてハンドラ関数を呼び出すことができます。ネスティングが増加すると、ハンドラ関数を呼び出します(インデントを追跡する場合は、1つのargをインクリメントします)レベル);レベルが減少すると、関連するAST部分が関数から返されます。

(大域変数を使用すると、命令型言語では私には起こりえない何かがありますが、それがインスタンス変数です。ステートモナドはこれと概念的に非常に似ています)

最後に、私は「私のレキシングルールをすべてのモナドの中に入れることはできません」という表現は、モナドの何らかの誤解を示していると思います。代わりに(.)または($)と機能のアプリケーションを組み合わせる

data AST = ... 
type Step = State Int AST 

parseFunction :: Stream -> Step 
parseFunction s = do 
    level <- get 
    ... 
    if anotherFunction then put (level + 1) >> parseFunction ... 
    else parseWhatever 
    ... 
    return node 

parse :: Stream -> Step 
parse s = do 
    if looksLikeFunction then parseFunction ... 

main = runState parse 0 -- initial nesting of 0 

、あなたが(>>=)または(>>)と組み合わせる:私が解析し、グローバルな状態を維持するために必要な場合は、私のコードは次のようになります。それ以外のアルゴリズムは同じです。あなたは "funcallの" のようなものを持っている場合

eval :: Environment -> Node -> Evaluated 
eval e (Constant x) = Evaluated x 
eval e (Variable x) = Evaluated (lookup e x) 
eval e (Function f x y) = (f <$> (`eval` x) <*> (`eval` y)) e 

(または

eval e (Function f x y) = ((`eval` f) <*> (`eval` x) <*> (`eval` y)) e 

:(。 "内部" すべき "モナド" はありません)

最後に、あなたは応用的ファンクタを好むかもしれません...しかし、私は逃げます。)

アプリケーションファンクタ、モナド、および矢印を使って解析することに関する文献はたくさんあります。これらのすべてがあなたの問題を解決する可能性を秘めています。それらを読んで、あなたが得るものを見てください。

+0

私はプログラミング言語の設計に精通しています。ちょうどhaskellとalexにあまり慣れていません。情報をいただきありがとうございますが、私はすでにそれについてすべて知っていました。 「グローバルな状態を入れていない」とは、具体的には状態のモナドに入れるのではなく、むしろいくつかの変更可能な状態を持つことです。そして、 "私の字句規則をモナドに入れない"ことによって、私はアレックスの規則によって状態モナドを作成することができず、実際には達成可能であることを意味しました。あなたが言ったことの大半は私には役に立たなかったように私に出くわしましたが、とにかく#haskellの助けを借りて問題を解決することができました。ありがとうございます – kamatsu

+1

レイアウトはほとんど常にレクサー内で行われます。 – kamatsu

+0

状態モナドは「可変状態」ではないのはどうですか?技術的には変更はできませんが、(>> =)のソースコードを読むまでは気付かないでしょう。 – jrockway

関連する問題