インデント対応パーサ
インデントでなんとなくパースできるのができた
やっぱり文法は普通にしてlexでなんとかするのがいいかな
%default Node %token<Node> BEGIN END %token<Node> var expr %left '+' '-' %left '*' '/' %% program : void | program stmt stmt : begin block end begin : BEGIN {Console.WriteLine("".PadLeft($1.Indent) + "BEGIN")} end : END {Console.WriteLine("".PadLeft($1.Indent) + "END")} block : line | stmt # いらないかも | block line | block stmt line : void EOL | expr EOL {Console.WriteLine("".PadLeft($1.Indent + 1) + $1.Name)} expr : var {$$ = $1} | expr '+' expr {$1.Name = "(" + $1.Name + ") + " + $3.Name : $$ = $1} | expr '-' expr {$1.Name = "(" + $1.Name + ") - " + $3.Name : $$ = $1} | expr '*' expr {$1.Name = "(" + $1.Name + " * " + $3.Name + ")" : $$ = $1} | expr '/' expr {$1.Name = "(" + $1.Name + " / " + $3.Name + ")" : $$ = $1} var : VAR {$$ = New Node With {.Name = $1.Name, .Indent = $1.Value.Indent}} void :
lex側は
要は前にEOLがあればインデントを数えて前回より上ならBEGINを差し込みスタックに積む、下ならENDを差し込みスタックを下げる
前がEOLでなかったら特になにもしない
EOFが出ればスタックの分だけENDを差し込む
・・・なんだけど、ひたすら長くなった、泣きそう
Private prev_ As IToken(Of Node) = Nothing Private next_ As IToken(Of Node) = Nothing Private indent_stack_ As New List(Of Integer) If Me.next_ Is Nothing Then Me.next_ = Me.ReaderNext If Me.next_.InputToken = SymbolTypes.EOL Then Me.next_.Value.Indent = 0 If Me.indent_stack_.Count > 0 Then Me.next_.Value.Indent = Me.indent_stack_(Me.indent_stack_.Count - 1) Me.prev_ = Me.next_ Me.next_ = Nothing Return Me.prev_ End If If Me.prev_ IsNot Nothing AndAlso Me.prev_.InputToken <> SymbolTypes.EOL AndAlso Me.prev_.InputToken <> SymbolTypes.END AndAlso Me.indent_stack_.Count > 0 Then Me.next_.Value.Indent = Me.indent_stack_(Me.indent_stack_.Count - 1) End If If Me.indent_stack_.Count > 0 AndAlso (Me.next_.InputToken = SymbolTypes._END OrElse Me.next_.Value.Indent < Me.indent_stack_(Me.indent_stack_.Count - 1)) Then Dim block_end As IToken(Of Node) = Me.CreateBlockEnd(Me.indent_stack_(Me.indent_stack_.Count - 1)) Me.indent_stack_.RemoveAt(Me.indent_stack_.Count - 1) Me.prev_ = block_end Return block_end End If If Me.next_.InputToken = SymbolTypes._END Then Me.next_.Value.Indent = 0 Me.prev_ = Me.next_ Me.next_ = Nothing Return Me.prev_ End If If Me.indent_stack_.Count = 0 Then Me.indent_stack_.Add(Me.next_.Value.Indent) Return Me.CreateBlockBegin(Me.next_.Value.Indent) End If If Me.prev_ IsNot Nothing AndAlso (Me.prev_.InputToken = SymbolTypes.EOL OrElse Me.prev_.InputToken = SymbolTypes.BEGIN OrElse Me.prev_.InputToken = SymbolTypes.END) Then Dim prev_indent As Integer = Me.indent_stack_(Me.indent_stack_.Count - 1) Dim next_indent As Integer = Me.next_.Value.Indent If prev_indent = next_indent Then Me.prev_ = Me.next_ Me.next_ = Nothing ElseIf prev_indent < next_indent Then Me.indent_stack_.Add(next_indent) Me.prev_ = Me.CreateBlockBegin(next_indent) Else Me.indent_stack_.RemoveAt(Me.indent_stack_.Count - 1) Me.prev_ = Me.CreateBlockEnd(prev_indent) End If Else Me.next_.Value.Indent = Me.indent_stack_(Me.indent_stack_.Count - 1) Me.prev_ = Me.next_ Me.next_ = Nothing End If Return Me.prev_ Protected Overridable Function ReaderNext() As IToken(Of Node) RESTART_: If Me.EndOfStream() Then Return Me.CreateEndOfToken_ ' lex char Dim indent As Integer = 0 Dim c As Char = Me.NextChar Do While Char.IsWhiteSpace(c) indent += 1 Me.ReadChar() If Me.Parser.IsAccept(Me.eol_) AndAlso (c = Convert.ToChar(10) OrElse c = Convert.ToChar(13)) Then Return Me.CreateEndOfLine If Me.EndOfStream() Then Return Me.CreateEndOfToken_ c = Me.NextChar Loop If c = "#"c Then Me.ReadLineComment() GoTo RESTART_ End If Return Me.ReaderToken() End Function
ちょっとシェルスクリプトっぽい言語を実装してみようか